mirror of
https://github.com/zotanmew/nginx-rtmp-module.git
synced 2024-06-02 06:39:32 +02:00
Compare commits
1221 commits
Author | SHA1 | Date | |
---|---|---|---|
Laura Hausmann | a5f44b6d54 | ||
Laura Hausmann | cfb277dea7 | ||
Laura Hausmann | d2d55348d6 | ||
3b7ada6677 | |||
22861b746d | |||
6e40dbe805 | |||
5f8a96f505 | |||
9102003adf | |||
ca9fd4a380 | |||
0668f512b6 | |||
e57666bcf4 | |||
Laura Hausmann | 2fee90d89f | ||
Laura Hausmann | e73d44f0fb | ||
9eefedac83 | |||
c4dcf60e63 | |||
e8ff79dfb9 | |||
8e344d7994 | |||
4d15e2c0f1 | |||
d4d762e917 | |||
5cb0be7f09 | |||
1688a23f0a | |||
a2895a03d9 | |||
649d220306 | |||
ca1f3eeaa2 | |||
a4a1343bb8 | |||
14221340d3 | |||
f7254ae5e8 | |||
fe35e3e98b | |||
cdbb1d4dc1 | |||
d743c36996 | |||
59c2454b23 | |||
6c4be06423 | |||
23ec4ce2d7 | |||
882ef5ca1e | |||
eee3c5eb15 | |||
a0a55be887 | |||
ce5a10a0d1 | |||
3bf7523267 | |||
b2049f3c39 | |||
a5ac72c274 | |||
00fd6cfa53 | |||
b4ee055393 | |||
e2a626ac04 | |||
7b7d30f36c | |||
e5b78f2de7 | |||
a65297410e | |||
669059f41b | |||
ff3536996c | |||
15cc5d0226 | |||
504b9ee29d | |||
23d67822b2 | |||
21db986d97 | |||
a01cc448ee | |||
a898a09d87 | |||
916f3f8374 | |||
1c3dc989ef | |||
d25c56fa69 | |||
e65f2d099b | |||
f31e27fbaf | |||
bb4190e248 | |||
542106e4de | |||
9121b34bdc | |||
ff86f5c3fd | |||
f23323a51a | |||
f8992e572f | |||
4975784d46 | |||
07912c5cd1 | |||
95d81573c9 | |||
bc81475b6b | |||
6b8155cf3b | |||
9c71ce6761 | |||
d86287fe3c | |||
ebe697b601 | |||
dc76eb2641 | |||
18b228a01d | |||
4bf6852a28 | |||
4809496d78 | |||
315e8aa497 | |||
dbcb7aa966 | |||
2fd45d4114 | |||
c47cb2370f | |||
a037181c59 | |||
7381b66e13 | |||
a88bc39141 | |||
998de2937a | |||
a2d65b4251 | |||
26d6107307 | |||
e38fcac9c9 | |||
e4799c633a | |||
7db5ef0ea5 | |||
a9e0056d5b | |||
1d5a20ea2b | |||
eca3fa3b04 | |||
2b0596051e | |||
77ba897d2f | |||
6d9a85e061 | |||
b4ecd58544 | |||
aee81e3c8f | |||
45a02da89e | |||
51396cdebb | |||
358806e915 | |||
14b56c4a5b | |||
0df743179d | |||
965523f397 | |||
fe122c1597 | |||
a48dadfbc1 | |||
341b07409d | |||
970da5673d | |||
570204bdeb | |||
8b97be9593 | |||
c3237ae747 | |||
62748fe56d | |||
1e6ae8d94d | |||
281d2226d9 | |||
89dd74e666 | |||
4f96ff087d | |||
16851c4512 | |||
28f75cb86d | |||
2a6b426247 | |||
d171a0a9b0 | |||
0d94bb2c84 | |||
0bd7d6b375 | |||
307c8d969a | |||
93e9377dc6 | |||
5376bd3432 | |||
98f700a090 | |||
0bfbd6b39f | |||
f15596b8d1 | |||
64c0529fde | |||
01825510f7 | |||
93cf3b69f1 | |||
bfaccfd738 | |||
7eb100a306 | |||
a3924dce67 | |||
66b3bcf096 | |||
65e24b3fee | |||
182566fe93 | |||
e8304c9852 | |||
7e68afde6f | |||
d13e665e56 | |||
2855a9ffc1 | |||
86cfd20b28 | |||
cfadbd7779 | |||
c11797815d | |||
6666d789b5 | |||
ede4b5f0f4 | |||
9f75cc2c6e | |||
2d4613c906 | |||
fc013040b6 | |||
12595a21aa | |||
f9d89634ad | |||
f344f4ae92 | |||
d28e52b32b | |||
c0b592a57c | |||
5e179d7296 | |||
4ce7ea8b9d | |||
292a6c1ca8 | |||
f89d8c1973 | |||
96b69327fa | |||
298697a4da | |||
bf332f3794 | |||
a194707ea9 | |||
cd0d9f73a5 | |||
b9bdd89676 | |||
07a83d8750 | |||
b937376041 | |||
1b7d6148e5 | |||
63c87e8070 | |||
8c5139650d | |||
94343ad786 | |||
3352af8b73 | |||
118b808207 | |||
dc5add30a8 | |||
c206cd7978 | |||
9b25e901f8 | |||
f8da609671 | |||
06e49e05fd | |||
4c7dd6ed00 | |||
c80342e0ab | |||
b6687d6cab | |||
d1e9d8682e | |||
2bc42d90f2 | |||
9ec1c78749 | |||
cd416d5fd2 | |||
74fdeef568 | |||
0f73f4e05b | |||
f9aa396689 | |||
fd23b27f3f | |||
c078a7c3e0 | |||
17d5c4678d | |||
c9442e9aa3 | |||
f9c89a2e21 | |||
f1cde7d4a4 | |||
9faaa99829 | |||
509a9e2f75 | |||
f8097173ab | |||
bacdd48aac | |||
b666598e3a | |||
a344ee6607 | |||
7abda68ef7 | |||
754cca2a58 | |||
0d0882b29d | |||
83cb5a6f90 | |||
5b62a17516 | |||
498ff9a468 | |||
091936ea2d | |||
b4bbfff24b | |||
5d6ea2314f | |||
100ecc4fe8 | |||
8fda94268c | |||
414053862c | |||
f29fbc89a4 | |||
bd154e391b | |||
f0c9b595bc | |||
c39589c5ea | |||
854d5142df | |||
5504e4636c | |||
a92851011a | |||
b1e28ead5b | |||
335bf07021 | |||
24be2c72cd | |||
7f24b5f6cd | |||
bcbef42a7c | |||
b3f0fb0460 | |||
5ed7825c0c | |||
bb67548636 | |||
3497601b6b | |||
38f98aa81c | |||
139c8c2773 | |||
8409e4c499 | |||
cb439496eb | |||
ec3684f9f9 | |||
0327cbf651 | |||
ae7974bc2d | |||
f455876a9c | |||
4e17054a6d | |||
dec648681d | |||
46c0fe6d5a | |||
3207278846 | |||
2a68430ae2 | |||
70d056bf92 | |||
658f26fc2c | |||
4da120a125 | |||
fb80463ba9 | |||
21683a4408 | |||
25146e40f9 | |||
e9f58daf81 | |||
3170037e74 | |||
dc0b3df1b7 | |||
52b09f5300 | |||
ac4197b871 | |||
b49c7fb6ea | |||
5d4dfaee43 | |||
1e5b135b2b | |||
889e6afccb | |||
99cf8a1a10 | |||
1055727de8 | |||
de8faa55a7 | |||
c9e70d5994 | |||
593368e9e6 | |||
a05b3babfe | |||
961fde23f7 | |||
75fe5406b4 | |||
1616a378ef | |||
4c1b69b5bb | |||
038c3bbc67 | |||
0d77210815 | |||
c4ee944870 | |||
69d79ccda6 | |||
859540db60 | |||
087e5358e2 | |||
d797d269a4 | |||
95471ae880 | |||
a079875fd7 | |||
8341644121 | |||
098ded3e87 | |||
70c90bbac5 | |||
a4907e80ce | |||
48983747d3 | |||
6ce821727d | |||
809f094124 | |||
2c2e90b706 | |||
b053eca1f9 | |||
b92a262f4c | |||
955c7e6b64 | |||
f62a083806 | |||
cb36f9c23c | |||
1d1d4fcac6 | |||
459e730d34 | |||
8f0f2e75c9 | |||
17159755e5 | |||
de42f3801d | |||
1d21d68c3c | |||
2a54c8e089 | |||
d069f36f86 | |||
99433754df | |||
2362acf45c | |||
ad6c63c149 | |||
c54b413b43 | |||
7a35372e30 | |||
d99c069e8e | |||
5fb4c99ca9 | |||
e1f92b1409 | |||
dd5f2aa117 | |||
d574043a96 | |||
c390521963 | |||
83dc27d30a | |||
2262649c1f | |||
9ebfcc1978 | |||
0bb2323990 | |||
7500b4bd90 | |||
eb1cfef69e | |||
0b3d545ff6 | |||
5e1d735992 | |||
997f24c5c6 | |||
8099f44828 | |||
51ab327abf | |||
69c090d85f | |||
2f82fa2e8e | |||
773336e497 | |||
359d76b555 | |||
4bed919cbe | |||
416931d631 | |||
eff973ce01 | |||
53064a48f1 | |||
80d7b1c905 | |||
8acacd0d79 | |||
607a53842a | |||
812e2fd7b1 | |||
8c2229cce5 | |||
a72e33ea41 | |||
4e780909b9 | |||
876de488b8 | |||
abb017225b | |||
96855b1413 | |||
6f768dc4eb | |||
668125888b | |||
b31539ce95 | |||
0f337fe9a4 | |||
a43bafe3de | |||
dcf37b2c39 | |||
87bc473cde | |||
c76d0fad44 | |||
0210f7ca83 | |||
521f8998b0 | |||
434e6cfc06 | |||
04cbad5fda | |||
8d1b1c1243 | |||
1474aec731 | |||
78dcfbcc35 | |||
546c42efeb | |||
2eb36e3d7d | |||
acc1aa789f | |||
fac68de376 | |||
51c1459e04 | |||
39717828d6 | |||
a8094dfef1 | |||
8d9f99b540 | |||
036a7fd1d5 | |||
c392252301 | |||
65cd61e433 | |||
8a8f28f3b6 | |||
ea1fe6eaf5 | |||
8e62c72e86 | |||
8608faad1e | |||
5e5dd797c2 | |||
ee55c21b4c | |||
a8d148473d | |||
8d28f7f1de | |||
0166380bef | |||
0aaf2a6259 | |||
7dd70f88ea | |||
5507823b9d | |||
5c8d5b34fb | |||
0a33c0575e | |||
2f6a4fa279 | |||
13854403e2 | |||
5e8fe20497 | |||
b8b055dd77 | |||
95075aaa2a | |||
732b44883c | |||
f53d158432 | |||
7e19dce7c4 | |||
fe0d805179 | |||
ef424df677 | |||
57a96ac2bc | |||
5b68307710 | |||
307245415b | |||
095c659ad8 | |||
f45c0d50db | |||
0d1e1fec7c | |||
8542e21e17 | |||
b77337edab | |||
bcf7df1e70 | |||
61cb334917 | |||
6bb620f2fc | |||
da0128a2fc | |||
471d299f5d | |||
f39d3f66a3 | |||
0a5b06609b | |||
3a5f9eea78 | |||
18fa7a5016 | |||
aa4bcf89d7 | |||
7eb0f4e2d1 | |||
d98b24c4b7 | |||
e8081fd94f | |||
b84e00d5bf | |||
9e634ae882 | |||
51c80e2179 | |||
31f4788835 | |||
fb4793f48a | |||
38126a2c3f | |||
775f4e0682 | |||
bd73e06884 | |||
6f2e105800 | |||
4fd9c6c15c | |||
1b5e19d4e7 | |||
831823a772 | |||
b9fcf7d880 | |||
96e60a4ac0 | |||
6e1008ee9c | |||
047b72c192 | |||
d01ffc0c88 | |||
5bd02e7ed3 | |||
b07e0e2dee | |||
2f54340454 | |||
45b39f7762 | |||
ed09425a5d | |||
43ececd070 | |||
0eb32ae1ca | |||
d11823bcf8 | |||
7c7d1f1db2 | |||
43718388f4 | |||
c928742cb9 | |||
ec5687f459 | |||
46edc6ccfd | |||
4b3385562a | |||
fb3adeda9f | |||
a1b1c205f1 | |||
749cea7cb7 | |||
9abf8355d9 | |||
fcbd18c7f7 | |||
29dfda882a | |||
27df4c10af | |||
b73075e28c | |||
ed68e4a2eb | |||
15ec66a7bf | |||
995688b9cf | |||
a933495bf1 | |||
935156c352 | |||
ba94a0c1e4 | |||
33240c33df | |||
f7d65ba915 | |||
a95065b90d | |||
9227aca90d | |||
884366a2e0 | |||
f6af3f2343 | |||
24addede15 | |||
85dbf397e4 | |||
81ce4788f9 | |||
20584b05b8 | |||
bec6d8553a | |||
1d421c85f4 | |||
87ee276489 | |||
a9ddfed095 | |||
0dca45f0b6 | |||
8c0b83514c | |||
2c7111cf10 | |||
3b680915a8 | |||
df33838e37 | |||
fe4877f216 | |||
37033ed8c0 | |||
85d34da0d4 | |||
c18fd3aad4 | |||
6caee7cb88 | |||
0e07492797 | |||
7ea7690db2 | |||
6067ac21e0 | |||
748192877a | |||
5a38108bad | |||
503375fea8 | |||
0fb3ce40e0 | |||
8f9965b7f2 | |||
91c69aed28 | |||
401d7e929c | |||
1cfb7aeb58 | |||
b564a096fd | |||
8856d11dd9 | |||
f599c70568 | |||
a6913ea68a | |||
893e17343a | |||
040c96ed9b | |||
9177be55dd | |||
b62651efbd | |||
055228cc8a | |||
0ac369cd5a | |||
2f3ff168c3 | |||
c89459b784 | |||
f8588a450f | |||
97d144ae28 | |||
737ea5ce34 | |||
05548c515f | |||
857eca9a61 | |||
2060dccbf6 | |||
a5a59aa50c | |||
940ff26009 | |||
67443c28b4 | |||
f052e4cc48 | |||
0f8641d0c8 | |||
fa2b5b037f | |||
5eb6dce453 | |||
face62aa34 | |||
49a3ee0dc6 | |||
db3fc521d8 | |||
c442e1fc2d | |||
b4e77fa750 | |||
7b4df729c1 | |||
a3cffbb6c2 | |||
c11b916501 | |||
d7b4f138a4 | |||
488c571497 | |||
b52ebc8534 | |||
6b92cd6b29 | |||
0b5c799b25 | |||
337714edef | |||
2f1091679b | |||
4ec43349ad | |||
489f5cf5db | |||
e39b7f62a5 | |||
9a5e9a7790 | |||
9956185950 | |||
0f95890a73 | |||
8696ca005f | |||
afc765560f | |||
daa1c66e64 | |||
cbe760aa8a | |||
c4e78b6ea9 | |||
01c8daf4b4 | |||
4dcb52ae1a | |||
7e517f1619 | |||
3f045ccdca | |||
61dc8afa65 | |||
308dbd47d1 | |||
388d735460 | |||
47cdfc4b67 | |||
470c3effc8 | |||
d60c0bf1c7 | |||
14ba61505f | |||
cbd7a71723 | |||
0c35b73152 | |||
3b4452d43e | |||
dc1afb50be | |||
ec68e7c2d5 | |||
95447b3dc6 | |||
c41d4e7810 | |||
1bd6aed4c8 | |||
49f8f4d097 | |||
56b54b1215 | |||
161e36ee9d | |||
cb2a18a3ec | |||
248335216a | |||
9389670657 | |||
c752e0696c | |||
479a574cec | |||
8c81d3ef39 | |||
db34613f1a | |||
e9a9c5f4bd | |||
b93e2cdff6 | |||
e4070fb777 | |||
8553cf204b | |||
f94fcee9fb | |||
021db6cfd9 | |||
f114ce4ce9 | |||
289ee42c53 | |||
613234b866 | |||
22984bc309 | |||
e29e64ed7d | |||
3d70ba4777 | |||
a47b23204b | |||
58bd602946 | |||
aeb20d0a24 | |||
849dbf6b89 | |||
244961076f | |||
db5d361743 | |||
6fa61c090e | |||
8a332610bc | |||
beb76aa8d5 | |||
36d744d257 | |||
8958f0b33d | |||
11e14ee928 | |||
1d30518c8f | |||
ed98ca42ef | |||
f4d974425c | |||
09db8210a7 | |||
de2a4258d7 | |||
73f94b99ff | |||
97a5a860e2 | |||
dd75b749da | |||
d0fb258d10 | |||
b0de943956 | |||
7688a62874 | |||
c61e99a368 | |||
d422c3685d | |||
f79aa206ec | |||
6dc5b12f03 | |||
0ad74ac2bf | |||
2bb16425da | |||
c0fc4bbdcc | |||
c2fcd8c09c | |||
5db5d5af24 | |||
f35e965033 | |||
7b5c5e9932 | |||
0958e4f034 | |||
76f433fb45 | |||
b9e5bb6ef0 | |||
61b9c53972 | |||
d393508e58 | |||
d34c6393f6 | |||
3fc1598db9 | |||
709b173bdc | |||
dae4c77e93 | |||
2e6d8b219a | |||
a3db04dd5b | |||
9b24a2f11a | |||
f9532fea8c | |||
6a1cf17dcb | |||
daae21764e | |||
05e0a68281 | |||
5d8a534aa4 | |||
0bd07562c5 | |||
921b28112c | |||
059dba448d | |||
814bcf4825 | |||
eee52d83c4 | |||
682d24d682 | |||
92310a7240 | |||
e68add616c | |||
612fc4dd75 | |||
9431058e3e | |||
47f74f7968 | |||
7f7fcc8d5c | |||
15fe840681 | |||
1a0dd69f34 | |||
9aa030f51c | |||
6298aa7e1d | |||
e5d2df6fc0 | |||
0a8f0ee6c5 | |||
2986cb7301 | |||
11e3f53fd2 | |||
5d28b1b44c | |||
5d3189ad97 | |||
1894d333aa | |||
368466b0ea | |||
018daa47b5 | |||
6a848ea47c | |||
e22ca28604 | |||
b96bcfc4ed | |||
4eca0892a3 | |||
8cc2a64895 | |||
ac5df69487 | |||
b40024492e | |||
f619e072ff | |||
53e68df5a9 | |||
e916ca9ad0 | |||
0e01665e0b | |||
4155794f45 | |||
3e25f91004 | |||
3aa528fccd | |||
6923889e53 | |||
57dd1406bd | |||
8526a59fd4 | |||
ee4f57bc69 | |||
0b6a84b0b8 | |||
af8446a098 | |||
349259329d | |||
43539b7234 | |||
6903ac23e4 | |||
e28dfad05c | |||
4151532589 | |||
6db8ab7963 | |||
138c330da2 | |||
5e55f62b88 | |||
561c421f54 | |||
2aabf02fe4 | |||
5715784e7c | |||
6b96942097 | |||
8104bff06f | |||
6d5986aa59 | |||
36002359c2 | |||
d59438d92d | |||
5325df0135 | |||
f4b29b0547 | |||
c514c9f715 | |||
c0ad999f37 | |||
027c65cd8f | |||
8c39b37131 | |||
2741432356 | |||
f358a2e8f9 | |||
2a9d41496a | |||
aefd113e8d | |||
98efe9ed7f | |||
b2ba2680a3 | |||
67aa7d5d0d | |||
eb1baa936e | |||
2c235d8633 | |||
f89bbae235 | |||
0153d0865b | |||
00a640d0d6 | |||
5d00a5bb6c | |||
aeae84bf8b | |||
af5703b357 | |||
9c88e12cf8 | |||
ea65ac688c | |||
2ed36a2611 | |||
4906e816be | |||
e160af92d7 | |||
ccb0de2500 | |||
413ec7be56 | |||
58f5073d4e | |||
9b3471d79f | |||
3bd60857bd | |||
406d3a9527 | |||
77860c0036 | |||
b85499c144 | |||
3dff38d716 | |||
5fd0f21eb6 | |||
ada1efd594 | |||
59eac372ab | |||
ba23d995d1 | |||
b6194ed6e6 | |||
5a4ee6392b | |||
54d9d41bf0 | |||
3eaa43a7a3 | |||
837856e03b | |||
cbccd06bab | |||
06f4699399 | |||
b86bc7636e | |||
c418e9cc33 | |||
b0a7fa1c6b | |||
73e877bbeb | |||
e5e5766ebb | |||
bd562e4ff7 | |||
e19ff641b0 | |||
f2f28cbe48 | |||
3d54d69e25 | |||
7294825de9 | |||
1a053d02a6 | |||
b3e8cd026d | |||
a09b5b6e6c | |||
96b0fddbb1 | |||
0a4296b9a2 | |||
8658d99529 | |||
a45e3d5cbe | |||
a0a4cc21b4 | |||
5c947253af | |||
6ab14605ba | |||
ac924d7f94 | |||
9f4296c083 | |||
990222d643 | |||
18e4762db2 | |||
02dd440a25 | |||
696e488ecb | |||
d6abe23bbc | |||
098d6db0fa | |||
176a436942 | |||
ebca0917a4 | |||
fa3e5a35c1 | |||
5db704cf2b | |||
b145f3bc9c | |||
dec5e44160 | |||
b0af7648c6 | |||
36e9b5f52d | |||
95880e2bb6 | |||
5e1f7f17aa | |||
22de95e634 | |||
1ee8979804 | |||
52e3819d26 | |||
ec3fe5cdac | |||
7ae35dbdaa | |||
96902cf81a | |||
b16983e043 | |||
0c1ac736ec | |||
8f1cba309f | |||
6d5a33df75 | |||
6d37487635 | |||
ffe04bbea8 | |||
745b0494b1 | |||
62e03d710c | |||
bc4f8317ff | |||
55c5b229da | |||
35380300e3 | |||
7c47087c5b | |||
6ed71b7c22 | |||
e0a82e47a9 | |||
cbbb7b66db | |||
b439dacb82 | |||
5999daebc2 | |||
ba58e69b90 | |||
753a92f105 | |||
a46983c2a2 | |||
3192d657f1 | |||
fd99086834 | |||
93b6692735 | |||
6f2808c7d1 | |||
445c45145d | |||
f7fab67ffb | |||
70e4ff5bcd | |||
1b28f94042 | |||
1ef89f4089 | |||
8a96e7942a | |||
903abb6646 | |||
d8386ce7e0 | |||
b5461f61c6 | |||
7fbfb36440 | |||
e178a42c63 | |||
284e521e82 | |||
9f85961535 | |||
8d4f850817 | |||
d3ce27a96c | |||
d143af86de | |||
924f2c1b56 | |||
98d959ac53 | |||
20b55b394a | |||
5824635938 | |||
c65e51d61b | |||
101b43a478 | |||
4adc5f7487 | |||
b8424c9b63 | |||
eacfd8fac4 | |||
f65f07deb3 | |||
c86e30fd27 | |||
cb4623aa4f | |||
a3fde6ea53 | |||
ff1c7ecec5 | |||
795c1538a3 | |||
21174b2ee7 | |||
a4c790e0ee | |||
5aac8b5cb3 | |||
5cd5f83d8d | |||
67e7f4818f | |||
ebb20b848e | |||
f7e7ea0fc1 | |||
f23075c78d | |||
e5d61f2c62 | |||
72d175ed7f | |||
bff1c355ec | |||
2ef9075f2b | |||
fb824371e7 | |||
0740b046d6 | |||
6e55e3a77f | |||
cc632eb6b6 | |||
83aa7b9e89 | |||
ce7ae837b3 | |||
01d3b8857c | |||
9b4725a01a | |||
b4ca0bede8 | |||
87686029aa | |||
ac13bbf1af | |||
0b757e8a49 | |||
e630c91391 | |||
0cd7883a43 | |||
b70d37edb1 | |||
3d6d65f7c7 | |||
840b62bf71 | |||
17886deadd | |||
6485716fb4 | |||
cdb084e375 | |||
9aff59fc43 | |||
76211a4bd2 | |||
129a161fb8 | |||
abc2704db1 | |||
11cc6e6019 | |||
d8fbe51e3f | |||
8494e179d1 | |||
e1650d8629 | |||
3f08385144 | |||
9a31ca9237 | |||
a4d55c0ff5 | |||
6b0dddbcc0 | |||
ed5f06db8a | |||
ff247dafff | |||
52c2ef3f91 | |||
067c73710d | |||
2ae585d99b | |||
f221540a78 | |||
004accda9a | |||
6951ef8d18 | |||
3a403f8563 | |||
b4e7e68ce8 | |||
02b9e2d4f7 | |||
81171a8582 | |||
8c5b864339 | |||
4a775d6f0b | |||
96526896aa | |||
94fdac52a7 | |||
5d7a5ea535 | |||
6b5f76654c | |||
3c2e29f215 | |||
acf6ea076c | |||
1d4f5023f3 | |||
9aa411409d | |||
764a3583b2 | |||
1dbd9b228d | |||
562a6aad7e | |||
3492892450 | |||
8293c425a5 | |||
58491f8fed | |||
224b6d942a | |||
24539ca116 | |||
7f447bbe95 | |||
a92d23d530 | |||
f22e72ab21 | |||
db0f765528 | |||
a6c10a15bd | |||
82b55fce63 | |||
1968afdcc5 | |||
8b25656409 | |||
5576444928 | |||
6b1ba3fe07 | |||
e426519a2f | |||
06b5712df1 | |||
c200683397 | |||
e243e814ab | |||
d5a1913072 | |||
5af9dee9b3 | |||
820753e546 | |||
05927f717b | |||
2bd3d17fbb | |||
ecb6462d56 | |||
256587016e | |||
250ca9818a | |||
c127250a8b | |||
e80a2c4f57 | |||
3056478b1c | |||
cb8d25f366 | |||
38270e5ca0 | |||
cbd359e189 | |||
75020e9d9f | |||
45c9b7e225 | |||
4cbe407bd3 | |||
8e181cdb19 | |||
35425eeedb | |||
50931a9e29 | |||
3d3d898430 | |||
d01128a3fb | |||
295551947a | |||
309285632f | |||
219de8eded | |||
dea898a6ea | |||
e0f9d944a0 | |||
07cd2644c8 | |||
5070ff151a | |||
0d1d7138e5 | |||
aca6162ebc | |||
dd19680ed9 | |||
b960e68a0b | |||
4e475cccb5 | |||
dbc3ac2438 | |||
e21ba2dc2c | |||
1a2a8e2867 | |||
7b27c6cd17 | |||
cbaff8f1df | |||
bfb7df5f6a | |||
a28626f661 | |||
dcd716e256 | |||
7e6d0c6693 | |||
bde580020f | |||
06dc82f912 | |||
7cd376686a | |||
5042963256 | |||
eb4e9e8d90 | |||
413443c6d7 | |||
d25f7c3c5a | |||
6ad152ee4c | |||
28e5b05e01 | |||
d0ec6881c1 | |||
64c5eb3d88 | |||
fb89d9b931 | |||
2149ca64e5 | |||
92b20ce565 | |||
26d932eb1c | |||
58bc83de8a | |||
0478c4445f | |||
313fd228a1 | |||
47bade991b | |||
45663d471d | |||
52c23332e5 | |||
0d2672938f | |||
64b24423d5 | |||
3caf2fd7b4 | |||
19d3bd6cc9 | |||
447da4f7e3 | |||
8def5f3945 | |||
9788b1a530 | |||
fa3630d63d | |||
ba87ca48c5 | |||
f2eadabf0e | |||
792f5aedc7 | |||
bf487dba0b | |||
71f92aca8d | |||
51647d03b3 | |||
d912561f65 | |||
223b2713af | |||
87d1cebfe2 | |||
d9579d627d | |||
c9973fc68a | |||
68515773e5 | |||
1719ef4433 | |||
62c750ab8b | |||
32ab32f168 | |||
083b0cd629 | |||
53fd032ae1 | |||
ae71903de4 | |||
f48bcc5235 | |||
78f6ccd604 | |||
63c4269a91 | |||
d21a240b12 | |||
5493fad47d | |||
45cd9825a2 | |||
5dd0120cc8 | |||
5153992d5d | |||
1d44a18ad2 | |||
633abd83a1 | |||
1e0e98a439 | |||
dc698edf24 | |||
43ceafc186 | |||
cba4ecc560 | |||
54d536bdeb | |||
7707515810 | |||
8855a1e1be | |||
2c1fbcfb8f | |||
f679b888c8 | |||
b188f679b9 | |||
2fdec45460 | |||
7c99cfba0a | |||
358f7680e2 | |||
1da4bb923e | |||
bf04e66862 | |||
95e2c4172a | |||
fca76ef349 | |||
5539cb7935 | |||
356b824af3 | |||
696cfda126 | |||
266e206afc | |||
c377e3b5c6 | |||
88346934e5 | |||
edd957f5cd | |||
077a2a7c67 | |||
218e312a1f | |||
b69efd3e94 | |||
4eda3e8392 | |||
6143abc418 | |||
4b25e34338 | |||
ebc47f03a4 | |||
9a118b612e | |||
6be685f21b | |||
cc1e4899cc | |||
7c13597508 | |||
7bab4ee919 | |||
4bb48483be | |||
eec4b7b401 | |||
61b253c08c | |||
b2a095c3bb | |||
aa0c204ef3 | |||
e389302642 | |||
108bae8695 | |||
63e19f8d67 | |||
09eee918b9 | |||
670e72c8d5 | |||
e8e61e3d96 | |||
2218a36fca | |||
c8d55c0d2d | |||
b35147450b | |||
de985dabd2 | |||
61ffd994bc | |||
224615dbbb | |||
613e7072c1 | |||
5ced4aea88 | |||
3027868a50 | |||
7112fd9fd4 | |||
744ff2f723 | |||
8eddc92f2f | |||
345a00f9a3 | |||
49382c826b | |||
52f8295d5c | |||
ac93e06d65 | |||
3fb41195a5 | |||
30924a47ae | |||
cfab13d47d | |||
a6a430ec2a | |||
7e344dc2c9 | |||
63713c650d | |||
95570ce2c5 | |||
aa791de4cc | |||
6e471119cc | |||
eae15c6013 | |||
ec28243851 | |||
d105a251f7 | |||
107ea3699e | |||
32279ddf26 | |||
1f9072bbe6 | |||
806ef97ba3 | |||
70b27b1971 | |||
da1915bb56 | |||
bd51d4b18d | |||
83dbef4567 | |||
7cad98106f | |||
c3067ced06 | |||
f17fd5a032 | |||
a4f48c5baa | |||
4d4af44bf2 | |||
17188b25eb | |||
f209925e4b | |||
6761a88516 | |||
7d895d54a6 | |||
1e4c1af565 | |||
87b7c6d043 | |||
e572e242c2 | |||
d82c16499d | |||
4e713b75e8 | |||
7b88858b44 | |||
591d7f5f3f | |||
a201dd0c4a | |||
bd40fe63f9 | |||
606675085d | |||
ca26b6ab94 | |||
f6ccfb6fa1 | |||
c61d7ac56f | |||
ac90157265 | |||
239b947b4d | |||
bf8a4ed916 | |||
51f7d3012d | |||
8c9e3ad36f | |||
391bb0b392 | |||
513f2f7e83 | |||
15405e8edd | |||
3f9dea711d | |||
c1d21a76a9 | |||
0c9254647b | |||
fb36b95425 | |||
ed2125f5d2 | |||
bb5b770757 | |||
c9eb9291bb | |||
5722ad5761 | |||
1051367274 | |||
6ae16bf9d8 | |||
40684b12b4 | |||
201d1b28ae | |||
a041a9d75c | |||
e060e717f3 | |||
b063daecdb | |||
427614a35a | |||
6e01a91c9a | |||
e9426a4090 | |||
9a21b694af | |||
3152e03dd3 | |||
3d6d474290 | |||
4343ec28e7 | |||
c066285733 | |||
f9f1bcaa6e | |||
c5a8a39019 | |||
02e35b776e | |||
c9eb30083e | |||
b61d811f96 | |||
b08d308ed0 | |||
359c346d35 | |||
97815a66c7 | |||
20411201bc | |||
6295147db1 | |||
90f985fa2f | |||
7aa513cfcd | |||
4f3cbb76bd | |||
a43412edd1 | |||
dbbd320864 | |||
fd54d6cc0f | |||
c34d07c012 | |||
f118918a47 | |||
cd3bef4b05 | |||
cf1976cd05 | |||
321c4899cc | |||
a0fe6e5ea4 | |||
20a8d06eb6 | |||
cd3aea8d8c | |||
2a3408cf40 | |||
686f57ce74 | |||
0945de3219 | |||
2e4ba3d102 | |||
f6d2406ae7 | |||
498b505622 | |||
d3c5ad1196 | |||
8f648495ec | |||
ed3337d588 | |||
67a621de57 | |||
dccb9e4044 | |||
cff6a4aca7 | |||
18d8526793 | |||
a1ee083e25 | |||
38c7cf3137 | |||
bf7d0acad2 | |||
f47cec2601 | |||
e6ba076c2d | |||
caec91b857 | |||
bcd601832a | |||
fa6d9fc84f | |||
7f0f9f6ba3 | |||
c1d778314d | |||
8b1d1d32e0 | |||
d943d51975 | |||
2c52b09841 | |||
1934e838df | |||
31d18ed447 | |||
e563c3146e | |||
7b70e92413 | |||
3980a59237 | |||
f2746b6ee1 | |||
140a322b99 | |||
f925fd0bc6 | |||
d641811545 | |||
50df6be855 | |||
1e9a7e6efc | |||
b9ee8dbe09 |
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
/.idea
|
||||
/.settings
|
||||
/.project
|
||||
/.cproject
|
||||
/.vscode
|
15
AUTHORS
Normal file
15
AUTHORS
Normal file
|
@ -0,0 +1,15 @@
|
|||
Project author:
|
||||
|
||||
Roman Arutyunyan
|
||||
Moscow, Russia
|
||||
|
||||
Contacts:
|
||||
arut@qip.ru
|
||||
arutyunyan.roman@gmail.com
|
||||
|
||||
Fork author:
|
||||
Sergey Dryanzhinsky
|
||||
Moscow, Russia
|
||||
|
||||
Contacts:
|
||||
sergey.dryabzhinsky@gmail.com
|
22
LICENSE
Normal file
22
LICENSE
Normal file
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) 2012-2014, Roman Arutyunyan
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
336
README.md
Normal file
336
README.md
Normal file
|
@ -0,0 +1,336 @@
|
|||
# NGINX-based Media Streaming Server
|
||||
|
||||
## nginx-rtmp-module
|
||||
|
||||
### Project blog
|
||||
|
||||
http://nginx-rtmp.blogspot.com
|
||||
|
||||
### Documentation
|
||||
|
||||
* [Home](doc/README.md)
|
||||
* [Control module](doc/control_modul.md)
|
||||
* [Debug log](doc/debug_log.md)
|
||||
* [Directives](doc/directives.md)
|
||||
* [Examples](doc/examples.md)
|
||||
* [Exec wrapper in bash](doc/exec_wrapper_in_bash.md)
|
||||
* [FAQ](doc/faq.md)
|
||||
* [Getting number of subscribers](doc/getting_number_of_subscribers.md)
|
||||
* [Getting started with nginx rtmp](doc/getting_started.md)
|
||||
* [Installing in Gentoo](doc/installing_in_gentoo.md)
|
||||
* [Installing on Ubuntu using PPAs](doc/installing_ubuntu_using_ppas.md)
|
||||
* [Tutorial](doc/tutorial.md)
|
||||
|
||||
*Source: https://github.com/arut/nginx-rtmp-module/wiki*
|
||||
|
||||
* [Latest updates](doc/README.md#updates)
|
||||
|
||||
### Google group
|
||||
|
||||
https://groups.google.com/group/nginx-rtmp
|
||||
|
||||
https://groups.google.com/group/nginx-rtmp-ru (Russian)
|
||||
|
||||
### Donation page (Paypal etc)
|
||||
|
||||
http://arut.github.com/nginx-rtmp-module/
|
||||
|
||||
### Features
|
||||
|
||||
* RTMP/HLS/MPEG-DASH live streaming
|
||||
|
||||
* RTMP Video on demand FLV/MP4,
|
||||
playing from local filesystem or HTTP
|
||||
|
||||
* Stream relay support for distributed
|
||||
streaming: push & pull models
|
||||
|
||||
* Recording streams in multiple FLVs
|
||||
|
||||
* H264/AAC support
|
||||
|
||||
* Online transcoding with FFmpeg
|
||||
|
||||
* HTTP callbacks (publish/play/record/update etc)
|
||||
|
||||
* Running external programs on certain events (exec)
|
||||
|
||||
* HTTP control module for recording audio/video and dropping clients
|
||||
|
||||
* Advanced buffering techniques
|
||||
to keep memory allocations at a minimum
|
||||
level for faster streaming and low
|
||||
memory footprint
|
||||
|
||||
* Proved to work with Wirecast, FMS, Wowza,
|
||||
JWPlayer, FlowPlayer, StrobeMediaPlayback,
|
||||
ffmpeg, avconv, rtmpdump, flvstreamer
|
||||
and many more
|
||||
|
||||
* Statistics in XML/XSL in machine- & human-
|
||||
readable form
|
||||
|
||||
* Linux/FreeBSD/MacOS/Windows
|
||||
|
||||
### Build
|
||||
|
||||
cd to NGINX source directory & run this:
|
||||
|
||||
./configure --add-module=/path/to/nginx-rtmp-module
|
||||
make
|
||||
make install
|
||||
|
||||
Several versions of nginx (1.3.14 - 1.5.0) require http_ssl_module to be
|
||||
added as well:
|
||||
|
||||
./configure --add-module=/path/to/nginx-rtmp-module --with-http_ssl_module
|
||||
|
||||
For building debug version of nginx add `--with-debug`
|
||||
|
||||
./configure --add-module=/path/to-nginx/rtmp-module --with-debug
|
||||
|
||||
[Read more about debug log](https://github.com/arut/nginx-rtmp-module/wiki/Debug-log)
|
||||
|
||||
### Contributing and Branch Policy
|
||||
|
||||
The "dev" branch is the one where all contributions will be merged before reaching "master".
|
||||
If you plan to propose a patch, please commit into the "dev" branch or its own feature branch.
|
||||
Direct commit to "master" are not permitted.
|
||||
|
||||
### Windows limitations
|
||||
|
||||
Windows support is limited. These features are not supported
|
||||
|
||||
* execs
|
||||
* static pulls
|
||||
* auto_push
|
||||
|
||||
### RTMP URL format
|
||||
|
||||
rtmp://rtmp.example.com/app[/name]
|
||||
|
||||
app - should match one of application {}
|
||||
blocks in config
|
||||
|
||||
name - interpreted by each application
|
||||
can be empty
|
||||
|
||||
|
||||
### Multi-worker live streaming
|
||||
|
||||
This NGINX-RTMP module does not support multi-worker live
|
||||
streaming. While this feature can be enabled through rtmp_auto_push on|off directive, it is ill advised because it is incompatible with NGINX versions starting 1.7.2 and up, there for it should not be used.
|
||||
|
||||
|
||||
### Example nginx.conf
|
||||
|
||||
rtmp {
|
||||
|
||||
server {
|
||||
|
||||
listen 1935;
|
||||
|
||||
chunk_size 4000;
|
||||
|
||||
# TV mode: one publisher, many subscribers
|
||||
application mytv {
|
||||
|
||||
# enable live streaming
|
||||
live on;
|
||||
|
||||
# record first 1K of stream
|
||||
record all;
|
||||
record_path /tmp/av;
|
||||
record_max_size 1K;
|
||||
|
||||
# append current timestamp to each flv
|
||||
record_unique on;
|
||||
|
||||
# publish only from localhost
|
||||
allow publish 127.0.0.1;
|
||||
deny publish all;
|
||||
|
||||
#allow play all;
|
||||
}
|
||||
|
||||
# Transcoding (ffmpeg needed)
|
||||
application big {
|
||||
live on;
|
||||
|
||||
# On every published stream run this command (ffmpeg)
|
||||
# with substitutions: $app/${app}, $name/${name} for application & stream name.
|
||||
#
|
||||
# This ffmpeg call receives stream from this application &
|
||||
# reduces the resolution down to 32x32. The stream is the published to
|
||||
# 'small' application (see below) under the same name.
|
||||
#
|
||||
# ffmpeg can do anything with the stream like video/audio
|
||||
# transcoding, resizing, altering container/codec params etc
|
||||
#
|
||||
# Multiple exec lines can be specified.
|
||||
|
||||
exec ffmpeg -re -i rtmp://localhost:1935/$app/$name -vcodec flv -acodec copy -s 32x32
|
||||
-f flv rtmp://localhost:1935/small/${name};
|
||||
}
|
||||
|
||||
application small {
|
||||
live on;
|
||||
# Video with reduced resolution comes here from ffmpeg
|
||||
}
|
||||
|
||||
application webcam {
|
||||
live on;
|
||||
|
||||
# Stream from local webcam
|
||||
exec_static ffmpeg -f video4linux2 -i /dev/video0 -c:v libx264 -an
|
||||
-f flv rtmp://localhost:1935/webcam/mystream;
|
||||
}
|
||||
|
||||
application mypush {
|
||||
live on;
|
||||
|
||||
# Every stream published here
|
||||
# is automatically pushed to
|
||||
# these two machines
|
||||
push rtmp1.example.com;
|
||||
push rtmp2.example.com:1934;
|
||||
}
|
||||
|
||||
application mypull {
|
||||
live on;
|
||||
|
||||
# Pull all streams from remote machine
|
||||
# and play locally
|
||||
pull rtmp://rtmp3.example.com pageUrl=www.example.com/index.html;
|
||||
}
|
||||
|
||||
application mystaticpull {
|
||||
live on;
|
||||
|
||||
# Static pull is started at nginx start
|
||||
pull rtmp://rtmp4.example.com pageUrl=www.example.com/index.html name=mystream static;
|
||||
}
|
||||
|
||||
# video on demand
|
||||
application vod {
|
||||
play /var/flvs;
|
||||
}
|
||||
|
||||
application vod2 {
|
||||
play /var/mp4s;
|
||||
}
|
||||
|
||||
# Many publishers, many subscribers
|
||||
# no checks, no recording
|
||||
application videochat {
|
||||
|
||||
live on;
|
||||
|
||||
# The following notifications receive all
|
||||
# the session variables as well as
|
||||
# particular call arguments in HTTP POST
|
||||
# request
|
||||
|
||||
# Make HTTP request & use HTTP retcode
|
||||
# to decide whether to allow publishing
|
||||
# from this connection or not
|
||||
on_publish http://localhost:8080/publish;
|
||||
|
||||
# Same with playing
|
||||
on_play http://localhost:8080/play;
|
||||
|
||||
# Publish/play end (repeats on disconnect)
|
||||
on_done http://localhost:8080/done;
|
||||
|
||||
# All above mentioned notifications receive
|
||||
# standard connect() arguments as well as
|
||||
# play/publish ones. If any arguments are sent
|
||||
# with GET-style syntax to play & publish
|
||||
# these are also included.
|
||||
# Example URL:
|
||||
# rtmp://localhost/myapp/mystream?a=b&c=d
|
||||
|
||||
# record 10 video keyframes (no audio) every 2 minutes
|
||||
record keyframes;
|
||||
record_path /tmp/vc;
|
||||
record_max_frames 10;
|
||||
record_interval 2m;
|
||||
|
||||
# Async notify about an flv recorded
|
||||
on_record_done http://localhost:8080/record_done;
|
||||
|
||||
}
|
||||
|
||||
|
||||
# HLS
|
||||
|
||||
# For HLS to work please create a directory in tmpfs (/tmp/hls here)
|
||||
# for the fragments. The directory contents is served via HTTP (see
|
||||
# http{} section in config)
|
||||
#
|
||||
# Incoming stream must be in H264/AAC. For iPhones use baseline H264
|
||||
# profile (see ffmpeg example).
|
||||
# This example creates RTMP stream from movie ready for HLS:
|
||||
#
|
||||
# ffmpeg -loglevel verbose -re -i movie.avi -vcodec libx264
|
||||
# -vprofile baseline -acodec libmp3lame -ar 44100 -ac 1
|
||||
# -f flv rtmp://localhost:1935/hls/movie
|
||||
#
|
||||
# If you need to transcode live stream use 'exec' feature.
|
||||
#
|
||||
application hls {
|
||||
live on;
|
||||
hls on;
|
||||
hls_path /tmp/hls;
|
||||
}
|
||||
|
||||
# MPEG-DASH is similar to HLS
|
||||
|
||||
application dash {
|
||||
live on;
|
||||
dash on;
|
||||
dash_path /tmp/dash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# HTTP can be used for accessing RTMP stats
|
||||
http {
|
||||
|
||||
server {
|
||||
|
||||
listen 8080;
|
||||
|
||||
# This URL provides RTMP statistics in XML
|
||||
location /stat {
|
||||
rtmp_stat all;
|
||||
|
||||
# Use this stylesheet to view XML as web page
|
||||
# in browser
|
||||
rtmp_stat_stylesheet stat.xsl;
|
||||
}
|
||||
|
||||
location /stat.xsl {
|
||||
# XML stylesheet to view RTMP stats.
|
||||
# Copy stat.xsl wherever you want
|
||||
# and put the full directory path here
|
||||
root /path/to/stat.xsl/;
|
||||
}
|
||||
|
||||
location /hls {
|
||||
# Serve HLS fragments
|
||||
types {
|
||||
application/vnd.apple.mpegurl m3u8;
|
||||
video/mp2t ts;
|
||||
}
|
||||
root /tmp;
|
||||
add_header Cache-Control no-cache;
|
||||
}
|
||||
|
||||
location /dash {
|
||||
# Serve DASH fragments
|
||||
root /tmp;
|
||||
add_header Cache-Control no-cache;
|
||||
}
|
||||
}
|
||||
}
|
25
TODO
25
TODO
|
@ -1,25 +0,0 @@
|
|||
- implement chain-reuse for output
|
||||
|
||||
- move shared bufs-related code to a separate .c
|
||||
|
||||
- get rid of greedy send_chain
|
||||
|
||||
- remove macros hell from ngx_rtmp_send.c
|
||||
|
||||
- packet dropping
|
||||
|
||||
- l <-> cl
|
||||
|
||||
- closing session on send error
|
||||
causes crash because of double-freeing stack
|
||||
|
||||
- add max message size
|
||||
|
||||
- recognize amf-meta
|
||||
|
||||
- shortcuts for big-endian copy
|
||||
|
||||
- implement loc confs (=fms apps)
|
||||
loc options:
|
||||
- broadcast/file
|
||||
- HTTP callbacks on invoke calls
|
120
config
120
config
|
@ -1,16 +1,118 @@
|
|||
ngx_addon_name="ngx_rtmp_module"
|
||||
|
||||
CORE_MODULES="$CORE_MODULES
|
||||
RTMP_CORE_MODULES=" \
|
||||
ngx_rtmp_module \
|
||||
ngx_rtmp_core_module \
|
||||
ngx_rtmp_broadcast_module"
|
||||
|
||||
NGX_ADDON_SRCS="$NGX_ADDON_SRCS \
|
||||
ngx_rtmp_cmd_module \
|
||||
ngx_rtmp_codec_module \
|
||||
ngx_rtmp_access_module \
|
||||
ngx_rtmp_record_module \
|
||||
ngx_rtmp_live_module \
|
||||
ngx_rtmp_play_module \
|
||||
ngx_rtmp_flv_module \
|
||||
ngx_rtmp_mp4_module \
|
||||
ngx_rtmp_netcall_module \
|
||||
ngx_rtmp_relay_module \
|
||||
ngx_rtmp_exec_module \
|
||||
ngx_rtmp_auto_push_module \
|
||||
ngx_rtmp_auto_push_index_module \
|
||||
ngx_rtmp_log_module \
|
||||
ngx_rtmp_limit_module \
|
||||
ngx_rtmp_hls_module \
|
||||
ngx_rtmp_dash_module \
|
||||
ngx_rtmp_notify_module \
|
||||
"
|
||||
RTMP_HTTP_MODULES=" \
|
||||
ngx_rtmp_stat_module \
|
||||
ngx_rtmp_control_module \
|
||||
"
|
||||
RTMP_DEPS=" \
|
||||
$ngx_addon_dir/ngx_rtmp_amf.h \
|
||||
$ngx_addon_dir/ngx_rtmp_bandwidth.h \
|
||||
$ngx_addon_dir/ngx_rtmp_cmd_module.h \
|
||||
$ngx_addon_dir/ngx_rtmp_codec_module.h \
|
||||
$ngx_addon_dir/ngx_rtmp_eval.h \
|
||||
$ngx_addon_dir/ngx_rtmp.h \
|
||||
$ngx_addon_dir/ngx_rtmp_version.h \
|
||||
$ngx_addon_dir/ngx_rtmp_live_module.h \
|
||||
$ngx_addon_dir/ngx_rtmp_netcall_module.h \
|
||||
$ngx_addon_dir/ngx_rtmp_play_module.h \
|
||||
$ngx_addon_dir/ngx_rtmp_record_module.h \
|
||||
$ngx_addon_dir/ngx_rtmp_relay_module.h \
|
||||
$ngx_addon_dir/ngx_rtmp_streams.h \
|
||||
$ngx_addon_dir/ngx_rtmp_bitop.h \
|
||||
$ngx_addon_dir/ngx_rtmp_proxy_protocol.h \
|
||||
$ngx_addon_dir/hls/ngx_rtmp_mpegts.h \
|
||||
$ngx_addon_dir/dash/ngx_rtmp_mp4.h \
|
||||
"
|
||||
RTMP_CORE_SRCS=" \
|
||||
$ngx_addon_dir/ngx_rtmp.c \
|
||||
$ngx_addon_dir/ngx_rtmp_shared.c \
|
||||
$ngx_addon_dir/ngx_rtmp_init.c \
|
||||
$ngx_addon_dir/ngx_rtmp_handshake.c \
|
||||
$ngx_addon_dir/ngx_rtmp_handler.c \
|
||||
$ngx_addon_dir/ngx_rtmp_core_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_amf0.c \
|
||||
$ngx_addon_dir/ngx_rtmp_amf.c \
|
||||
$ngx_addon_dir/ngx_rtmp_send.c \
|
||||
$ngx_addon_dir/ngx_rtmp_shared.c \
|
||||
$ngx_addon_dir/ngx_rtmp_eval.c \
|
||||
$ngx_addon_dir/ngx_rtmp_receive.c \
|
||||
$ngx_addon_dir/ngx_rtmp_broadcast_module.c"
|
||||
$ngx_addon_dir/ngx_rtmp_core_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_cmd_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_codec_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_access_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_record_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_live_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_play_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_flv_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_mp4_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_netcall_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_relay_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_bandwidth.c \
|
||||
$ngx_addon_dir/ngx_rtmp_exec_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_auto_push_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_log_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_limit_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_bitop.c \
|
||||
$ngx_addon_dir/ngx_rtmp_proxy_protocol.c \
|
||||
$ngx_addon_dir/hls/ngx_rtmp_hls_module.c \
|
||||
$ngx_addon_dir/dash/ngx_rtmp_dash_module.c \
|
||||
$ngx_addon_dir/hls/ngx_rtmp_mpegts.c \
|
||||
$ngx_addon_dir/hls/ngx_rtmp_mpegts_crc.c \
|
||||
$ngx_addon_dir/dash/ngx_rtmp_mp4.c \
|
||||
$ngx_addon_dir/ngx_rtmp_notify_module.c \
|
||||
"
|
||||
RTMP_HTTP_SRCS=" \
|
||||
$ngx_addon_dir/ngx_rtmp_stat_module.c \
|
||||
$ngx_addon_dir/ngx_rtmp_control_module.c \
|
||||
"
|
||||
ngx_module_incs=$ngx_addon_dir
|
||||
ngx_module_deps=$RTMP_DEPS
|
||||
|
||||
if [ "$ngx_module_link" = "" ] ; then
|
||||
# Old nginx version
|
||||
ngx_module_link=NONE
|
||||
|
||||
EVENT_MODULES="$EVENT_MODULES $RTMP_CORE_MODULES"
|
||||
HTTP_MODULES="$HTTP_MODULES $RTMP_HTTP_MODULES"
|
||||
NGX_ADDON_DEPS="$NGX_ADDON_DEPS $RTMP_DEPS"
|
||||
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $RTMP_CORE_SRCS $RTMP_HTTP_SRCS"
|
||||
fi
|
||||
|
||||
if [ $ngx_module_link = DYNAMIC ] ; then
|
||||
ngx_module_name="$RTMP_CORE_MODULES $RTMP_HTTP_MODULES"
|
||||
ngx_module_srcs="$RTMP_CORE_SRCS $RTMP_HTTP_SRCS"
|
||||
. auto/module
|
||||
elif [ $ngx_module_link = ADDON ] ; then
|
||||
ngx_module_type=EVENT
|
||||
ngx_module_name=$RTMP_CORE_MODULES
|
||||
ngx_module_srcs=$RTMP_CORE_SRCS
|
||||
. auto/module
|
||||
ngx_module_type=HTTP
|
||||
ngx_module_name=$RTMP_HTTP_MODULES
|
||||
ngx_module_srcs=$RTMP_HTTP_SRCS
|
||||
. auto/module
|
||||
fi
|
||||
|
||||
USE_OPENSSL=YES
|
||||
|
||||
CFLAGS="$CFLAGS -I$ngx_addon_dir"
|
||||
# Debug build with all warnings as errors
|
||||
# CFLAGS="$CFLAGS -I$ngx_addon_dir -Wall -Wpointer-arith -Wno-unused-parameter -Werror"
|
||||
|
|
1761
dash/ngx_rtmp_dash_module.c
Normal file
1761
dash/ngx_rtmp_dash_module.c
Normal file
File diff suppressed because it is too large
Load diff
1167
dash/ngx_rtmp_mp4.c
Normal file
1167
dash/ngx_rtmp_mp4.c
Normal file
File diff suppressed because it is too large
Load diff
52
dash/ngx_rtmp_mp4.h
Normal file
52
dash/ngx_rtmp_mp4.h
Normal file
|
@ -0,0 +1,52 @@
|
|||
|
||||
|
||||
#ifndef _NGX_RTMP_MP4_H_INCLUDED_
|
||||
#define _NGX_RTMP_MP4_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_rtmp.h>
|
||||
|
||||
|
||||
#define NGX_RTMP_MP4_SAMPLE_SIZE 0x01
|
||||
#define NGX_RTMP_MP4_SAMPLE_DURATION 0x02
|
||||
#define NGX_RTMP_MP4_SAMPLE_DELAY 0x04
|
||||
#define NGX_RTMP_MP4_SAMPLE_KEY 0x08
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint32_t size;
|
||||
uint32_t duration;
|
||||
uint32_t delay;
|
||||
uint32_t timestamp;
|
||||
unsigned key:1;
|
||||
} ngx_rtmp_mp4_sample_t;
|
||||
|
||||
|
||||
typedef enum {
|
||||
NGX_RTMP_MP4_FILETYPE_INIT,
|
||||
NGX_RTMP_MP4_FILETYPE_SEG
|
||||
} ngx_rtmp_mp4_file_type_t;
|
||||
|
||||
|
||||
typedef enum {
|
||||
NGX_RTMP_MP4_VIDEO_TRACK,
|
||||
NGX_RTMP_MP4_AUDIO_TRACK
|
||||
} ngx_rtmp_mp4_track_type_t;
|
||||
|
||||
|
||||
ngx_int_t ngx_rtmp_mp4_write_ftyp(ngx_buf_t *b);
|
||||
ngx_int_t ngx_rtmp_mp4_write_styp(ngx_buf_t *b);
|
||||
ngx_int_t ngx_rtmp_mp4_write_moov(ngx_rtmp_session_t *s, ngx_buf_t *b,
|
||||
ngx_rtmp_mp4_track_type_t ttype);
|
||||
ngx_int_t ngx_rtmp_mp4_write_moof(ngx_buf_t *b, uint32_t earliest_pres_time,
|
||||
uint32_t sample_count, ngx_rtmp_mp4_sample_t *samples,
|
||||
ngx_uint_t sample_mask, uint32_t index);
|
||||
ngx_int_t ngx_rtmp_mp4_write_sidx(ngx_buf_t *b,
|
||||
ngx_uint_t reference_size, uint32_t earliest_pres_time,
|
||||
uint32_t latest_pres_time);
|
||||
ngx_uint_t ngx_rtmp_mp4_write_mdat(ngx_buf_t *b, ngx_uint_t size);
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_MP4_H_INCLUDED_ */
|
29
doc/README.md
Normal file
29
doc/README.md
Normal file
|
@ -0,0 +1,29 @@
|
|||
# Welcome to the nginx-rtmp-module documentation!
|
||||
|
||||
## Pages
|
||||
|
||||
* [Control module](control_modul.md)
|
||||
* [Debug log](debug_log.md)
|
||||
* [Directives](directives.md)
|
||||
* [Examples](examples.md)
|
||||
* [Exec wrapper in bash](exec_wrapper_in_bash.md)
|
||||
* [FAQ](faq.md)
|
||||
* [Getting number of subscribers](getting_number_of_subscribers.md)
|
||||
* [Getting started with nginx rtmp](getting_started.md)
|
||||
* [Installing in Gentoo](Installing_in_gentoo.md)
|
||||
* [Installing on Ubuntu using PPAs](installing_ubuntu_using_ppas.md)
|
||||
* [Tutorial](tutorial.md)
|
||||
|
||||
*Source: https://github.com/arut/nginx-rtmp-module/wiki*
|
||||
|
||||
## Updates
|
||||
|
||||
* Directives
|
||||
* Notify
|
||||
* [on_playlist](directives.md#on_playlist)
|
||||
* [notify_send_redirect](directives.md#notify_send_redirect)
|
||||
* Client Caching
|
||||
* [hls_allow_client_cache](directives.md#hls_allow_client_cache)
|
||||
* Dash MPD generation
|
||||
* [dash_clock_compensation](directives.md#dash_clock_compensation)
|
||||
* [dash_clock_helper_uri](directives.md#dash_clock_helper_uri)
|
94
doc/control_modul.md
Normal file
94
doc/control_modul.md
Normal file
|
@ -0,0 +1,94 @@
|
|||
# Control module
|
||||
|
||||
Control module is HTTP module which makes it possible to control rtmp module from outside using HTTP protocol. Here's an example of how to enable control.
|
||||
```sh
|
||||
http {
|
||||
...
|
||||
server {
|
||||
listen 8080;
|
||||
server_name localhost;
|
||||
....
|
||||
location /control {
|
||||
rtmp_control all;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There are several sub-modules within control module each controlling a different feature.
|
||||
|
||||
# Record
|
||||
This sub-module starts and stops recordings created with _manual_ flag.
|
||||
Syntax:
|
||||
```sh
|
||||
http://server.com/control/record/start|stop?srv=SRV&app=APP&name=NAME&rec=REC
|
||||
```
|
||||
|
||||
* srv=SRV - optional server{} block number within rtmp{} block, default to first server{} block
|
||||
* app=APP - required application name
|
||||
* name=NAME - required stream name
|
||||
* rec=REC - optional recorder name, defaults to root (unnamed) recorder
|
||||
|
||||
Example
|
||||
```sh
|
||||
rtmp {
|
||||
server {
|
||||
listen 1935;
|
||||
application myapp {
|
||||
live on;
|
||||
recorder rec1 {
|
||||
record all manual;
|
||||
record_suffix all.flv;
|
||||
record_path /tmp/rec;
|
||||
record_unique on;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Publish the stream with the following command
|
||||
```sh
|
||||
$ ffmpeg -i http://someserver.com/mychannel.ts -c:v copy -c:a nellymoser -ar 44100 -ac 1 -f flv rtmp://localhost/myapp/mystream
|
||||
```
|
||||
|
||||
Use the following commands to start and stop recording
|
||||
```sh
|
||||
$ curl "http://localhost:8080/control/record/start?app=myapp&name=mystream&rec=rec1"
|
||||
§ curl "http://localhost:8080/control/record/stop?app=myapp&name=mystream&rec=rec1"
|
||||
```
|
||||
|
||||
if the record start/stop request returns nothing sometimes, you should check if you use multi workers. one worker works great.
|
||||
|
||||
# Drop
|
||||
This sub-module provides a simple way to drop client connection.
|
||||
Syntax:
|
||||
```sh
|
||||
http://server.com/control/drop/publisher|subscriber|client?
|
||||
srv=SRV&app=APP&name=NAME&addr=ADDR&clientid=CLIENTID
|
||||
```
|
||||
|
||||
* srv, app, name - the same as above
|
||||
* addr - optional client address (the same as returned by rtmp_stat)
|
||||
* clientid - optional nginx client id (displayed in log and stat)
|
||||
|
||||
The first method ```drop/publisher``` drops publisher connection. The second ```drop/client``` drops every connection matching ```addr``` argument or all clients (including publisher) if ```addr``` is not specified.
|
||||
|
||||
Examples
|
||||
```sh
|
||||
$ curl http://localhost:8080/control/drop/publisher?app=myapp&name=mystream
|
||||
$ curl http://localhost:8080/control/drop/client?app=myapp&name=mystream
|
||||
$ curl http://localhost:8080/control/drop/client?app=myapp&name=mystream&addr=192.168.0.1
|
||||
$ curl http://localhost:8080/control/drop/client?app=myapp&name=mystream&clientid=1
|
||||
```
|
||||
|
||||
# Redirect
|
||||
Redirect play/publish client to a new stream.
|
||||
Syntax:
|
||||
```sh
|
||||
http://server.com/control/redirect/publisher|subscriber|client?
|
||||
srv=SRV&app=APP&name=NAME&addr=ADDR&clientid=CLIENTID&newname=NEWNAME
|
||||
```
|
||||
|
||||
* srv, app, name, addr, clients - the same as above
|
||||
* newname - new stream name to redirect to
|
16
doc/debug_log.md
Normal file
16
doc/debug_log.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Debug log
|
||||
|
||||
In case you need to solve a streaming problem you might need to watch debug log.
|
||||
For that configure nginx with *--with-debug* flag.
|
||||
```sh
|
||||
$ cd nginx-X.Y.Z
|
||||
$ ./configure --add-module=/path/to/nginx-rtmp-module --with-debug ...
|
||||
```
|
||||
|
||||
After compiling set nginx error.log level to *debug* in nginx.conf
|
||||
```sh
|
||||
error_log logs/error.log debug;
|
||||
```
|
||||
|
||||
After that you will have _a lot_ of debug info in error.log. Please grep
|
||||
what your problem relates to (exec, notify etc) and post to [nginx-rtmp google group](https://groups.google.com/group/nginx-rtmp) to help with solving it.
|
1824
doc/directives.md
Normal file
1824
doc/directives.md
Normal file
File diff suppressed because it is too large
Load diff
69
doc/examples.md
Normal file
69
doc/examples.md
Normal file
|
@ -0,0 +1,69 @@
|
|||
# Exampled
|
||||
|
||||
### Simple Video-on-Demand
|
||||
|
||||
```sh
|
||||
rtmp {
|
||||
server {
|
||||
listen 1935;
|
||||
application vod {
|
||||
play /var/flvs;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Simple live broadcast service
|
||||
```sh
|
||||
rtmp {
|
||||
server {
|
||||
listen 1935;
|
||||
application live {
|
||||
live on;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Re-translate remote stream
|
||||
```sh
|
||||
rtmp {
|
||||
server {
|
||||
listen 1935;
|
||||
application tv {
|
||||
live on;
|
||||
pull rtmp://cdn.example.com:443/programs/main pageUrl=http://www.example.com/index.html name=maintv;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Re-translate remote stream with HLS support
|
||||
```sh
|
||||
rtmp {
|
||||
server {
|
||||
listen 1935;
|
||||
application tv {
|
||||
live on;
|
||||
hls on;
|
||||
hls_path /tmp/tv2;
|
||||
hls_fragment 15s;
|
||||
|
||||
pull rtmp://tv2.example.com:443/root/new name=tv2;
|
||||
}
|
||||
}
|
||||
}
|
||||
http {
|
||||
server {
|
||||
listen 80;
|
||||
location /tv2 {
|
||||
alias /tmp/tv2;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Stream your X screen through RTMP
|
||||
```sh
|
||||
$ ffmpeg -f x11grab -follow_mouse centered -r 25 -s cif -i :0.0 -f flv rtmp://localhost/myapp/screen
|
||||
```
|
33
doc/exec_wrapper_in_bash.md
Normal file
33
doc/exec_wrapper_in_bash.md
Normal file
|
@ -0,0 +1,33 @@
|
|||
# Exec wrapper in bash
|
||||
|
||||
You can write exec wrapper in any language. However you should pay attention to termination process. When publisher closes the stream all executed processed get terminated. If you specify wrapper in ```exec``` directive instead of real ffmpeg then you might end up with your ffmpeg still alive and orphaned until it times out reading input data.
|
||||
|
||||
The solution is using signal traps. Here's an example of such wrapper in bash.
|
||||
|
||||
```sh
|
||||
#!/bin/bash
|
||||
|
||||
on_die ()
|
||||
{
|
||||
# kill all children
|
||||
pkill -KILL -P $$
|
||||
}
|
||||
|
||||
trap 'on_die' TERM
|
||||
ffmpeg -i rtmp://localhost/myapp/$1 -c copy -f flv rtmp://localhost/myapp2/$1 &
|
||||
wait
|
||||
```
|
||||
|
||||
The script registers SIGTERM handler which terminates child ffmpeg. Default signal sent by nginx-rtmp is SIGKILL which cannot be caught. For the above script to behave as expected you need to change exec kill signal with ```exec_kill_signal``` directive. It accept numeric or symbolic signal name (for POSIX.1-1990 signals). Here's example application.
|
||||
```sh
|
||||
application myapp {
|
||||
live on;
|
||||
|
||||
exec /var/scripts/exec_wrapper.sh $name;
|
||||
exec_kill_signal term;
|
||||
}
|
||||
|
||||
application myapp2 {
|
||||
live on;
|
||||
}
|
||||
```
|
26
doc/faq.md
Normal file
26
doc/faq.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
# FAQ
|
||||
|
||||
#### RTMP stream is not played normally in IE, stream stops after several seconds.
|
||||
|
||||
Add this directive to fix the problem
|
||||
```sh
|
||||
wait_video on;
|
||||
```
|
||||
|
||||
#### I use `pull` directive to get stream from remote location. That works for RTMP clients but does not work for HLS.
|
||||
|
||||
Currently HLS clients do not trigger any events. You cannot pull or exec when HLS client connects to server. However you can use static directives `exec_static`, `pull ... static` to pull the stream always.
|
||||
|
||||
#### Seek does not work with flv files recorded by the module.
|
||||
|
||||
To make the files seekable add flv metadata with external software like yamdi, flvmeta or ffmpeg.
|
||||
```sh
|
||||
exec_record_done yamdi -i $path -o /var/videos/$basename;
|
||||
```
|
||||
|
||||
#### Published stream is missing from stats page after some time and clients fail to connect
|
||||
|
||||
Check if you use multiple workers in nginx (`worker_processes`). In such case you have to enable:
|
||||
```sh
|
||||
rtmp_auto_push on;
|
||||
```
|
990
doc/flv.html
990
doc/flv.html
|
@ -1,990 +0,0 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<!-- saved from url=(0022)http://osflash.org/flv -->
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
|
||||
<title>
|
||||
flv [Open Source Flash]
|
||||
</title>
|
||||
|
||||
<meta name="generator" content="DokuWiki">
|
||||
<meta name="robots" content="index,follow">
|
||||
<meta name="date" content="2011-12-02T01:16:59-0500">
|
||||
<meta name="keywords" content="flv">
|
||||
<link rel="search" type="application/opensearchdescription+xml" href="http://osflash.org/lib/exe/opensearch.php" title="Open Source Flash">
|
||||
<link rel="start" href="http://osflash.org/">
|
||||
<link rel="contents" href="http://osflash.org/flv?do=index" title="Sitemap">
|
||||
<link rel="alternate" type="application/rss+xml" title="Recent Changes" href="http://osflash.org/feed.php">
|
||||
<link rel="alternate" type="application/rss+xml" title="Current Namespace" href="http://osflash.org/feed.php?mode=list&ns=">
|
||||
<link rel="edit" title="Edit this page" href="http://osflash.org/flv?do=edit">
|
||||
<link rel="alternate" type="text/html" title="Plain HTML" href="http://osflash.org/_export/xhtml/flv">
|
||||
<link rel="alternate" type="text/plain" title="Wiki Markup" href="http://osflash.org/_export/raw/flv">
|
||||
<link rel="canonical" href="./flv_files/flv.html">
|
||||
<link rel="stylesheet" media="screen" type="text/css" href="./flv_files/css.php">
|
||||
<link rel="stylesheet" media="all" type="text/css" href="./flv_files/css(1).php">
|
||||
<link rel="stylesheet" media="print" type="text/css" href="./flv_files/css(2).php">
|
||||
<script type="text/javascript"><!--//--><![CDATA[//><!--
|
||||
var NS='';var JSINFO = {"id":"flv","namespace":""};
|
||||
//--><!]]></script>
|
||||
<script type="text/javascript" charset="utf-8" src="./flv_files/js.php"></script>
|
||||
|
||||
<link rel="shortcut icon" href="http://osflash.org/lib/tpl/default/images/favicon.ico">
|
||||
|
||||
<style type="text/css">.recaptchatable td img{display:block}.recaptchatable .recaptcha_r1_c1{background:url('http://www.google.com/recaptcha/api/img/white/sprite.png') 0 -63px no-repeat;width:318px;height:9px}.recaptchatable .recaptcha_r2_c1{background:url('http://www.google.com/recaptcha/api/img/white/sprite.png') -18px 0 no-repeat;width:9px;height:57px}.recaptchatable .recaptcha_r2_c2{background:url('http://www.google.com/recaptcha/api/img/white/sprite.png') -27px 0 no-repeat;width:9px;height:57px}.recaptchatable .recaptcha_r3_c1{background:url('http://www.google.com/recaptcha/api/img/white/sprite.png') 0 0 no-repeat;width:9px;height:63px}.recaptchatable .recaptcha_r3_c2{background:url('http://www.google.com/recaptcha/api/img/white/sprite.png') -18px -57px no-repeat;width:300px;height:6px}.recaptchatable .recaptcha_r3_c3{background:url('http://www.google.com/recaptcha/api/img/white/sprite.png') -9px 0 no-repeat;width:9px;height:63px}.recaptchatable .recaptcha_r4_c1{background:url('http://www.google.com/recaptcha/api/img/white/sprite.png') -43px 0 no-repeat;width:171px;height:49px}.recaptchatable .recaptcha_r4_c2{background:url('http://www.google.com/recaptcha/api/img/white/sprite.png') -36px 0 no-repeat;width:7px;height:57px}.recaptchatable .recaptcha_r4_c4{background:url('http://www.google.com/recaptcha/api/img/white/sprite.png') -214px 0 no-repeat;width:97px;height:57px}.recaptchatable .recaptcha_r7_c1{background:url('http://www.google.com/recaptcha/api/img/white/sprite.png') -43px -49px no-repeat;width:171px;height:8px}.recaptchatable .recaptcha_r8_c1{background:url('http://www.google.com/recaptcha/api/img/white/sprite.png') -43px -49px no-repeat;width:25px;height:8px}.recaptchatable .recaptcha_image_cell center img{height:57px}.recaptchatable .recaptcha_image_cell center{height:57px}.recaptchatable .recaptcha_image_cell{background-color:white;height:57px}#recaptcha_area,#recaptcha_table{width:318px!important}.recaptchatable,#recaptcha_area tr,#recaptcha_area td,#recaptcha_area th{margin:0!important;border:0!important;padding:0!important;border-collapse:collapse!important;vertical-align:middle!important}.recaptchatable *{margin:0;padding:0;border:0;font-family:helvetica,sans-serif;font-size:8pt;color:black;position:static;top:auto;left:auto;right:auto;bottom:auto;text-align:left!important}.recaptchatable #recaptcha_image{margin:auto}.recaptchatable img{border:0!important;margin:0!important;padding:0!important}.recaptchatable a,.recaptchatable a:hover{outline:none;border:0!important;padding:0!important;text-decoration:none;color:blue;background:none!important;font-weight:normal}.recaptcha_input_area{position:relative!important;width:146px!important;height:45px!important;margin-left:20px!important;margin-right:5px!important;margin-top:4px!important;background:none!important}.recaptchatable label.recaptcha_input_area_text{margin:0!important;padding:0!important;position:static!important;top:auto!important;left:auto!important;right:auto!important;bottom:auto!important;background:none!important;height:auto!important;width:auto!important}.recaptcha_theme_red label.recaptcha_input_area_text,.recaptcha_theme_white label.recaptcha_input_area_text{color:black!important}.recaptcha_theme_blackglass label.recaptcha_input_area_text{color:white!important}.recaptchatable #recaptcha_response_field{width:145px!important;position:absolute!important;bottom:7px!important;padding:0!important;margin:0!important;font-size:10pt}.recaptcha_theme_blackglass #recaptcha_response_field,.recaptcha_theme_white #recaptcha_response_field{border:1px solid gray}.recaptcha_theme_red #recaptcha_response_field{border:1px solid #cca940}.recaptcha_audio_cant_hear_link{font-size:7pt;color:black}.recaptchatable{line-height:1em}#recaptcha_instructions_error{color:red!important}
|
||||
|
||||
.recaptcha_is_showing_audio .recaptcha_only_if_image,.recaptcha_isnot_showing_audio .recaptcha_only_if_audio,.recaptcha_had_incorrect_sol .recaptcha_only_if_no_incorrect_sol,.recaptcha_nothad_incorrect_sol .recaptcha_only_if_incorrect_sol{display:none !important}</style></head>
|
||||
|
||||
<body>
|
||||
<div class="dokuwiki">
|
||||
|
||||
<div class="stylehead">
|
||||
|
||||
<div class="header">
|
||||
<div class="pagename">
|
||||
[[<a href="http://osflash.org/flv?do=backlink" title="Backlinks">flv</a>]]
|
||||
</div>
|
||||
<div class="logo">
|
||||
<a href="http://osflash.org/" name="dokuwiki__top" id="dokuwiki__top" accesskey="h" title="[H]">Open Source Flash</a> </div>
|
||||
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="bar" id="bar__top">
|
||||
<div class="bar-left" id="bar__topleft">
|
||||
<form class="button btn_edit" method="post" action="./flv_files/flv.html"><div class="no"><input type="hidden" name="do" value="edit"><input type="hidden" name="rev" value=""><input type="submit" value="Edit this page" class="button" accesskey="e" title="Edit this page [E]"></div></form> <form class="button btn_revs" method="get" action="./flv_files/flv.html"><div class="no"><input type="hidden" name="do" value="revisions"><input type="submit" value="Old revisions" class="button" accesskey="o" title="Old revisions [O]"></div></form> </div>
|
||||
|
||||
<div class="bar-right" id="bar__topright">
|
||||
<form class="button btn_recent" method="get" action="./flv_files/flv.html"><div class="no"><input type="hidden" name="do" value="recent"><input type="submit" value="Recent changes" class="button" accesskey="r" title="Recent changes [R]"></div></form> <form action="http://osflash.org/" accept-charset="utf-8" class="search" id="dw__search" method="get"><div class="no"><input type="hidden" name="do" value="search"><input type="text" id="qsearch__in" accesskey="f" name="id" class="edit" title="[F]"><input type="submit" value="Search" class="button" title="Search"><div id="qsearch__out" class="ajax_qsearch JSpopup"></div></div></form>
|
||||
</div>
|
||||
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
|
||||
<div class="breadcrumbs">
|
||||
<span class="bchead">Trace:</span> <span class="bcsep">»</span> <span class="curid"><a href="./flv_files/flv.html" class="breadcrumbs" title="flv">flv</a></span> </div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="page">
|
||||
<!-- wikipage start -->
|
||||
<!-- TOC START -->
|
||||
<div class="toc">
|
||||
<div class="tocheader toctoggle" id="toc__header">Table of Contents</div>
|
||||
<div id="toc__inside">
|
||||
|
||||
<ul class="toc">
|
||||
<li class="level1"><div class="li"><span class="li"><a href="http://osflash.org/flv#flash_video_flv" class="toc">Flash Video (FLV)</a></span></div>
|
||||
<ul class="toc">
|
||||
<li class="level2"><div class="li"><span class="li"><a href="http://osflash.org/flv#overview" class="toc">Overview</a></span></div></li>
|
||||
<li class="level2"><div class="li"><span class="li"><a href="http://osflash.org/flv#issues" class="toc">Issues</a></span></div></li>
|
||||
<li class="level2"><div class="li"><span class="li"><a href="http://osflash.org/flv#video" class="toc">Video</a></span></div></li>
|
||||
<li class="level2"><div class="li"><span class="li"><a href="http://osflash.org/flv#audio" class="toc">Audio</a></span></div></li>
|
||||
<li class="level2"><div class="li"><span class="li"><a href="http://osflash.org/flv#image" class="toc">Image</a></span></div></li>
|
||||
<li class="level2"><div class="li"><span class="li"><a href="http://osflash.org/flv#metadata" class="toc">Metadata</a></span></div></li>
|
||||
<li class="level2"><div class="li"><span class="li"><a href="http://osflash.org/flv#flv_format" class="toc">FLV Format</a></span></div></li>
|
||||
<li class="level2"><div class="li"><span class="li"><a href="http://osflash.org/flv#http_streaming" class="toc">HTTP Streaming</a></span></div></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="level1"><div class="li"><span class="li"><a href="http://osflash.org/flv#discussion__section" class="toc">Discussion</a></span></div></li></ul>
|
||||
</div>
|
||||
</div>
|
||||
<!-- TOC END -->
|
||||
|
||||
<h1 class="sectionedit1"><a name="flash_video_flv" id="flash_video_flv">Flash Video (FLV)</a></h1>
|
||||
<div class="level1">
|
||||
|
||||
<p>
|
||||
|
||||
Flash Video is the name of a file format used to deliver video over the Internet using Adobe Flash Player version 6 or newer. Flash Video content may also be embedded within SWF files. Until version 9 update 3 of the Flash Player, Flash Video referred to a proprietary file format, having the extension .FLV but Adobe introduced <a href="http://www.kaourantin.net/2007/10/new-file-extensions-and-mime-types.html" class="urlextern" title="http://www.kaourantin.net/2007/10/new-file-extensions-and-mime-types.html" rel="nofollow">new file extensions and MIME types</a> and suggests to use those instead of the old FLV:
|
||||
|
||||
</p>
|
||||
<div class="table sectionedit2"><table class="inline">
|
||||
<tbody><tr class="row0">
|
||||
<th class="col0 leftalign"> File Extension </th><th class="col1 leftalign"> <a href="http://ftyps.com/" class="urlextern" title="http://ftyps.com/" rel="nofollow">FTYP</a> </th><th class="col2 leftalign"> <a href="http://www.adobe.com/go/tn_19439" class="urlextern" title="http://www.adobe.com/go/tn_19439" rel="nofollow">MIME Type</a> </th><th class="col3 leftalign"> Description </th>
|
||||
</tr>
|
||||
<tr class="row1">
|
||||
<td class="col0 leftalign"> .f4v </td><td class="col1 leftalign"> 'F4V ' </td><td class="col2 leftalign"> video/mp4 </td><td class="col3 leftalign"> Video for Adobe Flash Player </td>
|
||||
</tr>
|
||||
<tr class="row2">
|
||||
<td class="col0 leftalign"> .f4p </td><td class="col1 leftalign"> 'F4P ' </td><td class="col2 leftalign"> video/mp4 </td><td class="col3 leftalign"> Protected Media for Adobe Flash Player </td>
|
||||
</tr>
|
||||
<tr class="row3">
|
||||
<td class="col0 leftalign"> .f4a </td><td class="col1 leftalign"> 'F4A ' </td><td class="col2 leftalign"> video/mp4 </td><td class="col3 leftalign"> Audio for Adobe Flash Player </td>
|
||||
</tr>
|
||||
<tr class="row4">
|
||||
<td class="col0 leftalign"> .f4b </td><td class="col1 leftalign"> 'F4B ' </td><td class="col2 leftalign"> video/mp4 </td><td class="col3 leftalign"> Audio Book for Adobe Flash Player </td>
|
||||
</tr>
|
||||
<tr class="row5">
|
||||
<td class="col0 leftalign"> .flv </td><td class="col1 leftalign"> </td><td class="col2 leftalign"> video/x-flv </td><td class="col3 leftalign"> <a href="http://en.wikipedia.org/wiki/Flash_Video" class="urlextern" title="http://en.wikipedia.org/wiki/Flash_Video" rel="nofollow">Flash Video</a> </td>
|
||||
</tr>
|
||||
</tbody></table></div>
|
||||
|
||||
<p>
|
||||
|
||||
It is possible to place H.264 and AAC streams into the traditional FLV file, but Adobe strongly encourages everyone to embrace the new standard file format. There are functional limits with the FLV structure when streaming H.264 which couldn't be overcome without a redesign of the file format. This is one of the reasons Adobe is moving away from the traditional FLV file structure. Specifically dealing with sequence headers and enders is tricky with FLV streams. Adobe is still working out if it's possible to place On2 VP6 streams into the new file format.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="secedit editbutton_section editbutton_1"><form class="button btn_secedit" method="post" action="./flv_files/flv.html"><div class="no"><input type="hidden" name="do" value="edit"><input type="hidden" name="rev" value="1322806619"><input type="hidden" name="summary" value="[Flash Video (FLV)] "><input type="hidden" name="target" value="section"><input type="hidden" name="range" value="1-1923"><input type="submit" value="Edit" class="button" title="Flash Video (FLV)"></div></form></div>
|
||||
<h2 class="sectionedit3"><a name="overview" id="overview">Overview</a></h2>
|
||||
<div class="level2">
|
||||
<ul>
|
||||
<li class="level1"><div class="li"> File format parser implementing parts of <a href="http://www.iso.org/iso/en/CombinedQueryResult.CombinedQueryResult?queryString=14496-12" class="urlextern" title="http://www.iso.org/iso/en/CombinedQueryResult.CombinedQueryResult?queryString=14496-12" rel="nofollow">ISO 14496-12</a> (very limited sub set of <a href="http://en.wikipedia.org/wiki/MPEG-4" class="urlextern" title="http://en.wikipedia.org/wiki/MPEG-4" rel="nofollow">MPEG-4</a>, 3GP and QuickTime movie support).</div>
|
||||
</li>
|
||||
<li class="level1"><div class="li"> Support for the 3GPP timed text specification <a href="http://www.3gpp.org/ftp/Specs/html-info/26245.htm" class="urlextern" title="http://www.3gpp.org/ftp/Specs/html-info/26245.htm" rel="nofollow">3GPP TS 26.245</a>. Essentially this is a standardized subtitle format within <a href="http://en.wikipedia.org/wiki/3GP" class="urlextern" title="http://en.wikipedia.org/wiki/3GP" rel="nofollow">3GP</a> files. Any number of text tracks are supported and all the information, including esoteric stuff like karaoke meta data is dumped in 'onMetaData' and a new 'onTextData' NetStream callback. Language information in the individual tracks is also reported. That means you can have sub titles in several languages. Check the <a href="http://www.3gpp.org/ftp/Specs/html-info/26245.htm" class="urlextern" title="http://www.3gpp.org/ftp/Specs/html-info/26245.htm" rel="nofollow">3GPP TS 26.245</a> specification to see what information is available. Note that you have to take care of the formatting and placement of the text yourself, the Flash Player will do nothing here. You can use <a href="http://gpac.sourceforge.net/doc_ttxt.php" class="urlextern" title="http://gpac.sourceforge.net/doc_ttxt.php" rel="nofollow">MP4Box</a> to inject text data into existing files.</div>
|
||||
</li>
|
||||
<li class="level1"><div class="li"> Partial parsing support for the <a href="http://atomicparsley.sourceforge.net/mpeg-4files.html" class="urlextern" title="http://atomicparsley.sourceforge.net/mpeg-4files.html" rel="nofollow">'ilst' atom</a> which is the ID3 equivalent iTunes uses to store meta data. This is usually present in iTunes files. It contains ID3 like information and is reported in the onMetaData callback as key/value pairs in a mixed array with the name 'tags'. ID3V2 is not supported right now.</div>
|
||||
</li>
|
||||
<li class="level1"><div class="li"> A software based <a href="http://en.wikipedia.org/wiki/H264" class="urlextern" title="http://en.wikipedia.org/wiki/H264" rel="nofollow">H.264</a> codec with the ability to decode Base, Mainline and High profiles.</div>
|
||||
</li>
|
||||
<li class="level1"><div class="li"> An <a href="http://en.wikipedia.org/wiki/Advanced_Audio_Coding" class="urlextern" title="http://en.wikipedia.org/wiki/Advanced_Audio_Coding" rel="nofollow">AAC</a> decoder supporting AAC Main, AAC LC and SBR (also known as HE-AAC <sup><a href="http://osflash.org/flv#fn__1" name="fnt__1" id="fnt__1" class="fn_top">1)</a></sup>. </div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
<div class="secedit editbutton_section editbutton_3"><form class="button btn_secedit" method="post" action="./flv_files/flv.html"><div class="no"><input type="hidden" name="do" value="edit"><input type="hidden" name="rev" value="1322806619"><input type="hidden" name="summary" value="[Overview] "><input type="hidden" name="target" value="section"><input type="hidden" name="range" value="1924-4103"><input type="submit" value="Edit" class="button" title="Overview"></div></form></div>
|
||||
<h2 class="sectionedit4"><a name="issues" id="issues">Issues</a></h2>
|
||||
<div class="level2">
|
||||
|
||||
<p>
|
||||
Tools to solve FLV-related issues:
|
||||
|
||||
</p>
|
||||
<ul>
|
||||
<li class="level1"><div class="li"> <a href="http://labs.adobe.com/downloads/flvcheck.html" class="urlextern" title="http://labs.adobe.com/downloads/flvcheck.html" rel="nofollow">Utility for checking FLV format</a></div>
|
||||
</li>
|
||||
<li class="level1"><div class="li"> <a href="http://www.buraks.com/flvmdi/" class="urlextern" title="http://www.buraks.com/flvmdi/" rel="nofollow">FLV Metadata Injector</a> - free closed-source tool which can inject metadata information into a FLV file</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
<div class="secedit editbutton_section editbutton_4"><form class="button btn_secedit" method="post" action="./flv_files/flv.html"><div class="no"><input type="hidden" name="do" value="edit"><input type="hidden" name="rev" value="1322806619"><input type="hidden" name="summary" value="[Issues] "><input type="hidden" name="target" value="section"><input type="hidden" name="range" value="4104-4385"><input type="submit" value="Edit" class="button" title="Issues"></div></form></div>
|
||||
<h2 class="sectionedit5"><a name="video" id="video">Video</a></h2>
|
||||
<div class="level2">
|
||||
|
||||
</div>
|
||||
|
||||
<h4><a name="overview1" id="overview1">Overview</a></h4>
|
||||
<div class="level4">
|
||||
|
||||
<p>
|
||||
You load and play .mp4,.m4v,.m4a,.mov and .3gp files using the same NetStream <acronym title="Application Programming Interface">API</acronym> you use to load FLV files. There are a few things to be aware of:
|
||||
</p>
|
||||
<ul>
|
||||
<li class="level1"><div class="li"> Video needs to be in H.264 format only. <acronym title="Motion Picture Experts Group">MPEG</acronym>-4 Part 2 (Xvid, DivX etc.) video is not supported, H.263 video is not supported, Sorenson Video is not supported. A lot of pod casts are still using <acronym title="Motion Picture Experts Group">MPEG</acronym>-4 Part 2 so do not be surprised if you do not see any video. </div>
|
||||
</li>
|
||||
<li class="level1"><div class="li"> the Flash Player is close to 100% compliant to the H.264 standard, all Base, Main, High and High 10 bit streams should play. </div>
|
||||
</li>
|
||||
<li class="level1"><div class="li"> Extended, High 4:2:2 and High 4:4:4 profiles are not officially supported at this time. They might or might not work depending on what features are used. There are no artificial lower limit on B-frames or any problems with B-pyramids like other players do. </div>
|
||||
</li>
|
||||
<li class="level1"><div class="li"> Since these files contain an index unlike old FLV files, the Flash Player provides a list of save seek points, e.g. times you can seek to without having the play head jump around. You'll get this information through the onMetaData callback in an array with the name 'seekpoints'. On the downside, some files are missing this information which also means that these files are not seekable at all! This is very different from the traditional FLV file format which is rather based on the notion of key frames to determine the seek points. </div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<h4><a name="codecs" id="codecs">Codecs</a></h4>
|
||||
<div class="level4">
|
||||
<div class="table sectionedit6"><table class="inline">
|
||||
<tbody><tr class="row0">
|
||||
<th class="col0"> Codec </th><th class="col1"> Introduced in Flash Player version </th><th class="col2"> Introduced in Flash Lite version </th><th class="col3"> Container Formats </th><th class="col4"> <acronym title="International Organization for Standardization">ISO</acronym> Specification </th><th class="col5"> Codec Id </th>
|
||||
</tr>
|
||||
<tr class="row1">
|
||||
<td class="col0"> <a href="http://en.wikipedia.org/wiki/Sorenson_codec" class="urlextern" title="http://en.wikipedia.org/wiki/Sorenson_codec" rel="nofollow"> Sorenson Spark</a> <sup><a href="http://osflash.org/flv#fn__2" name="fnt__2" id="fnt__2" class="fn_top">2)</a></sup> </td><td class="col1"> 6 </td><td class="col2"> 3 </td><td class="col3"> FLV </td><td class="col4"> </td><td class="col5"> 2 </td>
|
||||
</tr>
|
||||
<tr class="row2">
|
||||
<td class="col0"> Macromedia Screen Video <sup><a href="http://osflash.org/flv#fn__3" name="fnt__3" id="fnt__3" class="fn_top">3)</a></sup> </td><td class="col1"> 6 </td><td class="col2"> - </td><td class="col3"> FLV </td><td class="col4"> </td><td class="col5"> 3 </td>
|
||||
</tr>
|
||||
<tr class="row3">
|
||||
<td class="col0"> Macromedia ScreenVideo 2 <sup><a href="http://osflash.org/flv#fn__4" name="fnt__4" id="fnt__4" class="fn_top">4)</a></sup> </td><td class="col1"> 8 </td><td class="col2"> - </td><td class="col3"> FLV </td><td class="col4"> </td><td class="col5"> 6 </td>
|
||||
</tr>
|
||||
<tr class="row4">
|
||||
<td class="col0"> <a href="http://en.wikipedia.org/wiki/VP6" class="urlextern" title="http://en.wikipedia.org/wiki/VP6" rel="nofollow">On2 TrueMotion VP6-E</a> </td><td class="col1"> 8 </td><td class="col2"> 3 </td><td class="col3"> MOV </td><td class="col4"> </td><td class="col5"> 4 </td>
|
||||
</tr>
|
||||
<tr class="row5">
|
||||
<td class="col0"> <a href="http://www.on2.com/index.php?358" class="urlextern" title="http://www.on2.com/index.php?358" rel="nofollow">On2 TrueMotion VP6-S</a> </td><td class="col1"> 9.0.115.0 </td><td class="col2"> - </td><td class="col3"> MP4V, M4V </td><td class="col4"> </td><td class="col5"> 5 </td>
|
||||
</tr>
|
||||
<tr class="row6">
|
||||
<td class="col0"> <a href="http://en.wikipedia.org/wiki/H.264" class="urlextern" title="http://en.wikipedia.org/wiki/H.264" rel="nofollow">H.264</a> (<acronym title="Motion Picture Experts Group">MPEG</acronym>-4 Part 10) </td><td class="col1"> 9.0.115.0 </td><td class="col2"> - </td><td class="col3"> MP4, F4V, 3GP, 3G2 </td><td class="col4"> <a href="http://www.iso.org/iso/en/CombinedQueryResult.CombinedQueryResult?queryString=14496-10" class="urlextern" title="http://www.iso.org/iso/en/CombinedQueryResult.CombinedQueryResult?queryString=14496-10" rel="nofollow">ISO 14496-10</a> </td><td class="col5"> </td>
|
||||
</tr>
|
||||
</tbody></table></div>
|
||||
|
||||
<p>
|
||||
<a href="http://kb.adobe.com/selfservice/viewContent.do?externalId=kb402866&sliceId=1" class="urlextern" title="http://kb.adobe.com/selfservice/viewContent.do?externalId=kb402866&sliceId=1" rel="nofollow">Adobe Tech Note</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="secedit editbutton_section editbutton_5"><form class="button btn_secedit" method="post" action="./flv_files/flv.html"><div class="no"><input type="hidden" name="do" value="edit"><input type="hidden" name="rev" value="1322806619"><input type="hidden" name="summary" value="[Video] "><input type="hidden" name="target" value="section"><input type="hidden" name="range" value="4386-8088"><input type="submit" value="Edit" class="button" title="Video"></div></form></div>
|
||||
<h2 class="sectionedit7"><a name="audio" id="audio">Audio</a></h2>
|
||||
<div class="level2">
|
||||
|
||||
</div>
|
||||
|
||||
<h4><a name="overview2" id="overview2">Overview</a></h4>
|
||||
<div class="level4">
|
||||
<ul>
|
||||
<li class="level1"><div class="li"> Audio can be either <a href="http://en.wikipedia.org/wiki/Advanced_Audio_Coding" class="urlextern" title="http://en.wikipedia.org/wiki/Advanced_Audio_Coding" rel="nofollow">AAC</a> Main, AAC LC or SBR, corresponding to audio object types 0, 1 and 2. </div>
|
||||
</li>
|
||||
<li class="level1"><div class="li"> The '.mp3' sample type for tracks with mp3 audio is also supported. </div>
|
||||
</li>
|
||||
<li class="level1"><div class="li"> MP3inMP4 which intends to do multi-channel mp3 playback within mp4 files is not supported. </div>
|
||||
</li>
|
||||
<li class="level1"><div class="li"> The old QuickTime specific style of embedding AAC and <acronym title="Motion Picture Experts Group Layer 3">MP3</acronym> data is not supported. It is unlikely though that you will run into these kind of files.</div>
|
||||
</li>
|
||||
<li class="level1"><div class="li"> Unencrypted audio book files contain chapter information. This information is exposed in the onMetaData callback as an array of objects with name 'chapters'.</div>
|
||||
</li>
|
||||
<li class="level1"><div class="li"> The Flash Player can play back multi-channel AAC files, though the sound is mixed down to two channels and resampled to 44.1Khz. Multi channel playback is targeted for one of the next major revisions of the Flash Player. This requires complete redesign of the sound engine in the Flash Player which dates from circa 1996 and has not been improved since.</div>
|
||||
</li>
|
||||
<li class="level1"><div class="li"> All sampling rates from 8Khz to 96Khz are supported. A 32 tap <a href="http://en.wikipedia.org/wiki/Kaiser_window" class="urlextern" title="http://en.wikipedia.org/wiki/Kaiser_window" rel="nofollow">Kaiser Bessel</a> based FIR filter which resamples the sound to 44.1Khz, retaining high quality. The most common sample rate combinations have a hard coded number of phases. In case of a 48000 to 44100 Hz conversion the filter has 147 phases f.ex. </div>
|
||||
</li>
|
||||
<li class="level1"><div class="li"> Flash Player Update 3 Beta 2 now can play back any <acronym title="Motion Picture Experts Group Layer 3">MP3</acronym> sampling rate leveraging the same AAC implementation. No more chipmunks. Ever.</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
<h4><a name="codecs1" id="codecs1">Codecs</a></h4>
|
||||
<div class="level4">
|
||||
<div class="table sectionedit8"><table class="inline">
|
||||
<tbody><tr class="row0">
|
||||
<th class="col0 leftalign"> Codec </th><th class="col1"> Introduced in Flash Player version </th><th class="col2"> Container Formats </th><th class="col3"> <acronym title="International Organization for Standardization">ISO</acronym> Specification </th><th class="col4"> Codec Id </th>
|
||||
</tr>
|
||||
<tr class="row1">
|
||||
<td class="col0"> <a href="http://en.wikipedia.org/wiki/MP3" class="urlextern" title="http://en.wikipedia.org/wiki/MP3" rel="nofollow"> MP3</a> </td><td class="col1"> 6 </td><td class="col2"> <acronym title="Motion Picture Experts Group Layer 3">MP3</acronym> </td><td class="col3"> </td><td class="col4"> 2 </td>
|
||||
</tr>
|
||||
<tr class="row2">
|
||||
<td class="col0"> <a href="http://en.wikipedia.org/wiki/Nellymoser" class="urlextern" title="http://en.wikipedia.org/wiki/Nellymoser" rel="nofollow">Nellymoser ASAO Codec</a> (speech compression) audio content </td><td class="col1"> 6 </td><td class="col2"> FLV </td><td class="col3"> </td><td class="col4"> 5, 6 </td>
|
||||
</tr>
|
||||
<tr class="row3">
|
||||
<td class="col0"> Raw <a href="http://en.wikipedia.org/wiki/PCM" class="urlextern" title="http://en.wikipedia.org/wiki/PCM" rel="nofollow">PCM</a> sampled audio content </td><td class="col1"> 6 </td><td class="col2"> WAV </td><td class="col3"> </td><td class="col4"> 0 </td>
|
||||
</tr>
|
||||
<tr class="row4">
|
||||
<td class="col0"> <a href="http://en.wikipedia.org/wiki/ADPCM" class="urlextern" title="http://en.wikipedia.org/wiki/ADPCM" rel="nofollow"> ADPCM</a> (Adaptive Delta Pulse Code Modulation) audio content </td><td class="col1"> 6 </td><td class="col2"> </td><td class="col3"> </td><td class="col4"> 1 </td>
|
||||
</tr>
|
||||
<tr class="row5">
|
||||
<td class="col0"> <a href="http://en.wikipedia.org/wiki/Advanced_Audio_Coding" class="urlextern" title="http://en.wikipedia.org/wiki/Advanced_Audio_Coding" rel="nofollow">AAC</a> (<a href="http://en.wikipedia.org/wiki/HE-AAC" class="urlextern" title="http://en.wikipedia.org/wiki/HE-AAC" rel="nofollow">HE-AAC</a>/AAC <a href="http://en.wikipedia.org/wiki/Spectral_Band_Replication" class="urlextern" title="http://en.wikipedia.org/wiki/Spectral_Band_Replication" rel="nofollow">SBR</a>, AAC Main Profile, and AAC-LC) </td><td class="col1"> 9.0.115.0 </td><td class="col2"> M4A, MP4 </td><td class="col3"> <a href="http://www.iso.org/iso/en/CombinedQueryResult.CombinedQueryResult?queryString=14496-3" class="urlextern" title="http://www.iso.org/iso/en/CombinedQueryResult.CombinedQueryResult?queryString=14496-3" rel="nofollow">ISO 14496-3</a> </td><td class="col4"> </td>
|
||||
</tr>
|
||||
<tr class="row6">
|
||||
<td class="col0"> Speex </td><td class="col1"> 10 </td><td class="col2"> FLV </td><td class="col3"> <a href="http://en.wikipedia.org/wiki/Speex" class="urlextern" title="http://en.wikipedia.org/wiki/Speex" rel="nofollow">Wiki</a> </td><td class="col4"> 11 </td>
|
||||
</tr>
|
||||
</tbody></table></div>
|
||||
|
||||
</div>
|
||||
<div class="secedit editbutton_section editbutton_7"><form class="button btn_secedit" method="post" action="./flv_files/flv.html"><div class="no"><input type="hidden" name="do" value="edit"><input type="hidden" name="rev" value="1322806619"><input type="hidden" name="summary" value="[Audio] "><input type="hidden" name="target" value="section"><input type="hidden" name="range" value="8089-10530"><input type="submit" value="Edit" class="button" title="Audio"></div></form></div>
|
||||
<h2 class="sectionedit9"><a name="image" id="image">Image</a></h2>
|
||||
<div class="level2">
|
||||
<ul>
|
||||
<li class="level1"><div class="li"> Image tracks encoded in <acronym title="Joint Photographics Experts Group">JPEG</acronym>, <acronym title="Graphics Interchange Format">GIF</acronym> and <acronym title="Portable Network Graphics">PNG</acronym> are accessible in AS3 as a byte array through the callback 'onImageData'. You can simply take that byte array and use the Loader class to display the images. Most often these images represent cover artwork for audio files. </div>
|
||||
</li>
|
||||
<li class="level1"><div class="li"> <acronym title="Tagged Image File Format">TIFF</acronym> image tracks are not supported, you might come across files using this. </div>
|
||||
</li>
|
||||
<li class="level1"><div class="li"> Support for the 'covr' meta data stored in iTunes files, accessible as byte arrays.</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
<div class="secedit editbutton_section editbutton_9"><form class="button btn_secedit" method="post" action="./flv_files/flv.html"><div class="no"><input type="hidden" name="do" value="edit"><input type="hidden" name="rev" value="1322806619"><input type="hidden" name="summary" value="[Image] "><input type="hidden" name="target" value="section"><input type="hidden" name="range" value="10531-10990"><input type="submit" value="Edit" class="button" title="Image"></div></form></div>
|
||||
<h2 class="sectionedit10"><a name="metadata" id="metadata">Metadata</a></h2>
|
||||
<div class="level2">
|
||||
<div class="table sectionedit11"><table class="inline">
|
||||
<tbody><tr class="row0">
|
||||
<th class="col0"> Property </th><th class="col1"> Value </th><th class="col2"> Notes </th>
|
||||
</tr>
|
||||
<tr class="row1">
|
||||
<td class="col0"> duration </td><td class="col1 leftalign"> Obvious. </td><td class="col2 leftalign"> Unlike for FLV files this field will always be present. </td>
|
||||
</tr>
|
||||
<tr class="row2">
|
||||
<td class="col0"> videocodecid </td><td class="col1 leftalign"> For H.264 it reports 'avc1'. </td><td class="col2"> </td>
|
||||
</tr>
|
||||
<tr class="row3">
|
||||
<td class="col0"> audiocodecid </td><td class="col1 leftalign"> For AAC it reports 'mp4a', for <acronym title="Motion Picture Experts Group Layer 3">MP3</acronym> it reports '.mp3'. </td><td class="col2"> </td>
|
||||
</tr>
|
||||
<tr class="row4">
|
||||
<td class="col0"> avcprofile </td><td class="col1 leftalign"> 66, 77, 88, 100, 110, 122 or 144 </td><td class="col2 leftalign"> Corresponds to the H.264 profiles </td>
|
||||
</tr>
|
||||
<tr class="row5">
|
||||
<td class="col0"> avclevel </td><td class="col1 leftalign"> A number between 10 and 51. </td><td class="col2 leftalign"> Consult <a href="http://en.wikipedia.org/wiki/H264#Levels" class="urlextern" title="http://en.wikipedia.org/wiki/H264#Levels" rel="nofollow">this</a> list to find out more. </td>
|
||||
</tr>
|
||||
<tr class="row6">
|
||||
<td class="col0"> aottype </td><td class="col1 leftalign"> Either 0, 1 or 2. </td><td class="col2 leftalign"> This corresponds to AAC Main, AAC LC and SBR audio types. </td>
|
||||
</tr>
|
||||
<tr class="row7">
|
||||
<td class="col0"> moovposition </td><td class="col1 leftalign"> int </td><td class="col2 leftalign"> The offset in bytes of the <a href="http://developer.apple.com/documentation/QuickTime/QTFF/qtff.pdf" class="urlextern" title="http://developer.apple.com/documentation/QuickTime/QTFF/qtff.pdf" rel="nofollow">moov atom</a> in a file. </td>
|
||||
</tr>
|
||||
<tr class="row8">
|
||||
<td class="col0"> trackinfo </td><td class="col1 leftalign"> Array </td><td class="col2 leftalign"> An array of objects containing various infomation about all the tracks in a file. </td>
|
||||
</tr>
|
||||
<tr class="row9">
|
||||
<td class="col0"> chapters </td><td class="col1 leftalign"> Array </td><td class="col2 leftalign"> Information about chapters in audiobooks. </td>
|
||||
</tr>
|
||||
<tr class="row10">
|
||||
<td class="col0"> seekpoints </td><td class="col1 leftalign"> Array </td><td class="col2 leftalign"> Times you can directly feed into NetStream.seek(); </td>
|
||||
</tr>
|
||||
<tr class="row11">
|
||||
<td class="col0"> videoframerate </td><td class="col1 leftalign"> int </td><td class="col2 leftalign"> The frame rate of the video if a monotone frame rate is used. Most videos will have a monotone frame rate. </td>
|
||||
</tr>
|
||||
<tr class="row12">
|
||||
<td class="col0"> audiosamplerate </td><td class="col1"> </td><td class="col2 leftalign"> The original sampling rate of the audio track. </td>
|
||||
</tr>
|
||||
<tr class="row13">
|
||||
<td class="col0"> audiochannels </td><td class="col1"> </td><td class="col2 leftalign"> The original number of channels of the audio track. </td>
|
||||
</tr>
|
||||
<tr class="row14">
|
||||
<td class="col0"> tags </td><td class="col1"> </td><td class="col2 leftalign"> ID3 like tag information </td>
|
||||
</tr>
|
||||
</tbody></table></div>
|
||||
|
||||
</div>
|
||||
<div class="secedit editbutton_section editbutton_10"><form class="button btn_secedit" method="post" action="./flv_files/flv.html"><div class="no"><input type="hidden" name="do" value="edit"><input type="hidden" name="rev" value="1322806619"><input type="hidden" name="summary" value="[Metadata] "><input type="hidden" name="target" value="section"><input type="hidden" name="range" value="10991-12281"><input type="submit" value="Edit" class="button" title="Metadata"></div></form></div>
|
||||
<h2 class="sectionedit12"><a name="flv_format" id="flv_format">FLV Format</a></h2>
|
||||
<div class="level2">
|
||||
|
||||
<p>
|
||||
A Flash Video file (.FLV file extension) consists of a short header, and then interleaved audio, video, and metadata packets. The audio and video packets are stored very similarly to those in <a href="http://osflash.org/swf" class="wikilink1" title="swf">SWF</a>, and the metadata packets consist of <a href="http://osflash.org/documentation/amf" class="wikilink1" title="documentation:amf">AMF</a> data.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<h4><a name="flv_header" id="flv_header">FLV Header</a></h4>
|
||||
<div class="level4">
|
||||
<div class="table sectionedit13"><table class="inline">
|
||||
<tbody><tr class="row0">
|
||||
<th class="col0 leftalign"> Field </th><th class="col1 leftalign"> Data Type </th><th class="col2 leftalign"> Example </th><th class="col3 leftalign"> Description </th>
|
||||
</tr>
|
||||
<tr class="row1">
|
||||
<td class="col0 leftalign"> Signature </td><td class="col1 leftalign"> byte[3] </td><td class="col2 leftalign"> “FLV” </td><td class="col3 leftalign"> Always “FLV” </td>
|
||||
</tr>
|
||||
<tr class="row2">
|
||||
<td class="col0 leftalign"> Version </td><td class="col1 leftalign"> uint8 </td><td class="col2 leftalign"> “\x01” (1) </td><td class="col3 leftalign"> Currently 1 for known FLV files </td>
|
||||
</tr>
|
||||
<tr class="row3">
|
||||
<td class="col0 leftalign"> Flags </td><td class="col1"> uint8 bitmask </td><td class="col2 leftalign"> “\x05” (5, audio+video) </td><td class="col3 leftalign"> Bitmask: 4 is audio, 1 is video </td>
|
||||
</tr>
|
||||
<tr class="row4">
|
||||
<td class="col0 leftalign"> Offset </td><td class="col1 leftalign"> uint32_be </td><td class="col2 leftalign"> “\x00\x00\x00\x09” (9) </td><td class="col3 leftalign"> Total size of header (always 9 for known FLV files) </td>
|
||||
</tr>
|
||||
</tbody></table></div>
|
||||
|
||||
</div>
|
||||
|
||||
<h4><a name="flv_stream" id="flv_stream">FLV Stream</a></h4>
|
||||
<div class="level4">
|
||||
<div class="table sectionedit14"><table class="inline">
|
||||
<tbody><tr class="row0">
|
||||
<th class="col0 leftalign"> Field </th><th class="col1 leftalign"> Data Type </th><th class="col2 leftalign"> Example </th><th class="col3 leftalign"> Description </th>
|
||||
</tr>
|
||||
<tr class="row1">
|
||||
<td class="col0 leftalign"> PreviousTagSize </td><td class="col1 leftalign"> uint32_be </td><td class="col2 leftalign"> “\x00\x00\x00\x00” (0) </td><td class="col3"> Always 0 </td>
|
||||
</tr>
|
||||
</tbody></table></div>
|
||||
|
||||
<p>
|
||||
|
||||
Then a sequence of tags <strong>followed</strong> by their size until <acronym title="End of file">EOF</acronym>.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<h4><a name="flv_tag" id="flv_tag">FLV Tag</a></h4>
|
||||
<div class="level4">
|
||||
<div class="table sectionedit15"><table class="inline">
|
||||
<tbody><tr class="row0">
|
||||
<th class="col0 leftalign"> Field </th><th class="col1 leftalign"> Data Type </th><th class="col2 leftalign"> Example </th><th class="col3 leftalign"> Description </th>
|
||||
</tr>
|
||||
<tr class="row1">
|
||||
<td class="col0 leftalign"> Type </td><td class="col1 leftalign"> uint8 </td><td class="col2 leftalign"> “\x12” (0x12, META) </td><td class="col3 leftalign"> Determines the layout of Body, see below for tag types </td>
|
||||
</tr>
|
||||
<tr class="row2">
|
||||
<td class="col0 leftalign"> BodyLength </td><td class="col1 leftalign"> uint24_be </td><td class="col2 leftalign"> “\x00\x00\xe0” (224) </td><td class="col3 leftalign"> Size of Body (total tag size - 11) </td>
|
||||
</tr>
|
||||
<tr class="row3">
|
||||
<td class="col0 leftalign"> Timestamp </td><td class="col1 leftalign"> uint24_be </td><td class="col2 leftalign"> “\x00\x00\x00” (0) </td><td class="col3 leftalign"> Timestamp of tag (in milliseconds) </td>
|
||||
</tr>
|
||||
<tr class="row4">
|
||||
<td class="col0 leftalign"> TimestampExtended </td><td class="col1 leftalign"> uint8 </td><td class="col2 leftalign"> “\x00” (0) </td><td class="col3 leftalign"> Timestamp extension to form a uint32_be. This field has the upper 8 bits. </td>
|
||||
</tr>
|
||||
<tr class="row5">
|
||||
<td class="col0 leftalign"> StreamId </td><td class="col1 leftalign"> uint24_be </td><td class="col2 leftalign"> “\x00\x00\x00” (0) </td><td class="col3 leftalign"> Always 0 </td>
|
||||
</tr>
|
||||
<tr class="row6">
|
||||
<td class="col0 leftalign"> Body </td><td class="col1 leftalign"> byte[BodyLength] </td><td class="col2 leftalign"> … </td><td class="col3 leftalign"> Dependent on the value of Type </td>
|
||||
</tr>
|
||||
</tbody></table></div>
|
||||
|
||||
</div>
|
||||
|
||||
<h4><a name="previous_tag_size" id="previous_tag_size">Previous tag size</a></h4>
|
||||
<div class="level4">
|
||||
<div class="table sectionedit16"><table class="inline">
|
||||
<tbody><tr class="row0">
|
||||
<th class="col0 leftalign"> Field </th><th class="col1 leftalign"> Data Type </th><th class="col2 leftalign"> Example </th><th class="col3 leftalign"> Description </th>
|
||||
</tr>
|
||||
<tr class="row1">
|
||||
<td class="col0 leftalign"> PreviousTagSize </td><td class="col1 leftalign"> uint32_be </td><td class="col2 leftalign"> “\x00\x00\x00\x00” (0) </td><td class="col3"> Total size of previous tag, or 0 for first tag</td>
|
||||
</tr>
|
||||
</tbody></table></div>
|
||||
|
||||
</div>
|
||||
|
||||
<h4><a name="flv_tag_types" id="flv_tag_types">FLV Tag Types</a></h4>
|
||||
<div class="level4">
|
||||
<div class="table sectionedit17"><table class="inline">
|
||||
<tbody><tr class="row0">
|
||||
<th class="col0 leftalign"> Tag code </th><th class="col1 leftalign"> Name </th><th class="col2 leftalign"> Description </th>
|
||||
</tr>
|
||||
<tr class="row1">
|
||||
<td class="col0 leftalign"> 0x08 </td><td class="col1 leftalign"> AUDIO </td><td class="col2 leftalign"> Contains an audio packet similar to a <a href="http://osflash.org/swf" class="wikilink1" title="swf">SWF</a> SoundStreamBlock plus codec information </td>
|
||||
</tr>
|
||||
<tr class="row2">
|
||||
<td class="col0 leftalign"> 0x09 </td><td class="col1 leftalign"> VIDEO </td><td class="col2 leftalign"> Contains a video packet similar to a <a href="http://osflash.org/swf" class="wikilink1" title="swf">SWF</a> VideoFrame plus codec information </td>
|
||||
</tr>
|
||||
<tr class="row3">
|
||||
<td class="col0 leftalign"> 0x12 </td><td class="col1 leftalign"> META </td><td class="col2 leftalign"> Contains two <a href="http://osflash.org/amf" class="wikilink2" title="amf" rel="nofollow">AMF</a> packets, the name of the event and the data to go with it </td>
|
||||
</tr>
|
||||
</tbody></table></div>
|
||||
|
||||
</div>
|
||||
|
||||
<h4><a name="flv_tag_0x08audio" id="flv_tag_0x08audio">FLV Tag 0x08: AUDIO</a></h4>
|
||||
<div class="level4">
|
||||
|
||||
<p>
|
||||
|
||||
The first byte of an audio packet contains bitflags that
|
||||
describe the codec used, with the following layout:
|
||||
|
||||
</p>
|
||||
<div class="table sectionedit18"><table class="inline">
|
||||
<tbody><tr class="row0">
|
||||
<th class="col0 leftalign"> Name </th><th class="col1 leftalign"> Expression </th><th class="col2 leftalign"> Description </th>
|
||||
</tr>
|
||||
<tr class="row1">
|
||||
<td class="col0 leftalign"> soundType </td><td class="col1 leftalign"> (byte & 0x01) » 0 </td><td class="col2 leftalign"> 0: mono, 1: stereo </td>
|
||||
</tr>
|
||||
<tr class="row2">
|
||||
<td class="col0 leftalign"> soundSize </td><td class="col1 leftalign"> (byte & 0x02) » 1 </td><td class="col2 leftalign"> 0: 8-bit, 1: 16-bit </td>
|
||||
</tr>
|
||||
<tr class="row3">
|
||||
<td class="col0 leftalign"> soundRate </td><td class="col1 leftalign"> (byte & 0x0C) » 2 </td><td class="col2 leftalign"> 0: 5.5 kHz (or speex 16kHz), 1: 11 kHz, 2: 22 kHz, 3: 44 kHz </td>
|
||||
</tr>
|
||||
<tr class="row4">
|
||||
<td class="col0 leftalign"> soundFormat </td><td class="col1 leftalign"> (byte & 0xf0) » 4 </td><td class="col2 leftalign"> 0: Uncompressed, 1: ADPCM, 2: <acronym title="Motion Picture Experts Group Layer 3">MP3</acronym>, 5: Nellymoser 8kHz mono, 6: Nellymoser, 11: Speex </td>
|
||||
</tr>
|
||||
</tbody></table></div>
|
||||
|
||||
<p>
|
||||
|
||||
The rest of the audio packet is simply the relevant data for that format, as per a <a href="http://osflash.org/swf" class="wikilink1" title="swf">SWF</a> SoundStreamBlock.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<h4><a name="flv_tag_0x09video" id="flv_tag_0x09video">FLV Tag 0x09: VIDEO</a></h4>
|
||||
<div class="level4">
|
||||
|
||||
<p>
|
||||
|
||||
The first byte of a video packet describes contains bitflags
|
||||
that describe the codec used, and the type of frame
|
||||
|
||||
</p>
|
||||
<div class="table sectionedit19"><table class="inline">
|
||||
<tbody><tr class="row0">
|
||||
<th class="col0 leftalign"> Name </th><th class="col1 leftalign"> Expression </th><th class="col2 leftalign"> Description </th>
|
||||
</tr>
|
||||
<tr class="row1">
|
||||
<td class="col0 leftalign"> codecID </td><td class="col1 leftalign"> (byte & 0x0f) » 0 </td><td class="col2"> 2: Sorensen H.263, 3: Screen video, 4: On2 VP6, 5: On2 VP6 Alpha, 6: ScreenVideo 2 </td>
|
||||
</tr>
|
||||
<tr class="row2">
|
||||
<td class="col0 leftalign"> frameType </td><td class="col1 leftalign"> (byte & 0xf0) » 4 </td><td class="col2"> 1: keyframe, 2: inter frame, 3: disposable inter frame </td>
|
||||
</tr>
|
||||
</tbody></table></div>
|
||||
|
||||
<p>
|
||||
|
||||
In some cases it is also useful to decode some of the body of the video
|
||||
packet, such as to acquire its resolution (if the initial onMetaData META
|
||||
tag is missing, for example).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
TODO: Describe the techniques for acquiring this information. Until then, you can
|
||||
consult the <a href="http://osflash.org/flashticle" class="wikilink1" title="flashticle">flashticle</a> sources.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<h4><a name="flv_tag_0x12meta" id="flv_tag_0x12meta">FLV Tag 0x12: META</a></h4>
|
||||
<div class="level4">
|
||||
|
||||
<p>
|
||||
|
||||
The contents of a meta packet are two <a href="http://osflash.org/amf" class="wikilink2" title="amf" rel="nofollow">AMF</a> packets. The first is
|
||||
almost always a short uint16_be length-prefixed UTF-8 string
|
||||
(<a href="http://osflash.org/amf" class="wikilink2" title="amf" rel="nofollow">AMF</a> type 0x02), and the second is typically a mixed array
|
||||
(<a href="http://osflash.org/amf" class="wikilink2" title="amf" rel="nofollow">AMF</a> type 0x08). However, the second chunk typically contains
|
||||
a variety of types, so a full <a href="http://osflash.org/amf" class="wikilink2" title="amf" rel="nofollow">AMF</a> parser should be used.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="secedit editbutton_section editbutton_12"><form class="button btn_secedit" method="post" action="./flv_files/flv.html"><div class="no"><input type="hidden" name="do" value="edit"><input type="hidden" name="rev" value="1322806619"><input type="hidden" name="summary" value="[FLV Format] "><input type="hidden" name="target" value="section"><input type="hidden" name="range" value="12282-16912"><input type="submit" value="Edit" class="button" title="FLV Format"></div></form></div>
|
||||
<h2 class="sectionedit20"><a name="http_streaming" id="http_streaming">HTTP Streaming</a></h2>
|
||||
<div class="level2">
|
||||
|
||||
<p>
|
||||
|
||||
It is possible to semi-stream flv over http using a trick which sends the normal headers then skips forward to a desired point in the file and moves the timestamps forward accordingly.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="http://www.flashcomguru.com/index.cfm/2005/11/2/Streaming-flv-video-via-PHP-take-two" class="urlextern" title="http://www.flashcomguru.com/index.cfm/2005/11/2/Streaming-flv-video-via-PHP-take-two" rel="nofollow">A sample php script and fla is available at FlashComGuru</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Another tool that you can use to stream flv files using http is using <a href="http://fanno.dk/index.php?option=com_content&task=blogcategory&id=15&Itemid=53" class="urlextern" title="http://fanno.dk/index.php?option=com_content&task=blogcategory&id=15&Itemid=53" rel="nofollow">Flv4PHP</a> this tool is both a FLV Metadata injector and a stream tool, using php 4.x. this Project is <acronym title="GNU General Public License">GPL</acronym>.
|
||||
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="secedit editbutton_section editbutton_20"><form class="button btn_secedit" method="post" action="./flv_files/flv.html"><div class="no"><input type="hidden" name="do" value="edit"><input type="hidden" name="rev" value="1322806619"><input type="hidden" name="summary" value="[HTTP Streaming] "><input type="hidden" name="target" value="section"><input type="hidden" name="range" value="16913-"><input type="submit" value="Edit" class="button" title="HTTP Streaming"></div></form></div><div class="footnotes">
|
||||
<div class="fn"><sup><a href="http://osflash.org/flv#fnt__1" id="fn__1" name="fn__1" class="fn_bot">1)</a></sup>
|
||||
The support of AAC allows you to encode audio to 64Kbit/s with the same quality of a 128Kbit/s encoded <acronym title="Motion Picture Experts Group Layer 3">MP3</acronym>. Further more, for other use more susceptible to bandwidth usage, like Internet Radio, HE-AAC v2 gives the possibility to encode audio to 32Kbit/s or lower with a surprisingly good final result. In low bitrate streaming scenarios this can make the difference.</div>
|
||||
<div class="fn"><sup><a href="http://osflash.org/flv#fnt__2" id="fn__2" name="fn__2" class="fn_bot">2)</a></sup>
|
||||
Flash documentation does not state a number for “their” version of Sorenson but describes the codec as a variant of <a href="http://www.itu.int/ITU-T/" class="urlextern" title="http://www.itu.int/ITU-T/" rel="nofollow">ITU-T</a> (International Telecommunications Union-Telecommunication Standardization Sector) recommendation <a href="http://en.wikipedia.org/wiki/H.263" class="urlextern" title="http://en.wikipedia.org/wiki/H.263" rel="nofollow">H.263</a> (<a href="http://www.digitalpreservation.gov/formats/fdd/fdd000080.shtml" class="urlextern" title="http://www.digitalpreservation.gov/formats/fdd/fdd000080.shtml" rel="nofollow">MPEG-4_V</a>). In early 2006, one of Sorenson's compression applications to produce content for Flash offered the <a href="http://www.digitalpreservation.gov/formats/fdd/fdd000066.shtml" class="urlextern" title="http://www.digitalpreservation.gov/formats/fdd/fdd000066.shtml" rel="nofollow">Sorenson_3</a> codec, described by experts as a variant of ITU-T H.264 (<a href="http://www.digitalpreservation.gov/formats/fdd/fdd000081.shtml" class="urlextern" title="http://www.digitalpreservation.gov/formats/fdd/fdd000081.shtml" rel="nofollow">MPEG-4_AVC</a>). By late 2006, Sorenson offered new compression applications with other outputs.</div>
|
||||
<div class="fn"><sup><a href="http://osflash.org/flv#fnt__3" id="fn__3" name="fn__3" class="fn_bot">3)</a></sup>
|
||||
This codec divides the screen in wide macroblocks (es: 64×64 pixels), reduces the number of colors, and transmits the changed blocks after compressing them in <a href="http://en.wikipedia.org/wiki/Zlib" class="urlextern" title="http://en.wikipedia.org/wiki/Zlib" rel="nofollow">zlib</a>. This is very similar to what VNC does.formats are bitmap tile based, can be lossy by reducing color depths and are compressed</div>
|
||||
<div class="fn"><sup><a href="http://osflash.org/flv#fnt__4" id="fn__4" name="fn__4" class="fn_bot">4)</a></sup>
|
||||
This codec can use two different types of macroblock: Iblock and Kblock. The Kblock works like a keyframe and is archived for future references. The Iblock is encoded as differences from a previous block. This new approach, similar to the usual compression of generic video content, guarantees a much better compression ratio, especially in a standard “moving windows” scenario.</div>
|
||||
</div>
|
||||
<div class="comment_wrapper">
|
||||
<h2><a name="discussion__section" id="discussion__section">
|
||||
Discussion
|
||||
</a></h2>
|
||||
<div class="level2 hfeed">
|
||||
<div class="hentry">
|
||||
<div class="comment_head">
|
||||
<a name="comment_c8e7e062114b038b3efbe276e9b8b4a8" id="comment_c8e7e062114b038b3efbe276e9b8b4a8"></a>
|
||||
<span class="vcard author"><span class="fn">WattsArlene</span></span>, <abbr class="published" title="2011-01-11T01:54:04Z">2011/01/11 01:54</abbr>
|
||||
</div>
|
||||
<div class="comment_body entry-content">
|
||||
That's good that we can get the <a href="http://bestfinance-blog.com/topics/personal-loans">personal loans</a> moreover, this opens up completely new possibilities.
|
||||
</div>
|
||||
<div class="comment_buttons">
|
||||
<form class="button discussion__reply" method="get" action="http://osflash.org/doku.php#discussion__comment_form">
|
||||
<div class="no">
|
||||
<input type="hidden" name="id" value="flv">
|
||||
<input type="hidden" name="do" value="show">
|
||||
<input type="hidden" name="comment" value="reply">
|
||||
<input type="hidden" name="cid" value="c8e7e062114b038b3efbe276e9b8b4a8">
|
||||
<input type="submit" value="Reply" class="button" title="Reply">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hentry">
|
||||
<div class="comment_head">
|
||||
<a name="comment_d026b76350f7475da83b6992d895400f" id="comment_d026b76350f7475da83b6992d895400f"></a>
|
||||
<span class="vcard author"><span class="fn">koisirinut</span></span>, <abbr class="published" title="2011-01-15T11:03:04Z">2011/01/15 11:03</abbr>
|
||||
</div>
|
||||
<div class="comment_body entry-content">
|
||||
**Bold Text**
|
||||
</div>
|
||||
<div class="comment_buttons">
|
||||
<form class="button discussion__reply" method="get" action="http://osflash.org/doku.php#discussion__comment_form">
|
||||
<div class="no">
|
||||
<input type="hidden" name="id" value="flv">
|
||||
<input type="hidden" name="do" value="show">
|
||||
<input type="hidden" name="comment" value="reply">
|
||||
<input type="hidden" name="cid" value="d026b76350f7475da83b6992d895400f">
|
||||
<input type="submit" value="Reply" class="button" title="Reply">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hentry">
|
||||
<div class="comment_head">
|
||||
<a name="comment_7a598a1b59df777722b3cb3b52553244" id="comment_7a598a1b59df777722b3cb3b52553244"></a>
|
||||
<span class="vcard author"><span class="fn">muhammad basar</span></span>, <abbr class="published" title="2011-01-16T09:35:42Z">2011/01/16 09:35</abbr>
|
||||
</div>
|
||||
<div class="comment_body entry-content">
|
||||
http://bestfinance-blog.com/topics/personal-loans">personal loans</a> moreover, this opens up completely new possibilities.
|
||||
</div>
|
||||
<div class="comment_buttons">
|
||||
<form class="button discussion__reply" method="get" action="http://osflash.org/doku.php#discussion__comment_form">
|
||||
<div class="no">
|
||||
<input type="hidden" name="id" value="flv">
|
||||
<input type="hidden" name="do" value="show">
|
||||
<input type="hidden" name="comment" value="reply">
|
||||
<input type="hidden" name="cid" value="7a598a1b59df777722b3cb3b52553244">
|
||||
<input type="submit" value="Reply" class="button" title="Reply">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hentry">
|
||||
<div class="comment_head">
|
||||
<a name="comment_0b15e51164660e15d87f6a00d65aee7e" id="comment_0b15e51164660e15d87f6a00d65aee7e"></a>
|
||||
<span class="vcard author"><span class="fn">Actarus</span></span>, <abbr class="published" title="2011-01-29T08:24:32Z">2011/01/29 08:24</abbr>
|
||||
</div>
|
||||
<div class="comment_body entry-content">
|
||||
Great documentation! Now, the RIFF format should be documented as well to complete this jewel. I have benchmarked the flv format using the wonderful getID3 library, it's here: http://tinyurl.com/flvdump, Enjoy.
|
||||
</div>
|
||||
<div class="comment_buttons">
|
||||
<form class="button discussion__reply" method="get" action="http://osflash.org/doku.php#discussion__comment_form">
|
||||
<div class="no">
|
||||
<input type="hidden" name="id" value="flv">
|
||||
<input type="hidden" name="do" value="show">
|
||||
<input type="hidden" name="comment" value="reply">
|
||||
<input type="hidden" name="cid" value="0b15e51164660e15d87f6a00d65aee7e">
|
||||
<input type="submit" value="Reply" class="button" title="Reply">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hentry">
|
||||
<div class="comment_head">
|
||||
<a name="comment_4dde8068e4a8eaab434aa572d59a7dc3" id="comment_4dde8068e4a8eaab434aa572d59a7dc3"></a>
|
||||
<span class="vcard author"><span class="fn">khan</span></span>, <abbr class="published" title="2011-02-22T08:40:33Z">2011/02/22 08:40</abbr>
|
||||
</div>
|
||||
<div class="comment_body entry-content">
|
||||
**Bold Text**
|
||||
</div>
|
||||
<div class="comment_buttons">
|
||||
<form class="button discussion__reply" method="get" action="http://osflash.org/doku.php#discussion__comment_form">
|
||||
<div class="no">
|
||||
<input type="hidden" name="id" value="flv">
|
||||
<input type="hidden" name="do" value="show">
|
||||
<input type="hidden" name="comment" value="reply">
|
||||
<input type="hidden" name="cid" value="4dde8068e4a8eaab434aa572d59a7dc3">
|
||||
<input type="submit" value="Reply" class="button" title="Reply">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hentry">
|
||||
<div class="comment_head">
|
||||
<a name="comment_b2176092ebf84736071e7e1cf8ca1757" id="comment_b2176092ebf84736071e7e1cf8ca1757"></a>
|
||||
<span class="vcard author"><span class="fn">mortgage loans</span></span>, <abbr class="published" title="2011-06-02T17:16:08Z">2011/06/02 17:16</abbr>
|
||||
</div>
|
||||
<div class="comment_body entry-content">
|
||||
Some time before, I really needed to buy a good car for my corporation but I did not have enough money and could not buy anything. Thank heaven my colleague suggested to try to take the credit loans at trustworthy creditors. Hence, I acted that and was satisfied with my consolidation loan.
|
||||
</div>
|
||||
<div class="comment_buttons">
|
||||
<form class="button discussion__reply" method="get" action="http://osflash.org/doku.php#discussion__comment_form">
|
||||
<div class="no">
|
||||
<input type="hidden" name="id" value="flv">
|
||||
<input type="hidden" name="do" value="show">
|
||||
<input type="hidden" name="comment" value="reply">
|
||||
<input type="hidden" name="cid" value="b2176092ebf84736071e7e1cf8ca1757">
|
||||
<input type="submit" value="Reply" class="button" title="Reply">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hentry">
|
||||
<div class="comment_head">
|
||||
<a name="comment_ae70f74eec88f9786d641b67e36d2e9c" id="comment_ae70f74eec88f9786d641b67e36d2e9c"></a>
|
||||
<span class="vcard author"><span class="fn">loans</span></span>, <abbr class="published" title="2011-06-29T04:15:09Z">2011/06/29 04:15</abbr>
|
||||
</div>
|
||||
<div class="comment_body entry-content">
|
||||
The loan suppose to be important for people, which want to start their own organization. In fact, it's very comfortable to get a student loan.
|
||||
</div>
|
||||
<div class="comment_buttons">
|
||||
<form class="button discussion__reply" method="get" action="http://osflash.org/doku.php#discussion__comment_form">
|
||||
<div class="no">
|
||||
<input type="hidden" name="id" value="flv">
|
||||
<input type="hidden" name="do" value="show">
|
||||
<input type="hidden" name="comment" value="reply">
|
||||
<input type="hidden" name="cid" value="ae70f74eec88f9786d641b67e36d2e9c">
|
||||
<input type="submit" value="Reply" class="button" title="Reply">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hentry">
|
||||
<div class="comment_head">
|
||||
<a name="comment_82e88e7e53b1128d7dc753bffa698b50" id="comment_82e88e7e53b1128d7dc753bffa698b50"></a>
|
||||
<span class="vcard author"><span class="fn">home loans</span></span>, <abbr class="published" title="2011-07-13T23:30:11Z">2011/07/13 23:30</abbr>
|
||||
</div>
|
||||
<div class="comment_body entry-content">
|
||||
Every one acknowledges that men's life seems to be high priced, nevertheless people require cash for various issues and not every person gets big sums money. Hence to get fast mortgage loans and credit loan would be a correct solution.
|
||||
</div>
|
||||
<div class="comment_buttons">
|
||||
<form class="button discussion__reply" method="get" action="http://osflash.org/doku.php#discussion__comment_form">
|
||||
<div class="no">
|
||||
<input type="hidden" name="id" value="flv">
|
||||
<input type="hidden" name="do" value="show">
|
||||
<input type="hidden" name="comment" value="reply">
|
||||
<input type="hidden" name="cid" value="82e88e7e53b1128d7dc753bffa698b50">
|
||||
<input type="submit" value="Reply" class="button" title="Reply">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hentry">
|
||||
<div class="comment_head">
|
||||
<a name="comment_8ac40edc198d913f7c2ce450ff90f978" id="comment_8ac40edc198d913f7c2ce450ff90f978"></a>
|
||||
<span class="vcard author"><span class="fn">johncein</span></span>, <abbr class="published" title="2011-07-16T02:06:04Z">2011/07/16 02:06</abbr>
|
||||
</div>
|
||||
<div class="comment_body entry-content">
|
||||
informative read... thanks for the source.<br><br><br><br><a href="http://hdwebplayer.com">Flv Player</a>
|
||||
</div>
|
||||
<div class="comment_buttons">
|
||||
<form class="button discussion__reply" method="get" action="http://osflash.org/doku.php#discussion__comment_form">
|
||||
<div class="no">
|
||||
<input type="hidden" name="id" value="flv">
|
||||
<input type="hidden" name="do" value="show">
|
||||
<input type="hidden" name="comment" value="reply">
|
||||
<input type="hidden" name="cid" value="8ac40edc198d913f7c2ce450ff90f978">
|
||||
<input type="submit" value="Reply" class="button" title="Reply">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hentry">
|
||||
<div class="comment_head">
|
||||
<a name="comment_df5e7e76303096d545f8f7dc92c0568e" id="comment_df5e7e76303096d545f8f7dc92c0568e"></a>
|
||||
<span class="vcard author"><span class="fn">valerie</span></span>, <abbr class="published" title="2011-08-27T02:00:07Z">2011/08/27 02:00</abbr>
|
||||
</div>
|
||||
<div class="comment_body entry-content">
|
||||
Eben pagan has released his new course guru blueprint and in this guru masterclass he teaches all that he has learn in his marketing career. <br>Check this website to know more about Eben pagan guru Master class..<a href=>]Eben pagan guru blueprint</a> <br>If you want to know more about the course you can follow the video on youtube about the guru blueprint.. <br><a href=http://www.youtube.com/watch?v=rm8Spt2_Zv0>Eben pagan guru blueprint</a> <br><a href=http://www.youtube.com/watch?v=5MrCM4MnSqM>Eben pagan guru blueprint</a> <br><a href=http://www.youtube.com/watch?v=ZEYhOkb9Wys>Eben pagan guru blueprint</a> <br> <br>you can go here to find out more about the guru masterclass.. <a href=http://www.helpandinfo.com/guru-masterclass-free-training-video-three.html>Eben pagan guru blueprint</a> <br> <br> <br> <br>You can learn much more about marketing by just listening to this guy talk. he is such an amazing personality. See as eben pagan releases his master class.. <br>guru blueprint..
|
||||
</div>
|
||||
<div class="comment_buttons">
|
||||
<form class="button discussion__reply" method="get" action="http://osflash.org/doku.php#discussion__comment_form">
|
||||
<div class="no">
|
||||
<input type="hidden" name="id" value="flv">
|
||||
<input type="hidden" name="do" value="show">
|
||||
<input type="hidden" name="comment" value="reply">
|
||||
<input type="hidden" name="cid" value="df5e7e76303096d545f8f7dc92c0568e">
|
||||
<input type="submit" value="Reply" class="button" title="Reply">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hentry">
|
||||
<div class="comment_head">
|
||||
<a name="comment_3f6f7ace43a8a97e126a4760a70f1d18" id="comment_3f6f7ace43a8a97e126a4760a70f1d18"></a>
|
||||
<span class="vcard author"><span class="fn">loans</span></span>, <abbr class="published" title="2011-09-01T17:28:20Z">2011/09/01 17:28</abbr>
|
||||
</div>
|
||||
<div class="comment_body entry-content">
|
||||
People in the world take the mortgage loans from various creditors, because that is easy.
|
||||
</div>
|
||||
<div class="comment_buttons">
|
||||
<form class="button discussion__reply" method="get" action="http://osflash.org/doku.php#discussion__comment_form">
|
||||
<div class="no">
|
||||
<input type="hidden" name="id" value="flv">
|
||||
<input type="hidden" name="do" value="show">
|
||||
<input type="hidden" name="comment" value="reply">
|
||||
<input type="hidden" name="cid" value="3f6f7ace43a8a97e126a4760a70f1d18">
|
||||
<input type="submit" value="Reply" class="button" title="Reply">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hentry">
|
||||
<div class="comment_head">
|
||||
<a name="comment_4ecb80aacb60a22b14e83c5f40c92eb5" id="comment_4ecb80aacb60a22b14e83c5f40c92eb5"></a>
|
||||
<span class="vcard author"><span class="fn">business loans</span></span>, <abbr class="published" title="2011-09-20T11:06:11Z">2011/09/20 11:06</abbr>
|
||||
</div>
|
||||
<div class="comment_body entry-content">
|
||||
This is known that money can make people independent. But how to act if someone doesn't have cash? The one way only is to get the mortgage loans or just credit loan.
|
||||
</div>
|
||||
<div class="comment_buttons">
|
||||
<form class="button discussion__reply" method="get" action="http://osflash.org/doku.php#discussion__comment_form">
|
||||
<div class="no">
|
||||
<input type="hidden" name="id" value="flv">
|
||||
<input type="hidden" name="do" value="show">
|
||||
<input type="hidden" name="comment" value="reply">
|
||||
<input type="hidden" name="cid" value="4ecb80aacb60a22b14e83c5f40c92eb5">
|
||||
<input type="submit" value="Reply" class="button" title="Reply">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hentry">
|
||||
<div class="comment_head">
|
||||
<a name="comment_63b8e3b6fba6a5e91a7f991aa4b096a4" id="comment_63b8e3b6fba6a5e91a7f991aa4b096a4"></a>
|
||||
<span class="vcard author"><span class="fn">confused</span></span>, <abbr class="published" title="2011-10-13T18:15:45Z">2011/10/13 18:15</abbr>
|
||||
</div>
|
||||
<div class="comment_body entry-content">
|
||||
WHAT THE HELL ARE DUMB ASS BOTS DOING ON HERE!!! NOBODY WANTS YOUR SCAMS OR ANY OTHER HORSE SHIT YOU CAN SHOVEL, GET A FUCKING LIFE.
|
||||
</div>
|
||||
<div class="comment_buttons">
|
||||
<form class="button discussion__reply" method="get" action="http://osflash.org/doku.php#discussion__comment_form">
|
||||
<div class="no">
|
||||
<input type="hidden" name="id" value="flv">
|
||||
<input type="hidden" name="do" value="show">
|
||||
<input type="hidden" name="comment" value="reply">
|
||||
<input type="hidden" name="cid" value="63b8e3b6fba6a5e91a7f991aa4b096a4">
|
||||
<input type="submit" value="Reply" class="button" title="Reply">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hentry">
|
||||
<div class="comment_head">
|
||||
<a name="comment_18022d31dfd0ae557e1f79a33ec4030e" id="comment_18022d31dfd0ae557e1f79a33ec4030e"></a>
|
||||
<span class="vcard author"><span class="fn">gymnsmitift</span></span>, <abbr class="published" title="2011-11-22T18:38:54Z">2011/11/22 18:38</abbr>
|
||||
</div>
|
||||
<div class="comment_body entry-content">
|
||||
wow, great forum! <a href=https://wiki.citizen.apps.gov/NEA_OPI/index.php/About_Antennadeals.com>Antennadeals.com</a>?
|
||||
</div>
|
||||
<div class="comment_buttons">
|
||||
<form class="button discussion__reply" method="get" action="http://osflash.org/doku.php#discussion__comment_form">
|
||||
<div class="no">
|
||||
<input type="hidden" name="id" value="flv">
|
||||
<input type="hidden" name="do" value="show">
|
||||
<input type="hidden" name="comment" value="reply">
|
||||
<input type="hidden" name="cid" value="18022d31dfd0ae557e1f79a33ec4030e">
|
||||
<input type="submit" value="Reply" class="button" title="Reply">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hentry">
|
||||
<div class="comment_head">
|
||||
<a name="comment_3ee3d4d3b9f6057651bb4bed46d83939" id="comment_3ee3d4d3b9f6057651bb4bed46d83939"></a>
|
||||
<span class="vcard author"><span class="fn">Postupalka</span></span>, <abbr class="published" title="2011-12-06T15:38:43Z">2011/12/06 15:38</abbr>
|
||||
</div>
|
||||
<div class="comment_body entry-content">
|
||||
Прокси-сервер работает на 8080 <a href="http://avtomast.com/ceni_na_toplivo-3.html">http://avtomast.com/</a> каневской чат
|
||||
</div>
|
||||
<div class="comment_buttons">
|
||||
<form class="button discussion__reply" method="get" action="http://osflash.org/doku.php#discussion__comment_form">
|
||||
<div class="no">
|
||||
<input type="hidden" name="id" value="flv">
|
||||
<input type="hidden" name="do" value="show">
|
||||
<input type="hidden" name="comment" value="reply">
|
||||
<input type="hidden" name="cid" value="3ee3d4d3b9f6057651bb4bed46d83939">
|
||||
<input type="submit" value="Reply" class="button" title="Reply">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hentry">
|
||||
<div class="comment_head">
|
||||
<a name="comment_25cd8133e922601184904567403e60ed" id="comment_25cd8133e922601184904567403e60ed"></a>
|
||||
<span class="vcard author"><span class="fn">Ali</span></span>, <abbr class="published" title="2012-01-02T07:38:45Z">2012/01/02 07:38</abbr>
|
||||
</div>
|
||||
<div class="comment_body entry-content">
|
||||
**Bold Text**Hello<br>I am working on a project in Flash which the user can draw some animation and then covert the final product as an FLV file so he can share it on facebook and youtube,<br>I have searched for a suitable solution but yet can’t find any, in go Animat (www.goAnimat.com) engine covert to FLV very smoothly,<br>So is it possible to guide me on how to do this, or maybe provide my with this service on servers.<br>I would really appreciate you help on this.
|
||||
</div>
|
||||
<div class="comment_buttons">
|
||||
<form class="button discussion__reply" method="get" action="http://osflash.org/doku.php#discussion__comment_form">
|
||||
<div class="no">
|
||||
<input type="hidden" name="id" value="flv">
|
||||
<input type="hidden" name="do" value="show">
|
||||
<input type="hidden" name="comment" value="reply">
|
||||
<input type="hidden" name="cid" value="25cd8133e922601184904567403e60ed">
|
||||
<input type="submit" value="Reply" class="button" title="Reply">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="comment_form">
|
||||
<form id="discussion__comment_form" method="post" action="http://osflash.org/doku.php" accept-charset="utf-8">
|
||||
<div class="no">
|
||||
<input type="hidden" name="id" value="flv">
|
||||
<input type="hidden" name="do" value="show">
|
||||
<input type="hidden" name="comment" value="add">
|
||||
<input type="hidden" name="reply" value="">
|
||||
<input type="hidden" name="user" value="80.68.252.79,10.31.1.141">
|
||||
<div class="comment_name">
|
||||
<label class="block" for="discussion__comment_name">
|
||||
<span>Real name:</span>
|
||||
<input type="text" class="edit" name="name" id="discussion__comment_name" size="50" tabindex="1" value="">
|
||||
</label>
|
||||
</div>
|
||||
<div class="comment_mail">
|
||||
<label class="block" for="discussion__comment_mail">
|
||||
<span>E-Mail:</span>
|
||||
<input type="text" class="edit" name="mail" id="discussion__comment_mail" size="50" tabindex="2" value="">
|
||||
</label>
|
||||
</div>
|
||||
<div class="comment_text">
|
||||
<div id="discussion__comment_toolbar"><button class="toolbutton" title="Bold Text [B]" accesskey="b"><img src="./flv_files/bold.png"></button><button class="toolbutton" title="Italic Text [I]" accesskey="i"><img src="./flv_files/italic.png"></button><button class="toolbutton" title="Underlined Text [U]" accesskey="u"><img src="./flv_files/underline.png"></button><button class="toolbutton" title="Code Text [C]" accesskey="c"><img src="./flv_files/mono.png"></button><button class="toolbutton" title="Strike-through Text [D]" accesskey="d"><img src="./flv_files/strike.png"></button><button class="toolbutton" title="Same Level Headline [8]" accesskey="8"><img src="./flv_files/hequal.png"></button><button class="toolbutton" title="Lower Headline [9]" accesskey="9"><img src="./flv_files/hminus.png"></button><button class="toolbutton" title="Higher Headline [0]" accesskey="0"><img src="./flv_files/hplus.png"></button><button class="toolbutton pk_hl" title="Select Headline"><img src="./flv_files/h.png"></button></div>
|
||||
<textarea class="edit" name="text" cols="80" rows="10" id="discussion__comment_text" tabindex="5"></textarea>
|
||||
</div>
|
||||
<div style="width: 320px;"></div><script type="text/javascript">
|
||||
var RecaptchaOptions = {theme: 'white',lang: 'en'
|
||||
};
|
||||
</script><script type="text/javascript" src="./flv_files/challenge"></script><script type="text/javascript" src="./flv_files/recaptcha.js"></script><div id="recaptcha_widget_div" style="" class=" recaptcha_nothad_incorrect_sol recaptcha_isnot_showing_audio"><div id="recaptcha_area"><table id="recaptcha_table" class="recaptchatable recaptcha_theme_white"> <tbody><tr> <td colspan="6" class="recaptcha_r1_c1"></td> </tr> <tr> <td class="recaptcha_r2_c1"></td> <td colspan="4" class="recaptcha_image_cell"><div id="recaptcha_image" style="width: 300px; height: 57px; "><img style="display:block;" alt="Проверка по слову reCAPTCHA" height="57" width="300" src="./flv_files/image"></div></td> <td class="recaptcha_r2_c2"></td> </tr> <tr> <td rowspan="6" class="recaptcha_r3_c1"></td> <td colspan="4" class="recaptcha_r3_c2"></td> <td rowspan="6" class="recaptcha_r3_c3"></td> </tr> <tr> <td rowspan="3" class="recaptcha_r4_c1" height="49"> <div class="recaptcha_input_area"> <label for="recaptcha_response_field" class="recaptcha_input_area_text"><span id="recaptcha_instructions_image" class="recaptcha_only_if_image recaptcha_only_if_no_incorrect_sol">Введите то, что видите:</span><span id="recaptcha_instructions_audio" class="recaptcha_only_if_no_incorrect_sol recaptcha_only_if_audio">Введите то, что слышите:</span><span id="recaptcha_instructions_error" class="recaptcha_only_if_incorrect_sol">Неправильно. Повторите попытку.</span></label><br> <span id="recaptcha_challenge_field_holder" style="display: none; "><input type="hidden" name="recaptcha_challenge_field" id="recaptcha_challenge_field" value="03AHJ_Vut1HzJuqKt7EvDt-hY-WMA3VOT3S06e-i7HAnJ_qqXzgUYoH27KMjCNNzRnuntYAjDB1M6qaZ3wQYeumJpisKnJgNZou7KtjPUYjT242OihFfzGNtDszqkcaSVDVmSGR9BROuthp9KbCGj6BCzfSIvrnDs1ow"></span><input name="recaptcha_response_field" id="recaptcha_response_field" type="text" autocorrect="off" autocapitalize="off" autocomplete="off"> </div> </td> <td rowspan="4" class="recaptcha_r4_c2"></td> <td><a id="recaptcha_reload_btn" title="Обновить" href="javascript:Recaptcha.reload();"><img id="recaptcha_reload" width="25" height="17" src="./flv_files/refresh.gif" alt="Обновить"></a></td> <td rowspan="4" class="recaptcha_r4_c4"></td> </tr> <tr> <td><a id="recaptcha_switch_audio_btn" class="recaptcha_only_if_image" title="Звуковая проверка" href="javascript:Recaptcha.switch_type('audio');"><img id="recaptcha_switch_audio" width="25" height="16" alt="Звуковая проверка" src="./flv_files/audio.gif"></a><a id="recaptcha_switch_img_btn" class="recaptcha_only_if_audio" title="Визуальная проверка" href="javascript:Recaptcha.switch_type('image');"><img id="recaptcha_switch_img" width="25" height="16" alt="Визуальная проверка" src="./flv_files/text.gif"></a></td> </tr> <tr> <td><a id="recaptcha_whatsthis_btn" title="Справка" href="http://www.google.com/recaptcha/help?c=03AHJ_Vut1HzJuqKt7EvDt-hY-WMA3VOT3S06e-i7HAnJ_qqXzgUYoH27KMjCNNzRnuntYAjDB1M6qaZ3wQYeumJpisKnJgNZou7KtjPUYjT242OihFfzGNtDszqkcaSVDVmSGR9BROuthp9KbCGj6BCzfSIvrnDs1ow&hl=ru" target="_blank"><img id="recaptcha_whatsthis" width="25" height="16" src="./flv_files/help.gif" alt="Справка"></a></td> </tr> <tr> <td class="recaptcha_r7_c1"></td> <td class="recaptcha_r8_c1"></td> </tr> </tbody></table> </div></div><script>Recaptcha.widget = Recaptcha.$("recaptcha_widget_div"); Recaptcha.challenge_callback();</script>
|
||||
|
||||
<noscript>
|
||||
<iframe src="http://api.recaptcha.net/noscript?k=6LdTL8ASAAAAANsSpVQoKQNotApGWtkt72So2PCS" height="300" width="500" frameborder="0"></iframe><br/>
|
||||
<textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
|
||||
<input type="hidden" name="recaptcha_response_field" value="manual_challenge"/>
|
||||
</noscript> <input class="button comment_submit" id="discussion__btn_submit" type="submit" name="submit" accesskey="s" value="Save" title="Save [S]" tabindex="7">
|
||||
<input class="button comment_preview_button" id="discussion__btn_preview" type="button" name="preview" accesskey="p" value="Preview" title="Preview [P]">
|
||||
|
||||
<div class="comment_subscribe">
|
||||
<input type="checkbox" id="discussion__comment_subscribe" name="subscribe" tabindex="6">
|
||||
<label class="block" for="discussion__comment_subscribe">
|
||||
<span>Subscribe to comments</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="clearer"></div>
|
||||
<div id="discussion__comment_preview"> </div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- wikipage stop -->
|
||||
</div>
|
||||
|
||||
<div class="clearer"> </div>
|
||||
|
||||
|
||||
<div class="stylefoot">
|
||||
|
||||
<div class="meta">
|
||||
<div class="user">
|
||||
</div>
|
||||
<div class="doc">
|
||||
flv.txt · Last modified: 2011/12/02 01:16 by 219.161.44.239 </div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="bar" id="bar__bottom">
|
||||
<div class="bar-left" id="bar__bottomleft">
|
||||
<form class="button btn_edit" method="post" action="./flv_files/flv.html"><div class="no"><input type="hidden" name="do" value="edit"><input type="hidden" name="rev" value=""><input type="submit" value="Edit this page" class="button" accesskey="e" title="Edit this page [E]"></div></form> <form class="button btn_revs" method="get" action="./flv_files/flv.html"><div class="no"><input type="hidden" name="do" value="revisions"><input type="submit" value="Old revisions" class="button" accesskey="o" title="Old revisions [O]"></div></form> </div>
|
||||
<div class="bar-right" id="bar__bottomright">
|
||||
<form class="button btn_login" method="get" action="./flv_files/flv.html"><div class="no"><input type="hidden" name="do" value="login"><input type="hidden" name="sectok" value="a9d499d104bcfce8bd4f08e6bf64edd2"><input type="submit" value="Login" class="button" title="Login"></div></form> <form class="button btn_index" method="get" action="./flv_files/flv.html"><div class="no"><input type="hidden" name="do" value="index"><input type="submit" value="Sitemap" class="button" accesskey="x" title="Sitemap [X]"></div></form> <a class="nolink" href="http://osflash.org/flv#dokuwiki__top"><input type="button" class="button" value="Back to top" onclick="window.scrollTo(0, 0)" title="Back to top"></a>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div><iframe src="about:blank" style="height: 0px; width: 0px; visibility: hidden; border-top-style: none; border-right-style: none; border-bottom-style: none; border-left-style: none; border-width: initial; border-color: initial; border-image: initial; ">This frame prevents back/forward cache problems in Safari.</iframe>
|
||||
|
||||
<div class="footerinc">
|
||||
|
||||
<a href="http://osflash.org/feed.php" title="Recent changes RSS feed"><img src="./flv_files/button-rss.png" width="80" height="15" alt="Recent changes RSS feed"></a>
|
||||
|
||||
|
||||
<a href="http://www.dokuwiki.org/donate" title="Donate"><img src="./flv_files/button-donate.gif" alt="Donate" width="80" height="15"></a>
|
||||
|
||||
<a href="http://www.php.net/" title="Powered by PHP"><img src="./flv_files/button-php.gif" width="80" height="15" alt="Powered by PHP"></a>
|
||||
|
||||
<a href="http://validator.w3.org/check/referer" title="Valid XHTML 1.0"><img src="./flv_files/button-xhtml.png" width="80" height="15" alt="Valid XHTML 1.0"></a>
|
||||
|
||||
<a href="http://jigsaw.w3.org/css-validator/check/referer?profile=css3" title="Valid CSS"><img src="./flv_files/button-css.png" width="80" height="15" alt="Valid CSS"></a>
|
||||
|
||||
<a href="http://dokuwiki.org/" title="Driven by DokuWiki"><img src="./flv_files/button-dw.png" width="80" height="15" alt="Driven by DokuWiki"></a>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="no"><img src="./flv_files/indexer.php" width="1" height="1" alt=""></div>
|
||||
|
||||
|
||||
<div class="picker pk_hl" id="picker0" style="position: absolute; margin-left: -10000px; margin-top: -10000px; "><button class="toolbutton" title="Level 1 Headline [1]" accesskey="1"><img src="./flv_files/h1.png"></button><button class="toolbutton" title="Level 2 Headline [2]" accesskey="2"><img src="./flv_files/h2.png"></button><button class="toolbutton" title="Level 3 Headline [3]" accesskey="3"><img src="./flv_files/h3.png"></button><button class="toolbutton" title="Level 4 Headline [4]" accesskey="4"><img src="./flv_files/h4.png"></button><button class="toolbutton" title="Level 5 Headline [5]" accesskey="5"><img src="./flv_files/h5.png"></button></div></body></html>
|
38
doc/getting_number_of_subscribers.md
Normal file
38
doc/getting_number_of_subscribers.md
Normal file
|
@ -0,0 +1,38 @@
|
|||
# Getting number of subscribers
|
||||
|
||||
There's an easy way to display number of clients watching the stream. You need to
|
||||
|
||||
Set up statistics page at location `/stat`
|
||||
```sh
|
||||
location /stat {
|
||||
rtmp_stat all;
|
||||
allow 127.0.0.1;
|
||||
}
|
||||
```
|
||||
|
||||
Create a simple xsl stylesheet `nclients.xsl` extracting number of stream subscribers
|
||||
```xsl
|
||||
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||
|
||||
<xsl:output method="html"/>
|
||||
|
||||
<xsl:param name="app"/>
|
||||
<xsl:param name="name"/>
|
||||
|
||||
<xsl:template match="/">
|
||||
<xsl:value-of select="count(//application[name=$app]/live/stream[name=$name]/client[not(publishing) and flashver])"/>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
||||
```
|
||||
|
||||
Set up a location returning number of suscribers
|
||||
```sh
|
||||
location /nclients {
|
||||
proxy_pass http://127.0.0.1/stat;
|
||||
xslt_stylesheet /www/nclients.xsl app='$arg_app' name='$arg_name';
|
||||
add_header Refresh "3; $request_uri";
|
||||
}
|
||||
```
|
||||
|
||||
Use HTTP request `http://myserver.com/nclients?app=myapp&name=mystream` to get the number of stream subscribers. This number will be automatically refreshed every 3 seconds when opened in browser or iframe.
|
188
doc/getting_started.md
Normal file
188
doc/getting_started.md
Normal file
|
@ -0,0 +1,188 @@
|
|||
# Getting started with nginx rtmp
|
||||
|
||||
## Download, build and install
|
||||
|
||||
CD to build directory (home)
|
||||
```sh
|
||||
$ cd /usr/build
|
||||
```
|
||||
|
||||
Download & unpack latest nginx-rtmp (you can also use http)
|
||||
```sh
|
||||
$ git clone git://github.com/sergey-dryabzhinsky/nginx-rtmp-module
|
||||
```
|
||||
|
||||
Download & unpack nginx (you can also use svn)
|
||||
|
||||
```sh
|
||||
$ wget http://nginx.org/download/nginx-1.2.4.tar.gz
|
||||
$ tar xzf nginx-1.2.4.tar.gz
|
||||
$ cd nginx-1.2.4
|
||||
```
|
||||
|
||||
Build nginx with nginx-rtmp
|
||||
```sh
|
||||
$ ./configure --add-module=/usr/build/nginx-rtmp-module
|
||||
$ make
|
||||
$ make install
|
||||
```
|
||||
|
||||
For nginx 1.3.4-1.5.0 more options are needed
|
||||
```sh
|
||||
$ ./configure --add-module=/usr/build/nginx-rtmp-module --with-http_ssl_module
|
||||
$ make
|
||||
$ make install
|
||||
```
|
||||
|
||||
## Set up live streaming
|
||||
|
||||
To set up RTMP support you need to add `rtmp{}` section to `nginx.conf` (can be found in PREFIX/conf/nginx.conf). Stock `nginx.conf` contains only `http{}` section.
|
||||
|
||||
Use this `nginx.conf` instead of stock config:
|
||||
```sh
|
||||
#user nobody;
|
||||
worker_processes 1;
|
||||
|
||||
error_log logs/error.log debug;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
|
||||
server {
|
||||
listen 8080;
|
||||
server_name localhost;
|
||||
|
||||
# sample handlers
|
||||
#location /on_play {
|
||||
# if ($arg_pageUrl ~* localhost) {
|
||||
# return 201;
|
||||
# }
|
||||
# return 202;
|
||||
#}
|
||||
#location /on_publish {
|
||||
# return 201;
|
||||
#}
|
||||
|
||||
#location /vod {
|
||||
# alias /var/myvideos;
|
||||
#}
|
||||
|
||||
# rtmp stat
|
||||
location /stat {
|
||||
rtmp_stat all;
|
||||
rtmp_stat_stylesheet stat.xsl;
|
||||
}
|
||||
location /stat.xsl {
|
||||
# you can move stat.xsl to a different location
|
||||
root /usr/build/nginx-rtmp-module;
|
||||
}
|
||||
|
||||
# rtmp control
|
||||
location /control {
|
||||
rtmp_control all;
|
||||
}
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root html;
|
||||
}
|
||||
}
|
||||
}
|
||||
rtmp {
|
||||
server {
|
||||
listen 1935;
|
||||
ping 30s;
|
||||
notify_method get;
|
||||
|
||||
application myapp {
|
||||
live on;
|
||||
|
||||
# sample play/publish handlers
|
||||
#on_play http://localhost:8080/on_play;
|
||||
#on_publish http://localhost:8080/on_publish;
|
||||
|
||||
# sample recorder
|
||||
#recorder rec1 {
|
||||
# record all;
|
||||
# record_interval 30s;
|
||||
# record_path /tmp;
|
||||
# record_unique on;
|
||||
#}
|
||||
|
||||
# sample HLS
|
||||
#hls on;
|
||||
#hls_path /tmp/hls;
|
||||
#hls_sync 100ms;
|
||||
}
|
||||
|
||||
# Video on demand
|
||||
#application vod {
|
||||
# play /var/Videos;
|
||||
#}
|
||||
|
||||
# Video on demand over HTTP
|
||||
#application vod_http {
|
||||
# play http://localhost:8080/vod/;
|
||||
#}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Statistics
|
||||
|
||||
Navigate your browser to `http://localhost:8080/stat` to see current
|
||||
streaming statistics, connected clients, bandwidth etc.
|
||||
|
||||
## Publishing with ffmpeg
|
||||
|
||||
The easiest way to publish live video stream is using ffmpeg (or avconv).
|
||||
It's already installed on most systems and easy to install on others.
|
||||
|
||||
RTMP supports only a limited number of codecs. The most popular RTMP video
|
||||
codecs are H264, Sorenson-H263 (aka flv) and audio codecs AAC, MP3,
|
||||
Nellymoser, Speex. If your video is encoded with these codecs
|
||||
(the most common pair is H264/AAC) then you do not need any conversion.
|
||||
Otherwise you need to convert video to one of supported codecs.
|
||||
|
||||
We'll stream test file `/var/videos/test.mp4` to server with ffmpeg.
|
||||
|
||||
Streaming without conversion (given `test.mp4` codecs are compatible with RTMP)
|
||||
```sh
|
||||
$ ffmpeg -re -i /var/Videos/test.mp4 -c copy -f flv rtmp://localhost/myapp/mystream
|
||||
```
|
||||
|
||||
Streaming and encoding audio (AAC) and video (H264), need `libx264` and `libfaac`
|
||||
```sh
|
||||
$ ffmpeg -re -i /var/Videos/test.mp4 -c:v libx264 -c:a libfaac -ar 44100 -ac 1 -f flv rtmp://localhost/myapp/mystream
|
||||
```
|
||||
|
||||
Streaming and encoding audio (MP3) and video (H264), need `libx264` and `libmp3lame`
|
||||
```sh
|
||||
$ ffmpeg -re -i /var/Videos/test.mp4 -c:v libx264 -c:a libmp3lame -ar 44100 -ac 1 -f flv rtmp://localhost/myapp/mystream
|
||||
```
|
||||
|
||||
Streaming and encoding audio (Nellymoser) and video (Sorenson H263)
|
||||
```sh
|
||||
$ ffmpeg -re -i /var/Videos/test.mp4 -c:v flv -c:a nellymoser -ar 44100 -ac 1 -f flv rtmp://localhost/myapp/mystream
|
||||
```
|
||||
|
||||
## Publishing video from webcam
|
||||
```sh
|
||||
$ ffmpeg -f video4linux2 -i /dev/video0 -c:v libx264 -an -f flv rtmp://localhost/myapp/mystream
|
||||
```
|
||||
|
||||
## Playing with ffplay
|
||||
```sh
|
||||
$ ffplay rtmp://localhost/myapp/mystream
|
||||
```
|
||||
|
||||
## Publishing and playing with flash
|
||||
|
||||
See `test/rtmp-publisher` directory for test flash applets and html.
|
24
doc/installing_in_gentoo.md
Normal file
24
doc/installing_in_gentoo.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Installing in Gentoo
|
||||
|
||||
## Download module source code
|
||||
You have many options:
|
||||
* Get the zip at https://github.com/arut/nginx-rtmp-module/archive/master.zip
|
||||
* Or much better, do a git clone (see options in top of https://github.com/arut/nginx-rtmp-module)
|
||||
* Or get an ebuild from [fem-overlay](http://subversion.fem.tu-ilmenau.de/repository/fem-overlay/trunk/www-servers/nginx/nginx-1.2.5-r1.ebuild). And set the USE flag "nginx_modules_rtmp" or "nginx_modules_rtmp_hls".
|
||||
|
||||
## Emerge nginx with nginx-rtmp-module
|
||||
> NGINX_ADD_MODULES="/path/to/nginx-rtmp-module" emerge -va nginx
|
||||
|
||||
Replace `/path/to/` with the actual module's source path.
|
||||
You can add with this method any number of custom modules.
|
||||
|
||||
To make this change permanent see:
|
||||
http://wiki.gentoo.org/wiki/Knowledge_Base:Overriding_environment_variables_per_package
|
||||
|
||||
## Configure nginx
|
||||
Don't forget to include a rtmp section inside your nginx configuration file located at `/etc/nginx/nginx.conf`.
|
||||
|
||||
See:
|
||||
* [Getting started](getting_started.md) We already have done _Download, build and install_ Gentoo style ;-)
|
||||
* [More Examples](examples.md)
|
||||
* [Reference of all directives](directives.md)
|
30
doc/installing_ubuntu_using_ppas.md
Normal file
30
doc/installing_ubuntu_using_ppas.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Installing on Ubuntu using PPAs
|
||||
```sh
|
||||
$ sudo apt-get install dpkg-dev
|
||||
$ sudo apt-get source nginx
|
||||
$ cd /usr/src/nginx
|
||||
$ sudo git clone https://github.com/arut/nginx-rtmp-module.git
|
||||
$ cd nginx-[version-number]
|
||||
$ sudo vi debian/rules
|
||||
```
|
||||
|
||||
Edit the rules and at then end of the add-modules configuration string add
|
||||
```sh
|
||||
--add-module=/usr/src/nginx/nginx-rtmp-module \
|
||||
```
|
||||
|
||||
If installing for the first time build nginx dependancies.
|
||||
```sh
|
||||
$ sudo apt-get build-dep nginx
|
||||
$ dpkg-buildpackage -b
|
||||
```
|
||||
|
||||
(wait for a while while it builds... a really long while... like you might want to go grab a meal)
|
||||
|
||||
```sh
|
||||
$ cd .. && sudo dpkg --install nginx-common_1.3.13-1chl1~quantal1_all.deb nginx-full_1.3.13-1chl1~quantal1_amd64.deb
|
||||
$ sudo service nginx status
|
||||
$ sudo service nginx start (if nginx isn't running)
|
||||
```
|
||||
|
||||
[Source](http://serverfault.com/questions/227480/installing-optional-nginx-modules-with-apt-get)
|
Binary file not shown.
Binary file not shown.
34248
doc/rtmpout.html
34248
doc/rtmpout.html
File diff suppressed because it is too large
Load diff
114
doc/tutorial.md
Normal file
114
doc/tutorial.md
Normal file
|
@ -0,0 +1,114 @@
|
|||
# Tutorial
|
||||
|
||||
[This article is not finished yet]
|
||||
|
||||
## RTMP
|
||||
RTMP is a proprietary protocol developed by Adobe (Macromedia) for use
|
||||
in flash player. Until 2009 it had no public specification.
|
||||
A number of third-party RTMP-related products started in that period
|
||||
were based on the results of reverse engineering. In 2009
|
||||
[RTMP specification](http://www.adobe.com/devnet/rtmp.html) has been
|
||||
published which made developing such applications easier. However
|
||||
the spec is not full and misses significant issues concerning streaming H264.
|
||||
|
||||
## System requirements
|
||||
The module has been tested on Linux x86-family platforms.
|
||||
However it should work on FreeBSD too.
|
||||
|
||||
## Licence
|
||||
The module is distributed under BSD license.
|
||||
|
||||
## Building NGINX with the module
|
||||
Building is pretty obvious. Just cd to nginx source directory
|
||||
and configure nginx this way:
|
||||
```sh
|
||||
$ ./configure --add-module=/path/to/nginx-rtmp-module
|
||||
```
|
||||
|
||||
Then `make` and `make install`.
|
||||
|
||||
## Configuration
|
||||
|
||||
## Simple live application
|
||||
Simple live application configuration:
|
||||
```sh
|
||||
application live {
|
||||
|
||||
live on;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
You can add access list control:
|
||||
```sh
|
||||
application live {
|
||||
|
||||
live on;
|
||||
|
||||
allow publish 127.0.0.1;
|
||||
deny publish all;
|
||||
allow play all;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
And you can add record support for live streams:
|
||||
```sh
|
||||
application live {
|
||||
|
||||
live on;
|
||||
|
||||
allow publish 127.0.0.1;
|
||||
deny publish all;
|
||||
allow play all;
|
||||
|
||||
record all;
|
||||
record_path /path/to/record/dir;
|
||||
record_max_size 100M;
|
||||
record_unique off;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## HLS (HTTP Live Streaming)
|
||||
|
||||
## Choosing flash player
|
||||
To watch RTMP stream in browser one should either develop
|
||||
flash application for that or use one of available flash
|
||||
players. The most popular players which are proved to have
|
||||
no problems with the module are:
|
||||
|
||||
* [JWPlayer](http://www.longtailvideo.com/)
|
||||
* [FlowPlayer](http://flowplayer.org/)
|
||||
* [Strobe Media Playback](http://www.osmf.org/strobe_mediaplayback.html)
|
||||
* [Clappr](https://github.com/globocom/clappr)
|
||||
|
||||
Old versions of JWPlayer (<=4.4) supported capturing video
|
||||
from webcam. You can find that version in test/ subdirectory.
|
||||
However audio is not captured by this version of player.
|
||||
Recent free versions of JWPlayer have no capture capability at
|
||||
all.
|
||||
|
||||
## Transcoding streams
|
||||
You can use exec directive and ffmpeg for transcoding streams. For example:
|
||||
```sh
|
||||
application big {
|
||||
live on;
|
||||
exec /usr/bin/ffmpeg -re -i rtmp://localhost:1935/$app/$name -vcodec flv -acodec copy -s 32x32 -f flv rtmp://localhost:1935/small/${name};
|
||||
}
|
||||
application small {
|
||||
live on;
|
||||
}
|
||||
```
|
||||
|
||||
## Video on demand
|
||||
|
||||
## Distributed streaming
|
||||
|
||||
## Notifications & access control
|
||||
|
||||
## Statistics
|
||||
|
||||
## Verifying session
|
||||
|
||||
## Utilizing multi-core CPUs
|
2677
hls/ngx_rtmp_hls_module.c
Normal file
2677
hls/ngx_rtmp_hls_module.c
Normal file
File diff suppressed because it is too large
Load diff
486
hls/ngx_rtmp_mpegts.c
Normal file
486
hls/ngx_rtmp_mpegts.c
Normal file
|
@ -0,0 +1,486 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp_mpegts.h"
|
||||
#include "ngx_rtmp_mpegts_crc.h"
|
||||
|
||||
#include "ngx_rtmp_codec_module.h"
|
||||
|
||||
static u_char ngx_rtmp_mpegts_header[] = {
|
||||
|
||||
/* https://en.wikipedia.org/wiki/MPEG_transport_stream#Packet */
|
||||
|
||||
/* TS Header */
|
||||
0x47, // Sync byte
|
||||
0x40, 0x00, // TEI(1) + PUS(1) + TP(1) + PID(13)
|
||||
0x10, // TSC(2) + AFF(1) + PF(1) + CC(4)
|
||||
0x00, // adaption_field_length(8)
|
||||
|
||||
/* PAT */
|
||||
0x00, // table_id(8)
|
||||
0xb0, 0x0d, // 1011b(4) + section_length(12)
|
||||
0x00, 0x01, // transport_stream_id(16)
|
||||
0xc1, 0x00, 0x00, // 11b(2) + VN(5) + CNI(1), section_no(8), last_section_no(8)
|
||||
/* PAT program loop */
|
||||
0x00, 0x01, 0xef, 0xff, // program_number(16), reserved(3) + program_map_pid(13)
|
||||
/* PAT crc (CRC-32-MPEG2) */
|
||||
0x36, 0x90, 0xe2, 0x3d, // !!! Needs to be recalculated each time any bit in PAT is modified (which we dont do at the moment) !!!
|
||||
|
||||
/* stuffing 167 bytes */
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
|
||||
/* TS Header */
|
||||
0x47, // Sync byte
|
||||
0x4f, 0xff, // TEI(1) + PUS(1) + TP(1) + PID(13)
|
||||
0x10, // TSC(2) + AFF(1) + PF(1) + CC(4)
|
||||
0x00, // adaption_field_length(8)
|
||||
|
||||
/* PMT */
|
||||
0x02, // table_id(8)
|
||||
0xb0, 0x12, // 1011b(4) + section_length(12) (section length set below. Ignore this value in here)
|
||||
0x00, 0x01, // program_number(16)
|
||||
0xc1, 0x00, 0x00, // 11b(2) + VN(5) + CNI(1), section_no(8), last_section_no(8)
|
||||
0xe1, 0x00, // reserved(3) + PCR_PID(13)
|
||||
0xf0, 0x00, // reserved(4) + program_info_length(12)
|
||||
|
||||
/* PMT component loop, looped through when writing header */
|
||||
/* Max size of 14 bytes */
|
||||
/* Also includes the PMT CRC, calculated dynamically */
|
||||
0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff,
|
||||
|
||||
/* stuffing 157 bytes */
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
||||
};
|
||||
|
||||
static u_char ngx_rtmp_mpegts_header_h264[] = {
|
||||
//H.264 Video, PID 0x100
|
||||
0x1b, // stream_type(8)
|
||||
0xe1, 0x00, // reserved(3) + elementary_PID(13)
|
||||
0xf0, 0x00 // reserved(4) + ES_info_length(12)
|
||||
};
|
||||
|
||||
static u_char ngx_rtmp_mpegts_header_mp3[] = {
|
||||
//MP3 Audio, PID 0x101
|
||||
0x03, // stream_type(8)
|
||||
0xe1, 0x01, // reserved(3) + elementary_PID(13)
|
||||
0xf0, 0x00 // reserved(4) + ES_info_length(12)
|
||||
};
|
||||
|
||||
static u_char ngx_rtmp_mpegts_header_aac[] = {
|
||||
//ADTS AAC Audio, PID 0x101
|
||||
0x0f, // stream_type(8)
|
||||
0xe1, 0x01, // reserved(3) + elementary_PID(13)
|
||||
0xf0, 0x00 // reserved(4) + ES_info_length(12)
|
||||
};
|
||||
|
||||
#define NGX_RTMP_MPEGTS_PMT_CRC_START_OFFSET 193
|
||||
#define NGX_RTMP_MPEGTS_PMT_CRC_MIN_LENGTH 12
|
||||
#define NGX_RTMP_MPEGTS_PMT_SECTION_LENGTH_OFFSET 195
|
||||
#define NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET 205
|
||||
#define NGX_RTMP_MPEGTS_PID_SIZE 5
|
||||
|
||||
/* 700 ms PCR delay */
|
||||
#define NGX_RTMP_HLS_DELAY 63000
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_mpegts_write_file(ngx_rtmp_mpegts_file_t *file, u_char *in,
|
||||
size_t in_size)
|
||||
{
|
||||
u_char *out;
|
||||
size_t out_size, n;
|
||||
ssize_t rc;
|
||||
|
||||
static u_char buf[1024];
|
||||
|
||||
if (!file->encrypt) {
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,
|
||||
"mpegts: write %uz bytes", in_size);
|
||||
|
||||
rc = ngx_write_fd(file->fd, in, in_size);
|
||||
if (rc < 0) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
/* encrypt */
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,
|
||||
"mpegts: write %uz encrypted bytes", in_size);
|
||||
|
||||
out = buf;
|
||||
out_size = sizeof(buf);
|
||||
|
||||
if (file->size > 0 && file->size + in_size >= 16) {
|
||||
ngx_memcpy(file->buf + file->size, in, 16 - file->size);
|
||||
|
||||
in += 16 - file->size;
|
||||
in_size -= 16 - file->size;
|
||||
|
||||
AES_cbc_encrypt(file->buf, out, 16, &file->key, file->iv, AES_ENCRYPT);
|
||||
|
||||
out += 16;
|
||||
out_size -= 16;
|
||||
|
||||
file->size = 0;
|
||||
}
|
||||
|
||||
for ( ;; ) {
|
||||
n = in_size & ~0x0f;
|
||||
|
||||
if (n > 0) {
|
||||
if (n > out_size) {
|
||||
n = out_size;
|
||||
}
|
||||
|
||||
AES_cbc_encrypt(in, out, n, &file->key, file->iv, AES_ENCRYPT);
|
||||
|
||||
in += n;
|
||||
in_size -= n;
|
||||
|
||||
} else if (out == buf) {
|
||||
break;
|
||||
}
|
||||
|
||||
rc = ngx_write_fd(file->fd, buf, out - buf + n);
|
||||
if (rc < 0) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
out = buf;
|
||||
out_size = sizeof(buf);
|
||||
}
|
||||
|
||||
if (in_size) {
|
||||
ngx_memcpy(file->buf + file->size, in, in_size);
|
||||
file->size += in_size;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_mpegts_write_header(ngx_rtmp_mpegts_file_t *file, ngx_rtmp_codec_ctx_t *codec_ctx, ngx_uint_t mpegts_cc)
|
||||
{
|
||||
ngx_int_t next_pid_offset = 0; //Used to track the number of PIDs we have and the offset in 5-byte chunks
|
||||
|
||||
//MPEG-TS CC is 4 bits long. Need to truncate it here.
|
||||
mpegts_cc %= 0x0f;
|
||||
// And then put it in the headers
|
||||
ngx_rtmp_mpegts_header[3] = (ngx_rtmp_mpegts_header[3] & 0xf0) + (u_char)mpegts_cc;
|
||||
ngx_rtmp_mpegts_header[191] = (ngx_rtmp_mpegts_header[191] & 0xf0) + (u_char)mpegts_cc;
|
||||
|
||||
//ngx_rtmp_mpegts_header
|
||||
|
||||
if (codec_ctx->video_codec_id)
|
||||
{
|
||||
//Put h264 PID in the PMT
|
||||
ngx_memcpy(ngx_rtmp_mpegts_header+NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET+next_pid_offset, ngx_rtmp_mpegts_header_h264, NGX_RTMP_MPEGTS_PID_SIZE);
|
||||
|
||||
next_pid_offset += NGX_RTMP_MPEGTS_PID_SIZE;
|
||||
}
|
||||
|
||||
if (codec_ctx->audio_codec_id){
|
||||
//Put Audio PID in the PMT
|
||||
if (codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC) {
|
||||
ngx_memcpy(ngx_rtmp_mpegts_header+NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET+next_pid_offset, ngx_rtmp_mpegts_header_aac, NGX_RTMP_MPEGTS_PID_SIZE);
|
||||
}
|
||||
else
|
||||
{
|
||||
ngx_memcpy(ngx_rtmp_mpegts_header+NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET+next_pid_offset, ngx_rtmp_mpegts_header_mp3, NGX_RTMP_MPEGTS_PID_SIZE);
|
||||
}
|
||||
next_pid_offset += NGX_RTMP_MPEGTS_PID_SIZE;
|
||||
}
|
||||
|
||||
//Set section length of PMT
|
||||
//PMT is 13 bytes long without any programs in it. Add this in
|
||||
ngx_rtmp_mpegts_header[NGX_RTMP_MPEGTS_PMT_SECTION_LENGTH_OFFSET] = 13 + next_pid_offset;
|
||||
|
||||
//Calculate CRC
|
||||
ngx_rtmp_mpegts_crc_t crc = ngx_rtmp_mpegts_crc_init();
|
||||
crc = ngx_rtmp_mpegts_crc_update(crc, ngx_rtmp_mpegts_header+NGX_RTMP_MPEGTS_PMT_CRC_START_OFFSET, NGX_RTMP_MPEGTS_PMT_CRC_MIN_LENGTH+next_pid_offset);
|
||||
crc = ngx_rtmp_mpegts_crc_finalize(crc);
|
||||
|
||||
ngx_rtmp_mpegts_header[NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET+next_pid_offset] = (crc >> 24) & 0xff;
|
||||
ngx_rtmp_mpegts_header[NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET+next_pid_offset+1] = (crc >> 16) & 0xff;
|
||||
ngx_rtmp_mpegts_header[NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET+next_pid_offset+2] = (crc >> 8) & 0xff;
|
||||
ngx_rtmp_mpegts_header[NGX_RTMP_MPEGTS_PMT_LOOP_OFFSET+next_pid_offset+3] = crc & 0xff;
|
||||
|
||||
return ngx_rtmp_mpegts_write_file(file, ngx_rtmp_mpegts_header, sizeof(ngx_rtmp_mpegts_header));
|
||||
}
|
||||
|
||||
|
||||
static u_char *
|
||||
ngx_rtmp_mpegts_write_pcr(u_char *p, uint64_t pcr)
|
||||
{
|
||||
*p++ = (u_char) (pcr >> 25);
|
||||
*p++ = (u_char) (pcr >> 17);
|
||||
*p++ = (u_char) (pcr >> 9);
|
||||
*p++ = (u_char) (pcr >> 1);
|
||||
*p++ = (u_char) (pcr << 7 | 0x7e);
|
||||
*p++ = 0;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
static u_char *
|
||||
ngx_rtmp_mpegts_write_pts(u_char *p, ngx_uint_t fb, uint64_t pts)
|
||||
{
|
||||
ngx_uint_t val;
|
||||
|
||||
val = fb << 4 | (((pts >> 30) & 0x07) << 1) | 1;
|
||||
*p++ = (u_char) val;
|
||||
|
||||
val = (((pts >> 15) & 0x7fff) << 1) | 1;
|
||||
*p++ = (u_char) (val >> 8);
|
||||
*p++ = (u_char) val;
|
||||
|
||||
val = (((pts) & 0x7fff) << 1) | 1;
|
||||
*p++ = (u_char) (val >> 8);
|
||||
*p++ = (u_char) val;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_mpegts_write_frame(ngx_rtmp_mpegts_file_t *file,
|
||||
ngx_rtmp_mpegts_frame_t *f, ngx_buf_t *b)
|
||||
{
|
||||
ngx_uint_t pes_size, header_size, body_size, in_size, stuff_size, flags;
|
||||
u_char packet[188], *p, *base;
|
||||
ngx_int_t first, rc;
|
||||
|
||||
ngx_log_debug6(NGX_LOG_DEBUG_CORE, file->log, 0,
|
||||
"mpegts: pid=%ui, sid=%ui, pts=%uL, "
|
||||
"dts=%uL, key=%ui, size=%ui",
|
||||
f->pid, f->sid, f->pts, f->dts,
|
||||
(ngx_uint_t) f->key, (size_t) (b->last - b->pos));
|
||||
|
||||
first = 1;
|
||||
|
||||
while (b->pos < b->last) {
|
||||
p = packet;
|
||||
|
||||
f->cc++;
|
||||
|
||||
*p++ = 0x47;
|
||||
*p++ = (u_char) (f->pid >> 8);
|
||||
|
||||
if (first) {
|
||||
p[-1] |= 0x40;
|
||||
}
|
||||
|
||||
*p++ = (u_char) f->pid;
|
||||
*p++ = 0x10 | (f->cc & 0x0f); /* payload */
|
||||
|
||||
if (first) {
|
||||
|
||||
packet[3] |= 0x20; /* adaptation */
|
||||
|
||||
*p++ = 7; /* size */
|
||||
*p++ = 0x50; /* random access + PCR */
|
||||
|
||||
p = ngx_rtmp_mpegts_write_pcr(p, f->dts - NGX_RTMP_HLS_DELAY);
|
||||
|
||||
/* PES header */
|
||||
|
||||
*p++ = 0x00;
|
||||
*p++ = 0x00;
|
||||
*p++ = 0x01;
|
||||
*p++ = (u_char) f->sid;
|
||||
|
||||
header_size = 5;
|
||||
flags = 0x80; /* PTS */
|
||||
|
||||
if (f->dts != f->pts) {
|
||||
header_size += 5;
|
||||
flags |= 0x40; /* DTS */
|
||||
}
|
||||
|
||||
pes_size = (b->last - b->pos) + header_size + 3;
|
||||
if (pes_size > 0xffff) {
|
||||
pes_size = 0;
|
||||
}
|
||||
|
||||
*p++ = (u_char) (pes_size >> 8);
|
||||
*p++ = (u_char) pes_size;
|
||||
*p++ = 0x80; /* H222 */
|
||||
*p++ = (u_char) flags;
|
||||
*p++ = (u_char) header_size;
|
||||
|
||||
p = ngx_rtmp_mpegts_write_pts(p, flags >> 6, f->pts +
|
||||
NGX_RTMP_HLS_DELAY);
|
||||
|
||||
if (f->dts != f->pts) {
|
||||
p = ngx_rtmp_mpegts_write_pts(p, 1, f->dts +
|
||||
NGX_RTMP_HLS_DELAY);
|
||||
}
|
||||
|
||||
first = 0;
|
||||
}
|
||||
|
||||
body_size = (ngx_uint_t) (packet + sizeof(packet) - p);
|
||||
in_size = (ngx_uint_t) (b->last - b->pos);
|
||||
|
||||
if (body_size <= in_size) {
|
||||
ngx_memcpy(p, b->pos, body_size);
|
||||
b->pos += body_size;
|
||||
|
||||
} else {
|
||||
stuff_size = (body_size - in_size);
|
||||
|
||||
if (packet[3] & 0x20) {
|
||||
|
||||
/* has adaptation */
|
||||
|
||||
base = &packet[5] + packet[4];
|
||||
p = ngx_movemem(base + stuff_size, base, p - base);
|
||||
ngx_memset(base, 0xff, stuff_size);
|
||||
packet[4] += (u_char) stuff_size;
|
||||
|
||||
} else {
|
||||
|
||||
/* no adaptation */
|
||||
|
||||
packet[3] |= 0x20;
|
||||
p = ngx_movemem(&packet[4] + stuff_size, &packet[4],
|
||||
p - &packet[4]);
|
||||
|
||||
packet[4] = (u_char) (stuff_size - 1);
|
||||
if (stuff_size >= 2) {
|
||||
packet[5] = 0;
|
||||
ngx_memset(&packet[6], 0xff, stuff_size - 2);
|
||||
}
|
||||
}
|
||||
|
||||
ngx_memcpy(p, b->pos, in_size);
|
||||
b->pos = b->last;
|
||||
}
|
||||
|
||||
rc = ngx_rtmp_mpegts_write_file(file, packet, sizeof(packet));
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_mpegts_init_encryption(ngx_rtmp_mpegts_file_t *file,
|
||||
u_char *key, size_t key_len, uint64_t iv)
|
||||
{
|
||||
if (AES_set_encrypt_key(key, key_len * 8, &file->key)) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_memzero(file->iv, 8);
|
||||
|
||||
file->iv[8] = (u_char) (iv >> 56);
|
||||
file->iv[9] = (u_char) (iv >> 48);
|
||||
file->iv[10] = (u_char) (iv >> 40);
|
||||
file->iv[11] = (u_char) (iv >> 32);
|
||||
file->iv[12] = (u_char) (iv >> 24);
|
||||
file->iv[13] = (u_char) (iv >> 16);
|
||||
file->iv[14] = (u_char) (iv >> 8);
|
||||
file->iv[15] = (u_char) (iv);
|
||||
|
||||
file->encrypt = 1;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_mpegts_open_file(ngx_rtmp_mpegts_file_t *file, u_char *path,
|
||||
ngx_log_t *log, ngx_rtmp_codec_ctx_t *codec_ctx, ngx_uint_t mpegts_cc)
|
||||
{
|
||||
file->log = log;
|
||||
|
||||
file->fd = ngx_open_file(path, NGX_FILE_WRONLY, NGX_FILE_TRUNCATE,
|
||||
NGX_FILE_DEFAULT_ACCESS);
|
||||
|
||||
if (file->fd == NGX_INVALID_FILE) {
|
||||
ngx_log_error(NGX_LOG_ERR, log, ngx_errno,
|
||||
"hls: error creating fragment file");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
file->size = 0;
|
||||
|
||||
if (ngx_rtmp_mpegts_write_header(file, codec_ctx, mpegts_cc) != NGX_OK) {
|
||||
ngx_log_error(NGX_LOG_ERR, log, ngx_errno,
|
||||
"hls: error writing fragment header");
|
||||
ngx_close_file(file->fd);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_mpegts_close_file(ngx_rtmp_mpegts_file_t *file)
|
||||
{
|
||||
u_char buf[16];
|
||||
ssize_t rc;
|
||||
|
||||
if (file->encrypt) {
|
||||
ngx_memset(file->buf + file->size, 16 - file->size, 16 - file->size);
|
||||
|
||||
AES_cbc_encrypt(file->buf, buf, 16, &file->key, file->iv, AES_ENCRYPT);
|
||||
|
||||
rc = ngx_write_fd(file->fd, buf, 16);
|
||||
if (rc < 0) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
ngx_close_file(file->fd);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
48
hls/ngx_rtmp_mpegts.h
Normal file
48
hls/ngx_rtmp_mpegts.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_MPEGTS_H_INCLUDED_
|
||||
#define _NGX_RTMP_MPEGTS_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <openssl/aes.h>
|
||||
|
||||
#include <ngx_rtmp_codec_module.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_fd_t fd;
|
||||
ngx_log_t *log;
|
||||
unsigned encrypt:1;
|
||||
unsigned size:4;
|
||||
u_char buf[16];
|
||||
u_char iv[16];
|
||||
AES_KEY key;
|
||||
} ngx_rtmp_mpegts_file_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint64_t pts;
|
||||
uint64_t dts;
|
||||
ngx_uint_t pid;
|
||||
ngx_uint_t sid;
|
||||
ngx_uint_t cc;
|
||||
unsigned key:1;
|
||||
} ngx_rtmp_mpegts_frame_t;
|
||||
|
||||
|
||||
ngx_int_t ngx_rtmp_mpegts_init_encryption(ngx_rtmp_mpegts_file_t *file,
|
||||
u_char *key, size_t key_len, uint64_t iv);
|
||||
ngx_int_t ngx_rtmp_mpegts_open_file(ngx_rtmp_mpegts_file_t *file, u_char *path,
|
||||
ngx_log_t *log, ngx_rtmp_codec_ctx_t *codec_ctx, ngx_uint_t mpegts_cc);
|
||||
ngx_int_t ngx_rtmp_mpegts_close_file(ngx_rtmp_mpegts_file_t *file);
|
||||
ngx_int_t ngx_rtmp_mpegts_write_frame(ngx_rtmp_mpegts_file_t *file,
|
||||
ngx_rtmp_mpegts_frame_t *f, ngx_buf_t *b);
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_MPEGTS_H_INCLUDED_ */
|
80
hls/ngx_rtmp_mpegts_crc.c
Normal file
80
hls/ngx_rtmp_mpegts_crc.c
Normal file
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* \file crc.c
|
||||
* Functions and types for CRC checks.
|
||||
*
|
||||
* Generated on Thu May 5 15:32:31 2016,
|
||||
* by pycrc v0.9, https://pycrc.org
|
||||
* using the configuration:
|
||||
* Width = 32
|
||||
* Poly = 0x04c11db7
|
||||
* Xor_In = 0xffffffff
|
||||
* ReflectIn = False
|
||||
* Xor_Out = 0x00000000
|
||||
* ReflectOut = False
|
||||
* Algorithm = table-driven
|
||||
*****************************************************************************/
|
||||
#include "ngx_rtmp_mpegts_crc.h" /* include the header file generated with pycrc */
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Static table used for the table_driven implementation.
|
||||
*****************************************************************************/
|
||||
static const ngx_rtmp_mpegts_crc_t ngx_rtmp_mpegts_crc_table[256] = {
|
||||
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
|
||||
0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
|
||||
0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
|
||||
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
|
||||
0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
|
||||
0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
|
||||
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
|
||||
0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
|
||||
0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
|
||||
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
|
||||
0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
|
||||
0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
|
||||
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
|
||||
0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
|
||||
0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
|
||||
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
|
||||
0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
|
||||
0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
|
||||
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
|
||||
0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
|
||||
0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
|
||||
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
|
||||
0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
|
||||
0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
|
||||
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
|
||||
0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
|
||||
0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
|
||||
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
|
||||
0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
|
||||
0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
|
||||
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
|
||||
0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the crc value with new data.
|
||||
*
|
||||
* \param crc The current crc value.
|
||||
* \param data Pointer to a buffer of \a data_len bytes.
|
||||
* \param data_len Number of bytes in the \a data buffer.
|
||||
* \return The updated crc value.
|
||||
*****************************************************************************/
|
||||
ngx_rtmp_mpegts_crc_t ngx_rtmp_mpegts_crc_update(ngx_rtmp_mpegts_crc_t crc, const void *data, size_t data_len)
|
||||
{
|
||||
const unsigned char *d = (const unsigned char *)data;
|
||||
unsigned int tbl_idx;
|
||||
|
||||
while (data_len--) {
|
||||
tbl_idx = ((crc >> 24) ^ *d) & 0xff;
|
||||
crc = (ngx_rtmp_mpegts_crc_table[tbl_idx] ^ (crc << 8)) & 0xffffffff;
|
||||
|
||||
d++;
|
||||
}
|
||||
return crc & 0xffffffff;
|
||||
}
|
||||
|
||||
|
83
hls/ngx_rtmp_mpegts_crc.h
Normal file
83
hls/ngx_rtmp_mpegts_crc.h
Normal file
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* \file crc.h
|
||||
* Functions and types for CRC checks.
|
||||
*
|
||||
* Generated on Thu May 5 15:32:22 2016,
|
||||
* by pycrc v0.9, https://pycrc.org
|
||||
* using the configuration:
|
||||
* Width = 32
|
||||
* Poly = 0x04c11db7
|
||||
* Xor_In = 0xffffffff
|
||||
* ReflectIn = False
|
||||
* Xor_Out = 0x00000000
|
||||
* ReflectOut = False
|
||||
* Algorithm = table-driven
|
||||
*****************************************************************************/
|
||||
#ifndef _NGX_RTMP_MPEGTS_CRC_H_INCLUDED_
|
||||
#define _NGX_RTMP_MPEGTS_CRC_H_INCLUDED_
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* The definition of the used algorithm.
|
||||
*
|
||||
* This is not used anywhere in the generated code, but it may be used by the
|
||||
* application code to call algoritm-specific code, is desired.
|
||||
*****************************************************************************/
|
||||
#define CRC_ALGO_TABLE_DRIVEN 1
|
||||
|
||||
|
||||
/**
|
||||
* The type of the CRC values.
|
||||
*
|
||||
* This type must be big enough to contain at least 32 bits.
|
||||
*****************************************************************************/
|
||||
typedef uint_fast32_t ngx_rtmp_mpegts_crc_t;
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the initial crc value.
|
||||
*
|
||||
* \return The initial crc value.
|
||||
*****************************************************************************/
|
||||
static inline ngx_rtmp_mpegts_crc_t ngx_rtmp_mpegts_crc_init(void)
|
||||
{
|
||||
return 0xffffffff;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the crc value with new data.
|
||||
*
|
||||
* \param crc The current crc value.
|
||||
* \param data Pointer to a buffer of \a data_len bytes.
|
||||
* \param data_len Number of bytes in the \a data buffer.
|
||||
* \return The updated crc value.
|
||||
*****************************************************************************/
|
||||
ngx_rtmp_mpegts_crc_t ngx_rtmp_mpegts_crc_update(ngx_rtmp_mpegts_crc_t crc, const void *data, size_t data_len);
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the final crc value.
|
||||
*
|
||||
* \param crc The current crc value.
|
||||
* \return The final crc value.
|
||||
*****************************************************************************/
|
||||
static inline ngx_rtmp_mpegts_crc_t ngx_rtmp_mpegts_crc_finalize(ngx_rtmp_mpegts_crc_t crc)
|
||||
{
|
||||
return crc ^ 0x00000000;
|
||||
}
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* closing brace for extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* _NGX_RTMP_MPEGTS_CRC_H_INCLUDED_ */
|
348
ngx_rtmp.c
348
ngx_rtmp.c
|
@ -1,11 +1,13 @@
|
|||
|
||||
/*
|
||||
* Copyright (c) 2012 Roman Arutyunyan
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
#include <nginx.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
||||
|
||||
|
@ -20,10 +22,23 @@ static ngx_int_t ngx_rtmp_add_addrs6(ngx_conf_t *cf, ngx_rtmp_port_t *mport,
|
|||
ngx_rtmp_conf_addr_t *addr);
|
||||
#endif
|
||||
static ngx_int_t ngx_rtmp_cmp_conf_addrs(const void *one, const void *two);
|
||||
static ngx_int_t ngx_rtmp_init_events(ngx_conf_t *cf,
|
||||
static ngx_int_t ngx_rtmp_init_events(ngx_conf_t *cf,
|
||||
ngx_rtmp_core_main_conf_t *cmcf);
|
||||
static ngx_int_t ngx_rtmp_init_event_handlers(ngx_conf_t *cf,
|
||||
static ngx_int_t ngx_rtmp_init_event_handlers(ngx_conf_t *cf,
|
||||
ngx_rtmp_core_main_conf_t *cmcf);
|
||||
static char * ngx_rtmp_merge_applications(ngx_conf_t *cf,
|
||||
ngx_array_t *applications, void **app_conf, ngx_rtmp_module_t *module,
|
||||
ngx_uint_t ctx_index);
|
||||
static ngx_int_t ngx_rtmp_init_process(ngx_cycle_t *cycle);
|
||||
|
||||
|
||||
#if (nginx_version >= 1007011)
|
||||
ngx_queue_t ngx_rtmp_init_queue;
|
||||
#elif (nginx_version >= 1007005)
|
||||
ngx_thread_volatile ngx_queue_t ngx_rtmp_init_queue;
|
||||
#else
|
||||
ngx_thread_volatile ngx_event_t *ngx_rtmp_init_queue;
|
||||
#endif
|
||||
|
||||
|
||||
ngx_uint_t ngx_rtmp_max_module;
|
||||
|
@ -56,7 +71,7 @@ ngx_module_t ngx_rtmp_module = {
|
|||
NGX_CORE_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
ngx_rtmp_init_process, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
|
@ -72,10 +87,11 @@ ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
ngx_uint_t i, m, mi, s;
|
||||
ngx_conf_t pcf;
|
||||
ngx_array_t ports;
|
||||
ngx_module_t **modules;
|
||||
ngx_rtmp_listen_t *listen;
|
||||
ngx_rtmp_module_t *module;
|
||||
ngx_rtmp_conf_ctx_t *ctx;
|
||||
ngx_rtmp_core_srv_conf_t **cscfp;
|
||||
ngx_rtmp_core_srv_conf_t *cscf, **cscfp;
|
||||
ngx_rtmp_core_main_conf_t *cmcf;
|
||||
|
||||
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t));
|
||||
|
@ -86,14 +102,18 @@ ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
*(ngx_rtmp_conf_ctx_t **) conf = ctx;
|
||||
|
||||
/* count the number of the rtmp modules and set up their indices */
|
||||
|
||||
#if defined(nginx_version) && nginx_version >= 1009011
|
||||
modules = cf->cycle->modules;
|
||||
#else
|
||||
modules = ngx_modules;
|
||||
#endif
|
||||
ngx_rtmp_max_module = 0;
|
||||
for (m = 0; ngx_modules[m]; m++) {
|
||||
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
|
||||
for (m = 0; modules[m]; m++) {
|
||||
if (modules[m]->type != NGX_RTMP_MODULE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ngx_modules[m]->ctx_index = ngx_rtmp_max_module++;
|
||||
modules[m]->ctx_index = ngx_rtmp_max_module++;
|
||||
}
|
||||
|
||||
|
||||
|
@ -118,17 +138,28 @@ ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
|
||||
|
||||
/*
|
||||
* create the main_conf's, the null srv_conf's, and the null loc_conf's
|
||||
* the rtmp null app_conf context, it is used to merge
|
||||
* the server{}s' app_conf's
|
||||
*/
|
||||
|
||||
ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module);
|
||||
if (ctx->app_conf == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* create the main_conf's, the null srv_conf's, and the null app_conf's
|
||||
* of the all rtmp modules
|
||||
*/
|
||||
|
||||
for (m = 0; ngx_modules[m]; m++) {
|
||||
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
|
||||
for (m = 0; modules[m]; m++) {
|
||||
if (modules[m]->type != NGX_RTMP_MODULE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
module = ngx_modules[m]->ctx;
|
||||
mi = ngx_modules[m]->ctx_index;
|
||||
module = modules[m]->ctx;
|
||||
mi = modules[m]->ctx_index;
|
||||
|
||||
if (module->create_main_conf) {
|
||||
ctx->main_conf[mi] = module->create_main_conf(cf);
|
||||
|
@ -143,17 +174,24 @@ ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
return NGX_CONF_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (module->create_app_conf) {
|
||||
ctx->app_conf[mi] = module->create_app_conf(cf);
|
||||
if (ctx->app_conf[mi] == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pcf = *cf;
|
||||
cf->ctx = ctx;
|
||||
|
||||
for (m = 0; ngx_modules[m]; m++) {
|
||||
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
|
||||
for (m = 0; modules[m]; m++) {
|
||||
if (modules[m]->type != NGX_RTMP_MODULE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
module = ngx_modules[m]->ctx;
|
||||
module = modules[m]->ctx;
|
||||
|
||||
if (module->preconfiguration) {
|
||||
if (module->preconfiguration(cf) != NGX_OK) {
|
||||
|
@ -179,13 +217,13 @@ ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
cmcf = ctx->main_conf[ngx_rtmp_core_module.ctx_index];
|
||||
cscfp = cmcf->servers.elts;
|
||||
|
||||
for (m = 0; ngx_modules[m]; m++) {
|
||||
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
|
||||
for (m = 0; modules[m]; m++) {
|
||||
if (modules[m]->type != NGX_RTMP_MODULE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
module = ngx_modules[m]->ctx;
|
||||
mi = ngx_modules[m]->ctx_index;
|
||||
module = modules[m]->ctx;
|
||||
mi = modules[m]->ctx_index;
|
||||
|
||||
/* init rtmp{} main_conf's */
|
||||
|
||||
|
@ -214,19 +252,48 @@ ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
if (module->merge_app_conf) {
|
||||
|
||||
/* merge the server{}'s app_conf */
|
||||
|
||||
/*ctx->app_conf = cscfp[s]->ctx->loc_conf;*/
|
||||
|
||||
rv = module->merge_app_conf(cf,
|
||||
ctx->app_conf[mi],
|
||||
cscfp[s]->ctx->app_conf[mi]);
|
||||
if (rv != NGX_CONF_OK) {
|
||||
*cf = pcf;
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* merge the applications{}' app_conf's */
|
||||
|
||||
cscf = cscfp[s]->ctx->srv_conf[ngx_rtmp_core_module.ctx_index];
|
||||
|
||||
rv = ngx_rtmp_merge_applications(cf, &cscf->applications,
|
||||
cscfp[s]->ctx->app_conf,
|
||||
module, mi);
|
||||
if (rv != NGX_CONF_OK) {
|
||||
*cf = pcf;
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (ngx_rtmp_init_events(cf, cmcf) != NGX_OK) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
for (m = 0; ngx_modules[m]; m++) {
|
||||
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
|
||||
for (m = 0; modules[m]; m++) {
|
||||
if (modules[m]->type != NGX_RTMP_MODULE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
module = ngx_modules[m]->ctx;
|
||||
module = modules[m]->ctx;
|
||||
|
||||
if (module->postconfiguration) {
|
||||
if (module->postconfiguration(cf) != NGX_OK) {
|
||||
|
@ -259,79 +326,80 @@ ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_rtmp_merge_applications(ngx_conf_t *cf, ngx_array_t *applications,
|
||||
void **app_conf, ngx_rtmp_module_t *module, ngx_uint_t ctx_index)
|
||||
{
|
||||
char *rv;
|
||||
ngx_rtmp_conf_ctx_t *ctx, saved;
|
||||
ngx_rtmp_core_app_conf_t **cacfp;
|
||||
ngx_uint_t n;
|
||||
ngx_rtmp_core_app_conf_t *cacf;
|
||||
|
||||
if (applications == NULL) {
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
ctx = (ngx_rtmp_conf_ctx_t *) cf->ctx;
|
||||
saved = *ctx;
|
||||
|
||||
cacfp = applications->elts;
|
||||
for (n = 0; n < applications->nelts; ++n, ++cacfp) {
|
||||
|
||||
ctx->app_conf = (*cacfp)->app_conf;
|
||||
|
||||
rv = module->merge_app_conf(cf, app_conf[ctx_index],
|
||||
(*cacfp)->app_conf[ctx_index]);
|
||||
if (rv != NGX_CONF_OK) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
cacf = (*cacfp)->app_conf[ngx_rtmp_core_module.ctx_index];
|
||||
rv = ngx_rtmp_merge_applications(cf, &cacf->applications,
|
||||
(*cacfp)->app_conf,
|
||||
module, ctx_index);
|
||||
if (rv != NGX_CONF_OK) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
*ctx = saved;
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_init_events(ngx_conf_t *cf, ngx_rtmp_core_main_conf_t *cmcf)
|
||||
{
|
||||
size_t n;
|
||||
size_t n;
|
||||
|
||||
for(n = 0; n < NGX_RTMP_MSG_MAX; ++n) {
|
||||
if (ngx_array_init(&cmcf->events[n], cf->pool, 1,
|
||||
sizeof(ngx_rtmp_event_handler_pt)) != NGX_OK)
|
||||
for(n = 0; n < NGX_RTMP_MAX_EVENT; ++n) {
|
||||
if (ngx_array_init(&cmcf->events[n], cf->pool, 1,
|
||||
sizeof(ngx_rtmp_handler_pt)) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (ngx_array_init(&cmcf->amf0, cf->pool, 1,
|
||||
sizeof(ngx_hash_key_t)) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_array_init(&cmcf->disconnect, cf->pool, 1,
|
||||
sizeof(ngx_rtmp_disconnect_handler_pt)) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
static ngx_int_t
|
||||
ngx_rtmp_init_amf0_handler(ngx_rtmp_core_main_conf_t *cmcf,
|
||||
ngx_int_t id, ngx_array_t *array, ngx_hash_t *hash,
|
||||
ngx_rtmp_event_handler_pt handler)
|
||||
{
|
||||
ngx_hash_init_t calls_hash;
|
||||
ngx_rtmp_event_handler_pt *eh;
|
||||
ngx_hash_key_t *h;
|
||||
size_t n;
|
||||
|
||||
eh = ngx_array_push(&cmcf->events[id]);
|
||||
*eh = handler;
|
||||
|
||||
h = array->elts;
|
||||
for(n = 0; n < array->nelts; ++n, ++h) {
|
||||
h->key_hash = ngx_hash_key_lc(h->key.data, h->key.len);
|
||||
}
|
||||
|
||||
calls_hash.hash = hash;
|
||||
calls_hash.key = ngx_hash_key_lc;
|
||||
calls_hash.max_size = 512;
|
||||
calls_hash.bucket_size = ngx_cacheline_size;
|
||||
calls_hash.name = "amf0_hash";
|
||||
calls_hash.pool = cf->pool;
|
||||
calls_hash.temp_pool = NULL;
|
||||
|
||||
if (ngx_hash_init(&calls_hash, array->elts, array->nelts)
|
||||
!= NGX_OK)
|
||||
if (ngx_array_init(&cmcf->amf, cf->pool, 1,
|
||||
sizeof(ngx_rtmp_amf_handler_t)) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_init_event_handlers(ngx_conf_t *cf, ngx_rtmp_core_main_conf_t *cmcf)
|
||||
{
|
||||
ngx_hash_init_t calls_hash;
|
||||
ngx_rtmp_event_handler_pt *eh;
|
||||
ngx_hash_key_t *h;
|
||||
size_t n;
|
||||
ngx_rtmp_handler_pt *eh;
|
||||
ngx_rtmp_amf_handler_t *h;
|
||||
ngx_hash_key_t *ha;
|
||||
size_t n, m;
|
||||
|
||||
static size_t pm_events[] = {
|
||||
NGX_RTMP_MSG_CHUNK_SIZE,
|
||||
|
@ -340,40 +408,73 @@ ngx_rtmp_init_event_handlers(ngx_conf_t *cf, ngx_rtmp_core_main_conf_t *cmcf)
|
|||
NGX_RTMP_MSG_ACK_SIZE,
|
||||
NGX_RTMP_MSG_BANDWIDTH
|
||||
};
|
||||
static size_t amf0_events[] = {
|
||||
NGX_RTMP_MSG_AMF0_META,
|
||||
NGX_RTMP_MSG_AMF0_SHARED,
|
||||
NGX_RTMP_MSG_AMF0_CMD
|
||||
|
||||
static size_t amf_events[] = {
|
||||
NGX_RTMP_MSG_AMF_CMD,
|
||||
NGX_RTMP_MSG_AMF_META,
|
||||
NGX_RTMP_MSG_AMF_SHARED,
|
||||
NGX_RTMP_MSG_AMF3_CMD,
|
||||
NGX_RTMP_MSG_AMF3_META,
|
||||
NGX_RTMP_MSG_AMF3_SHARED
|
||||
};
|
||||
|
||||
/* init events */
|
||||
/* init standard protocol events */
|
||||
for(n = 0; n < sizeof(pm_events) / sizeof(pm_events[0]); ++n) {
|
||||
eh = ngx_array_push(&cmcf->events[pm_events[n]]);
|
||||
*eh = ngx_rtmp_protocol_message_handler;
|
||||
}
|
||||
|
||||
for(n = 0; n < sizeof(amf0_events) / sizeof(amf0_events[0]); ++n) {
|
||||
eh = ngx_array_push(&cmcf->events[amf0_events[n]]);
|
||||
*eh = ngx_rtmp_amf0_message_handler;
|
||||
/* init amf events */
|
||||
for(n = 0; n < sizeof(amf_events) / sizeof(amf_events[0]); ++n) {
|
||||
eh = ngx_array_push(&cmcf->events[amf_events[n]]);
|
||||
*eh = ngx_rtmp_amf_message_handler;
|
||||
}
|
||||
|
||||
/* init user protocol events */
|
||||
eh = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_USER]);
|
||||
*eh = ngx_rtmp_user_message_handler;
|
||||
|
||||
h = cmcf->amf0.elts;
|
||||
for(n = 0; n < cmcf->amf0.nelts; ++n, ++h) {
|
||||
h->key_hash = ngx_hash_key_lc(h->key.data, h->key.len);
|
||||
/* aggregate to audio/video map */
|
||||
eh = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AGGREGATE]);
|
||||
*eh = ngx_rtmp_aggregate_message_handler;
|
||||
|
||||
/* init amf callbacks */
|
||||
ngx_array_init(&cmcf->amf_arrays, cf->pool, 1, sizeof(ngx_hash_key_t));
|
||||
|
||||
h = cmcf->amf.elts;
|
||||
for(n = 0; n < cmcf->amf.nelts; ++n, ++h) {
|
||||
ha = cmcf->amf_arrays.elts;
|
||||
for(m = 0; m < cmcf->amf_arrays.nelts; ++m, ++ha) {
|
||||
if (h->name.len == ha->key.len
|
||||
&& !ngx_strncmp(h->name.data, ha->key.data, ha->key.len))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (m == cmcf->amf_arrays.nelts) {
|
||||
ha = ngx_array_push(&cmcf->amf_arrays);
|
||||
ha->key = h->name;
|
||||
ha->key_hash = ngx_hash_key_lc(ha->key.data, ha->key.len);
|
||||
ha->value = ngx_array_create(cf->pool, 1,
|
||||
sizeof(ngx_rtmp_handler_pt));
|
||||
if (ha->value == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
eh = ngx_array_push((ngx_array_t*)ha->value);
|
||||
*eh = h->handler;
|
||||
}
|
||||
|
||||
calls_hash.hash = &cmcf->amf0_hash;
|
||||
calls_hash.hash = &cmcf->amf_hash;
|
||||
calls_hash.key = ngx_hash_key_lc;
|
||||
calls_hash.max_size = 512;
|
||||
calls_hash.bucket_size = ngx_cacheline_size;
|
||||
calls_hash.name = "calls_hash";
|
||||
calls_hash.name = "amf_hash";
|
||||
calls_hash.pool = cf->pool;
|
||||
calls_hash.temp_pool = NULL;
|
||||
|
||||
if (ngx_hash_init(&calls_hash, cmcf->amf0.elts, cmcf->amf0.nelts)
|
||||
if (ngx_hash_init(&calls_hash, cmcf->amf_arrays.elts, cmcf->amf_arrays.nelts)
|
||||
!= NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
|
@ -455,6 +556,7 @@ found:
|
|||
addr->bind = listen->bind;
|
||||
addr->wildcard = listen->wildcard;
|
||||
addr->so_keepalive = listen->so_keepalive;
|
||||
addr->proxy_protocol = listen->proxy_protocol;
|
||||
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
|
||||
addr->tcp_keepidle = listen->tcp_keepidle;
|
||||
addr->tcp_keepintvl = listen->tcp_keepintvl;
|
||||
|
@ -515,7 +617,7 @@ ngx_rtmp_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports)
|
|||
|
||||
ls->addr_ntop = 1;
|
||||
ls->handler = ngx_rtmp_init_connection;
|
||||
ls->pool_size = 256;
|
||||
ls->pool_size = 4096;
|
||||
|
||||
/* TODO: error_log directive */
|
||||
ls->logp = &cf->cycle->new_log;
|
||||
|
@ -598,7 +700,11 @@ ngx_rtmp_add_addrs(ngx_conf_t *cf, ngx_rtmp_port_t *mport,
|
|||
|
||||
addrs[i].conf.ctx = addr[i].ctx;
|
||||
|
||||
len = ngx_sock_ntop(addr[i].sockaddr, buf, NGX_SOCKADDR_STRLEN, 1);
|
||||
len = ngx_sock_ntop(addr[i].sockaddr,
|
||||
#if (nginx_version >= 1005003)
|
||||
addr[i].socklen,
|
||||
#endif
|
||||
buf, NGX_SOCKADDR_STRLEN, 1);
|
||||
|
||||
p = ngx_pnalloc(cf->pool, len);
|
||||
if (p == NULL) {
|
||||
|
@ -609,6 +715,7 @@ ngx_rtmp_add_addrs(ngx_conf_t *cf, ngx_rtmp_port_t *mport,
|
|||
|
||||
addrs[i].conf.addr_text.len = len;
|
||||
addrs[i].conf.addr_text.data = p;
|
||||
addrs[i].conf.proxy_protocol = addr->proxy_protocol;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
|
@ -643,7 +750,11 @@ ngx_rtmp_add_addrs6(ngx_conf_t *cf, ngx_rtmp_port_t *mport,
|
|||
|
||||
addrs6[i].conf.ctx = addr[i].ctx;
|
||||
|
||||
len = ngx_sock_ntop(addr[i].sockaddr, buf, NGX_SOCKADDR_STRLEN, 1);
|
||||
len = ngx_sock_ntop(addr[i].sockaddr,
|
||||
#if (nginx_version >= 1005003)
|
||||
addr[i].socklen,
|
||||
#endif
|
||||
buf, NGX_SOCKADDR_STRLEN, 1);
|
||||
|
||||
p = ngx_pnalloc(cf->pool, len);
|
||||
if (p == NULL) {
|
||||
|
@ -654,6 +765,7 @@ ngx_rtmp_add_addrs6(ngx_conf_t *cf, ngx_rtmp_port_t *mport,
|
|||
|
||||
addrs6[i].conf.addr_text.len = len;
|
||||
addrs6[i].conf.addr_text.data = p;
|
||||
addrs6[i].conf.proxy_protocol = addr->proxy_protocol;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
|
@ -689,3 +801,51 @@ ngx_rtmp_cmp_conf_addrs(const void *one, const void *two)
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_fire_event(ngx_rtmp_session_t *s, ngx_uint_t evt,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in)
|
||||
{
|
||||
ngx_rtmp_core_main_conf_t *cmcf;
|
||||
ngx_array_t *ch;
|
||||
ngx_rtmp_handler_pt *hh;
|
||||
size_t n;
|
||||
|
||||
cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module);
|
||||
|
||||
ch = &cmcf->events[evt];
|
||||
hh = ch->elts;
|
||||
for(n = 0; n < ch->nelts; ++n, ++hh) {
|
||||
if (*hh && (*hh)(s, h, in) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
void *
|
||||
ngx_rtmp_rmemcpy(void *dst, const void* src, size_t n)
|
||||
{
|
||||
u_char *d, *s;
|
||||
|
||||
d = dst;
|
||||
s = (u_char*)src + n - 1;
|
||||
|
||||
while(s >= (u_char*)src) {
|
||||
*d++ = *s--;
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_init_process(ngx_cycle_t *cycle)
|
||||
{
|
||||
#if (nginx_version >= 1007005)
|
||||
ngx_queue_init((ngx_queue_t*) &ngx_rtmp_init_queue);
|
||||
#endif
|
||||
return NGX_OK;
|
||||
}
|
||||
|
|
404
ngx_rtmp.h
404
ngx_rtmp.h
|
@ -1,5 +1,6 @@
|
|||
|
||||
/*
|
||||
* Copyright (c) 2012 Roman Arutyunyan
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
|
@ -11,13 +12,22 @@
|
|||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
#include <ngx_event_connect.h>
|
||||
#include <nginx.h>
|
||||
|
||||
#include "ngx_rtmp_amf0.h"
|
||||
#include "ngx_rtmp_amf.h"
|
||||
#include "ngx_rtmp_bandwidth.h"
|
||||
|
||||
|
||||
#if (NGX_WIN32)
|
||||
typedef __int8 int8_t;
|
||||
typedef unsigned __int8 uint8_t;
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct {
|
||||
void **main_conf;
|
||||
void **srv_conf;
|
||||
void **app_conf;
|
||||
} ngx_rtmp_conf_ctx_t;
|
||||
|
||||
|
||||
|
@ -34,6 +44,7 @@ typedef struct {
|
|||
unsigned ipv6only:2;
|
||||
#endif
|
||||
unsigned so_keepalive:2;
|
||||
unsigned proxy_protocol:1;
|
||||
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
|
||||
int tcp_keepidle;
|
||||
int tcp_keepintvl;
|
||||
|
@ -45,19 +56,20 @@ typedef struct {
|
|||
typedef struct {
|
||||
ngx_rtmp_conf_ctx_t *ctx;
|
||||
ngx_str_t addr_text;
|
||||
unsigned proxy_protocol:1;
|
||||
} ngx_rtmp_addr_conf_t;
|
||||
|
||||
typedef struct {
|
||||
in_addr_t addr;
|
||||
ngx_rtmp_addr_conf_t conf;
|
||||
in_addr_t addr;
|
||||
} ngx_rtmp_in_addr_t;
|
||||
|
||||
|
||||
#if (NGX_HAVE_INET6)
|
||||
|
||||
typedef struct {
|
||||
struct in6_addr addr6;
|
||||
ngx_rtmp_addr_conf_t conf;
|
||||
struct in6_addr addr6;
|
||||
} ngx_rtmp_in6_addr_t;
|
||||
|
||||
#endif
|
||||
|
@ -88,6 +100,7 @@ typedef struct {
|
|||
unsigned ipv6only:2;
|
||||
#endif
|
||||
unsigned so_keepalive:2;
|
||||
unsigned proxy_protocol:1;
|
||||
#if (NGX_HAVE_KEEPALIVE_TUNABLE)
|
||||
int tcp_keepidle;
|
||||
int tcp_keepintvl;
|
||||
|
@ -100,18 +113,9 @@ typedef struct {
|
|||
|
||||
#define NGX_LOG_DEBUG_RTMP NGX_LOG_DEBUG_CORE
|
||||
|
||||
#define NGX_RTMP_HANDSHAKE_SIZE 1536
|
||||
|
||||
#define NGX_RTMP_DEFAULT_CHUNK_SIZE 128
|
||||
|
||||
|
||||
/* RTMP handshake stages */
|
||||
#define NGX_RTMP_HS_READ_DATA 0
|
||||
#define NGX_RTMP_HS_WRITE_DATA 1
|
||||
#define NGX_RTMP_HS_WRITE_ECHO 2
|
||||
#define NGX_RTMP_HS_READ_ECHO 3
|
||||
|
||||
|
||||
/* RTMP message types */
|
||||
#define NGX_RTMP_MSG_CHUNK_SIZE 1
|
||||
#define NGX_RTMP_MSG_ABORT 2
|
||||
|
@ -125,11 +129,16 @@ typedef struct {
|
|||
#define NGX_RTMP_MSG_AMF3_META 15
|
||||
#define NGX_RTMP_MSG_AMF3_SHARED 16
|
||||
#define NGX_RTMP_MSG_AMF3_CMD 17
|
||||
#define NGX_RTMP_MSG_AMF0_META 18
|
||||
#define NGX_RTMP_MSG_AMF0_SHARED 19
|
||||
#define NGX_RTMP_MSG_AMF0_CMD 20
|
||||
#define NGX_RTMP_MSG_AMF_META 18
|
||||
#define NGX_RTMP_MSG_AMF_SHARED 19
|
||||
#define NGX_RTMP_MSG_AMF_CMD 20
|
||||
#define NGX_RTMP_MSG_AGGREGATE 22
|
||||
#define NGX_RTMP_MSG_MAX 23
|
||||
#define NGX_RTMP_MSG_MAX 22
|
||||
|
||||
#define NGX_RTMP_CONNECT NGX_RTMP_MSG_MAX + 1
|
||||
#define NGX_RTMP_DISCONNECT NGX_RTMP_MSG_MAX + 2
|
||||
#define NGX_RTMP_HANDSHAKE_DONE NGX_RTMP_MSG_MAX + 3
|
||||
#define NGX_RTMP_MAX_EVENT NGX_RTMP_MSG_MAX + 4
|
||||
|
||||
|
||||
/* RMTP control message types */
|
||||
|
@ -141,6 +150,7 @@ typedef struct {
|
|||
#define NGX_RTMP_USER_PING_REQUEST 6
|
||||
#define NGX_RTMP_USER_PING_RESPONSE 7
|
||||
#define NGX_RTMP_USER_UNKNOWN 8
|
||||
#define NGX_RTMP_USER_BUFFER_END 31
|
||||
|
||||
|
||||
/* Chunk header:
|
||||
|
@ -161,30 +171,78 @@ typedef struct {
|
|||
|
||||
typedef struct {
|
||||
ngx_rtmp_header_t hdr;
|
||||
uint32_t dtime;
|
||||
uint32_t len; /* current fragment length */
|
||||
uint8_t ext;
|
||||
ngx_chain_t *in;
|
||||
} ngx_rtmp_stream_t;
|
||||
|
||||
|
||||
/* disable zero-sized array warning by msvc */
|
||||
|
||||
#if (NGX_WIN32)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable:4200)
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint32_t signature; /* "RTMP" */ /* <-- FIXME wtf */
|
||||
|
||||
ngx_connection_t *connection;
|
||||
ngx_event_t close;
|
||||
|
||||
void **ctx;
|
||||
void **main_conf;
|
||||
void **srv_conf;
|
||||
void **app_conf;
|
||||
|
||||
ngx_str_t *addr_text;
|
||||
int connected;
|
||||
|
||||
/* TODO: allocate this bufs from shared pool */
|
||||
ngx_buf_t hs_in_buf;
|
||||
ngx_buf_t hs_out_buf;
|
||||
#if (nginx_version >= 1007005)
|
||||
ngx_queue_t posted_dry_events;
|
||||
#else
|
||||
ngx_event_t *posted_dry_events;
|
||||
#endif
|
||||
|
||||
/* client buffer time in msec */
|
||||
uint32_t buflen;
|
||||
uint32_t ack_size;
|
||||
|
||||
/* connection parameters */
|
||||
ngx_str_t app;
|
||||
ngx_str_t args;
|
||||
ngx_str_t flashver;
|
||||
ngx_str_t swf_url;
|
||||
ngx_str_t tc_url;
|
||||
uint32_t acodecs;
|
||||
uint32_t vcodecs;
|
||||
ngx_str_t page_url;
|
||||
|
||||
/* handshake data */
|
||||
ngx_buf_t *hs_buf;
|
||||
u_char *hs_digest;
|
||||
unsigned hs_old:1;
|
||||
ngx_uint_t hs_stage;
|
||||
|
||||
/* connection timestamps */
|
||||
uint32_t epoch;
|
||||
uint32_t peer_epoch;
|
||||
ngx_msec_t epoch;
|
||||
ngx_msec_t peer_epoch;
|
||||
ngx_msec_t base_time;
|
||||
uint32_t current_time;
|
||||
|
||||
/* ready for publishing? */
|
||||
unsigned ready_for_publish:1;
|
||||
|
||||
/* ping */
|
||||
ngx_event_t ping_evt;
|
||||
unsigned ping_active:1;
|
||||
unsigned ping_reset:1;
|
||||
|
||||
/* auto-pushed? */
|
||||
unsigned auto_pushed:1;
|
||||
unsigned relay:1;
|
||||
unsigned static_relay:1;
|
||||
|
||||
/* input stream 0 (reserved by RTMP spec)
|
||||
* is used as free chain link */
|
||||
|
@ -196,51 +254,98 @@ typedef struct {
|
|||
uint32_t in_bytes;
|
||||
uint32_t in_last_ack;
|
||||
|
||||
ngx_chain_t *out;
|
||||
ngx_chain_t *out_free_chains;
|
||||
ngx_pool_t *in_old_pool;
|
||||
ngx_int_t in_chunk_size_changing;
|
||||
|
||||
ngx_connection_t *connection;
|
||||
|
||||
/* circular buffer of RTMP message pointers */
|
||||
ngx_msec_t timeout;
|
||||
uint32_t out_bytes;
|
||||
size_t out_pos, out_last;
|
||||
ngx_chain_t *out_chain;
|
||||
u_char *out_bpos;
|
||||
unsigned out_buffer:1;
|
||||
size_t out_queue;
|
||||
size_t out_cork;
|
||||
ngx_chain_t *out[0];
|
||||
} ngx_rtmp_session_t;
|
||||
|
||||
|
||||
typedef ngx_int_t (*ngx_rtmp_event_handler_pt)(ngx_rtmp_session_t *s,
|
||||
#if (NGX_WIN32)
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
|
||||
/* handler result code:
|
||||
* NGX_ERROR - error
|
||||
* NGX_OK - success, may continue
|
||||
* NGX_DONE - success, input parsed, reply sent; need no
|
||||
* more calls on this event */
|
||||
typedef ngx_int_t (*ngx_rtmp_handler_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
typedef ngx_int_t (*ngx_rtmp_disconnect_handler_pt)(ngx_rtmp_session_t *s);
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t name;
|
||||
ngx_rtmp_handler_pt handler;
|
||||
} ngx_rtmp_amf_handler_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_array_t servers; /* ngx_rtmp_core_srv_conf_t */
|
||||
ngx_array_t listen; /* ngx_rtmp_listen_t */
|
||||
|
||||
ngx_array_t events[NGX_RTMP_MSG_MAX];
|
||||
ngx_array_t events[NGX_RTMP_MAX_EVENT];
|
||||
|
||||
ngx_hash_t amf0_hash;
|
||||
ngx_array_t amf0;
|
||||
|
||||
ngx_array_t disconnect;
|
||||
ngx_hash_t amf_hash;
|
||||
ngx_array_t amf_arrays;
|
||||
ngx_array_t amf;
|
||||
} ngx_rtmp_core_main_conf_t;
|
||||
|
||||
|
||||
/* global main conf for stats */
|
||||
extern ngx_rtmp_core_main_conf_t *ngx_rtmp_core_main_conf;
|
||||
|
||||
|
||||
typedef struct ngx_rtmp_core_srv_conf_s {
|
||||
ngx_array_t applications; /* ngx_rtmp_core_app_conf_t */
|
||||
|
||||
ngx_msec_t timeout;
|
||||
ngx_msec_t ping;
|
||||
ngx_msec_t ping_timeout;
|
||||
ngx_flag_t so_keepalive;
|
||||
ngx_int_t max_streams;
|
||||
|
||||
ngx_uint_t ack_window;
|
||||
|
||||
|
||||
ngx_int_t chunk_size;
|
||||
ngx_pool_t *pool;
|
||||
ngx_chain_t *free;
|
||||
ngx_chain_t *free_chains;
|
||||
size_t max_buf;
|
||||
ngx_flag_t wait_key_frame;
|
||||
ngx_chain_t *free_hs;
|
||||
size_t max_message;
|
||||
ngx_flag_t play_time_fix;
|
||||
ngx_flag_t publish_time_fix;
|
||||
ngx_flag_t busy;
|
||||
size_t out_queue;
|
||||
size_t out_cork;
|
||||
ngx_msec_t buflen;
|
||||
|
||||
ngx_rtmp_conf_ctx_t *ctx;
|
||||
} ngx_rtmp_core_srv_conf_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_array_t applications; /* ngx_rtmp_core_app_conf_t */
|
||||
ngx_str_t name;
|
||||
void **app_conf;
|
||||
} ngx_rtmp_core_app_conf_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t *client;
|
||||
ngx_rtmp_session_t *session;
|
||||
} ngx_rtmp_log_ctx_t;
|
||||
} ngx_rtmp_error_log_ctx_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
|
@ -252,17 +357,24 @@ typedef struct {
|
|||
|
||||
void *(*create_srv_conf)(ngx_conf_t *cf);
|
||||
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev,
|
||||
void *conf);
|
||||
void *conf);
|
||||
|
||||
void *(*create_app_conf)(ngx_conf_t *cf);
|
||||
char *(*merge_app_conf)(ngx_conf_t *cf, void *prev,
|
||||
void *conf);
|
||||
} ngx_rtmp_module_t;
|
||||
|
||||
#define NGX_RTMP_MODULE 0x504D5452 /* "RTMP" */
|
||||
|
||||
#define NGX_RTMP_MAIN_CONF 0x02000000
|
||||
#define NGX_RTMP_SRV_CONF 0x04000000
|
||||
#define NGX_RTMP_APP_CONF 0x08000000
|
||||
#define NGX_RTMP_REC_CONF 0x10000000
|
||||
|
||||
|
||||
#define NGX_RTMP_MAIN_CONF_OFFSET offsetof(ngx_rtmp_conf_ctx_t, main_conf)
|
||||
#define NGX_RTMP_SRV_CONF_OFFSET offsetof(ngx_rtmp_conf_ctx_t, srv_conf)
|
||||
#define NGX_RTMP_APP_CONF_OFFSET offsetof(ngx_rtmp_conf_ctx_t, app_conf)
|
||||
|
||||
|
||||
#define ngx_rtmp_get_module_ctx(s, module) (s)->ctx[module.ctx_index]
|
||||
|
@ -273,11 +385,15 @@ typedef struct {
|
|||
#define ngx_rtmp_get_module_main_conf(s, module) \
|
||||
(s)->main_conf[module.ctx_index]
|
||||
#define ngx_rtmp_get_module_srv_conf(s, module) (s)->srv_conf[module.ctx_index]
|
||||
#define ngx_rtmp_get_module_app_conf(s, module) ((s)->app_conf ? \
|
||||
(s)->app_conf[module.ctx_index] : NULL)
|
||||
|
||||
#define ngx_rtmp_conf_get_module_main_conf(cf, module) \
|
||||
((ngx_rtmp_conf_ctx_t *) cf->ctx)->main_conf[module.ctx_index]
|
||||
#define ngx_rtmp_conf_get_module_srv_conf(cf, module) \
|
||||
((ngx_rtmp_conf_ctx_t *) cf->ctx)->srv_conf[module.ctx_index]
|
||||
#define ngx_rtmp_conf_get_module_app_conf(cf, module) \
|
||||
((ngx_rtmp_conf_ctx_t *) cf->ctx)->app_conf[module.ctx_index]
|
||||
|
||||
|
||||
#ifdef NGX_DEBUG
|
||||
|
@ -285,34 +401,97 @@ char* ngx_rtmp_message_type(uint8_t type);
|
|||
char* ngx_rtmp_user_message_type(uint16_t evt);
|
||||
#endif
|
||||
|
||||
void ngx_rtmp_init_connection(ngx_connection_t *c);
|
||||
void ngx_rtmp_close_connection(ngx_connection_t *c);
|
||||
u_char * ngx_rtmp_log_error(ngx_log_t *log, u_char *buf, size_t len);
|
||||
uint32_t ngx_rtmp_get_timestamp();
|
||||
void ngx_rtmp_init_connection(ngx_connection_t *c);
|
||||
ngx_rtmp_session_t * ngx_rtmp_init_session(ngx_connection_t *c,
|
||||
ngx_rtmp_addr_conf_t *addr_conf);
|
||||
void ngx_rtmp_finalize_session(ngx_rtmp_session_t *s);
|
||||
void ngx_rtmp_handshake(ngx_rtmp_session_t *s);
|
||||
void ngx_rtmp_client_handshake(ngx_rtmp_session_t *s, unsigned async);
|
||||
void ngx_rtmp_free_handshake_buffers(ngx_rtmp_session_t *s);
|
||||
void ngx_rtmp_cycle(ngx_rtmp_session_t *s);
|
||||
void ngx_rtmp_reset_ping(ngx_rtmp_session_t *s);
|
||||
ngx_int_t ngx_rtmp_fire_event(ngx_rtmp_session_t *s, ngx_uint_t evt,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
|
||||
|
||||
ngx_int_t ngx_rtmp_set_chunk_size(ngx_rtmp_session_t *s, ngx_uint_t size);
|
||||
|
||||
|
||||
/* Bit reverse: we need big-endians in many places */
|
||||
void * ngx_rtmp_rmemcpy(void *dst, const void* src, size_t n);
|
||||
|
||||
#define ngx_rtmp_rcpymem(dst, src, n) \
|
||||
(((u_char*)ngx_rtmp_rmemcpy(dst, src, n)) + (n))
|
||||
|
||||
|
||||
static ngx_inline uint16_t
|
||||
ngx_rtmp_r16(uint16_t n)
|
||||
{
|
||||
return (n << 8) | (n >> 8);
|
||||
}
|
||||
|
||||
|
||||
static ngx_inline uint32_t
|
||||
ngx_rtmp_r32(uint32_t n)
|
||||
{
|
||||
return (n << 24) | ((n << 8) & 0xff0000) | ((n >> 8) & 0xff00) | (n >> 24);
|
||||
}
|
||||
|
||||
|
||||
static ngx_inline uint64_t
|
||||
ngx_rtmp_r64(uint64_t n)
|
||||
{
|
||||
return (uint64_t) ngx_rtmp_r32((uint32_t) n) << 32 |
|
||||
ngx_rtmp_r32((uint32_t) (n >> 32));
|
||||
}
|
||||
|
||||
|
||||
/* Receiving messages */
|
||||
ngx_int_t ngx_rtmp_receive_message(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
ngx_int_t ngx_rtmp_protocol_message_handler(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
ngx_int_t ngx_rtmp_user_message_handler(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
ngx_int_t ngx_rtmp_amf0_message_handler(ngx_rtmp_session_t *s,
|
||||
ngx_int_t ngx_rtmp_aggregate_message_handler(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
ngx_int_t ngx_rtmp_amf_message_handler(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
ngx_int_t ngx_rtmp_amf_shared_object_handler(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
|
||||
|
||||
/* Shared output buffers */
|
||||
|
||||
/* Store refcount in negative bytes of shared buffer */
|
||||
|
||||
#define NGX_RTMP_REFCOUNT_TYPE uint32_t
|
||||
#define NGX_RTMP_REFCOUNT_BYTES sizeof(NGX_RTMP_REFCOUNT_TYPE)
|
||||
|
||||
#define ngx_rtmp_ref(b) \
|
||||
*((NGX_RTMP_REFCOUNT_TYPE*)(b) - 1)
|
||||
|
||||
#define ngx_rtmp_ref_set(b, v) \
|
||||
ngx_rtmp_ref(b) = v
|
||||
|
||||
#define ngx_rtmp_ref_get(b) \
|
||||
++ngx_rtmp_ref(b)
|
||||
|
||||
#define ngx_rtmp_ref_put(b) \
|
||||
--ngx_rtmp_ref(b)
|
||||
|
||||
ngx_chain_t * ngx_rtmp_alloc_shared_buf(ngx_rtmp_core_srv_conf_t *cscf);
|
||||
void ngx_rtmp_free_shared_bufs(ngx_rtmp_core_srv_conf_t *cscf,
|
||||
ngx_chain_t *out);
|
||||
void ngx_rtmp_free_shared_buf(ngx_rtmp_core_srv_conf_t *cscf,
|
||||
ngx_buf_t *b);
|
||||
void ngx_rtmp_acquire_shared_buf(ngx_buf_t *b);
|
||||
ngx_chain_t * ngx_rtmp_append_shared_bufs(ngx_rtmp_core_srv_conf_t *cscf,
|
||||
void ngx_rtmp_free_shared_chain(ngx_rtmp_core_srv_conf_t *cscf,
|
||||
ngx_chain_t *in);
|
||||
ngx_chain_t * ngx_rtmp_append_shared_bufs(ngx_rtmp_core_srv_conf_t *cscf,
|
||||
ngx_chain_t *head, ngx_chain_t *in);
|
||||
|
||||
#define ngx_rtmp_acquire_shared_chain(in) \
|
||||
ngx_rtmp_ref_get(in); \
|
||||
|
||||
|
||||
/* Sending messages */
|
||||
void ngx_rtmp_prepare_message(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
void ngx_rtmp_prepare_message(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_rtmp_header_t *lh, ngx_chain_t *out);
|
||||
ngx_int_t ngx_rtmp_send_message(ngx_rtmp_session_t *s, ngx_chain_t *out,
|
||||
ngx_uint_t priority);
|
||||
|
@ -321,49 +500,132 @@ ngx_int_t ngx_rtmp_send_message(ngx_rtmp_session_t *s, ngx_chain_t *out,
|
|||
* the bigger value the lower the priority.
|
||||
* priority=0 is the highest */
|
||||
|
||||
|
||||
#define NGX_RTMP_LIMIT_SOFT 0
|
||||
#define NGX_RTMP_LIMIT_HARD 1
|
||||
#define NGX_RTMP_LIMIT_DYNAMIC 2
|
||||
|
||||
/* Protocol control messages */
|
||||
ngx_int_t ngx_rtmp_send_chunk_size(ngx_rtmp_session_t *s,
|
||||
ngx_chain_t * ngx_rtmp_create_chunk_size(ngx_rtmp_session_t *s,
|
||||
uint32_t chunk_size);
|
||||
ngx_int_t ngx_rtmp_send_abort(ngx_rtmp_session_t *s,
|
||||
ngx_chain_t * ngx_rtmp_create_abort(ngx_rtmp_session_t *s,
|
||||
uint32_t csid);
|
||||
ngx_int_t ngx_rtmp_send_ack(ngx_rtmp_session_t *s,
|
||||
ngx_chain_t * ngx_rtmp_create_ack(ngx_rtmp_session_t *s,
|
||||
uint32_t seq);
|
||||
ngx_int_t ngx_rtmp_send_ack_size(ngx_rtmp_session_t *s,
|
||||
ngx_chain_t * ngx_rtmp_create_ack_size(ngx_rtmp_session_t *s,
|
||||
uint32_t ack_size);
|
||||
ngx_int_t ngx_rtmp_send_bandwidth(ngx_rtmp_session_t *s,
|
||||
ngx_chain_t * ngx_rtmp_create_bandwidth(ngx_rtmp_session_t *s,
|
||||
uint32_t ack_size, uint8_t limit_type);
|
||||
|
||||
ngx_int_t ngx_rtmp_send_chunk_size(ngx_rtmp_session_t *s,
|
||||
uint32_t chunk_size);
|
||||
ngx_int_t ngx_rtmp_send_abort(ngx_rtmp_session_t *s,
|
||||
uint32_t csid);
|
||||
ngx_int_t ngx_rtmp_send_ack(ngx_rtmp_session_t *s,
|
||||
uint32_t seq);
|
||||
ngx_int_t ngx_rtmp_send_ack_size(ngx_rtmp_session_t *s,
|
||||
uint32_t ack_size);
|
||||
ngx_int_t ngx_rtmp_send_bandwidth(ngx_rtmp_session_t *s,
|
||||
uint32_t ack_size, uint8_t limit_type);
|
||||
|
||||
/* User control messages */
|
||||
ngx_int_t ngx_rtmp_send_user_stream_begin(ngx_rtmp_session_t *s,
|
||||
ngx_chain_t * ngx_rtmp_create_stream_begin(ngx_rtmp_session_t *s,
|
||||
uint32_t msid);
|
||||
ngx_int_t ngx_rtmp_send_user_stream_eof(ngx_rtmp_session_t *s,
|
||||
ngx_chain_t * ngx_rtmp_create_stream_eof(ngx_rtmp_session_t *s,
|
||||
uint32_t msid);
|
||||
ngx_int_t ngx_rtmp_send_user_stream_dry(ngx_rtmp_session_t *s,
|
||||
ngx_chain_t * ngx_rtmp_create_stream_dry(ngx_rtmp_session_t *s,
|
||||
uint32_t msid);
|
||||
ngx_int_t ngx_rtmp_send_user_set_buflen(ngx_rtmp_session_t *s,
|
||||
ngx_chain_t * ngx_rtmp_create_set_buflen(ngx_rtmp_session_t *s,
|
||||
uint32_t msid, uint32_t buflen_msec);
|
||||
ngx_int_t ngx_rtmp_send_user_recorded(ngx_rtmp_session_t *s,
|
||||
ngx_chain_t * ngx_rtmp_create_recorded(ngx_rtmp_session_t *s,
|
||||
uint32_t msid);
|
||||
ngx_int_t ngx_rtmp_send_user_ping_request(ngx_rtmp_session_t *s,
|
||||
ngx_chain_t * ngx_rtmp_create_ping_request(ngx_rtmp_session_t *s,
|
||||
uint32_t timestamp);
|
||||
ngx_int_t ngx_rtmp_send_user_ping_response(ngx_rtmp_session_t *s,
|
||||
uint32_t timestamp);
|
||||
ngx_int_t ngx_rtmp_send_user_unknown(ngx_rtmp_session_t *s,
|
||||
ngx_chain_t * ngx_rtmp_create_ping_response(ngx_rtmp_session_t *s,
|
||||
uint32_t timestamp);
|
||||
|
||||
/* AMF0 sender/receiver */
|
||||
ngx_int_t ngx_rtmp_send_amf0(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_rtmp_amf0_elt_t *elts, size_t nelts);
|
||||
ngx_int_t ngx_rtmp_receive_amf0(ngx_rtmp_session_t *s, ngx_chain_t *in,
|
||||
ngx_rtmp_amf0_elt_t *elts, size_t nelts);
|
||||
ngx_int_t ngx_rtmp_send_stream_begin(ngx_rtmp_session_t *s,
|
||||
uint32_t msid);
|
||||
ngx_int_t ngx_rtmp_send_stream_eof(ngx_rtmp_session_t *s,
|
||||
uint32_t msid);
|
||||
ngx_int_t ngx_rtmp_send_stream_dry(ngx_rtmp_session_t *s,
|
||||
uint32_t msid);
|
||||
ngx_int_t ngx_rtmp_send_set_buflen(ngx_rtmp_session_t *s,
|
||||
uint32_t msid, uint32_t buflen_msec);
|
||||
ngx_int_t ngx_rtmp_send_recorded(ngx_rtmp_session_t *s,
|
||||
uint32_t msid);
|
||||
ngx_int_t ngx_rtmp_send_ping_request(ngx_rtmp_session_t *s,
|
||||
uint32_t timestamp);
|
||||
ngx_int_t ngx_rtmp_send_ping_response(ngx_rtmp_session_t *s,
|
||||
uint32_t timestamp);
|
||||
|
||||
/* AMF sender/receiver */
|
||||
ngx_int_t ngx_rtmp_append_amf(ngx_rtmp_session_t *s,
|
||||
ngx_chain_t **first, ngx_chain_t **last,
|
||||
ngx_rtmp_amf_elt_t *elts, size_t nelts);
|
||||
ngx_int_t ngx_rtmp_receive_amf(ngx_rtmp_session_t *s, ngx_chain_t *in,
|
||||
ngx_rtmp_amf_elt_t *elts, size_t nelts);
|
||||
|
||||
ngx_chain_t * ngx_rtmp_create_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_rtmp_amf_elt_t *elts, size_t nelts);
|
||||
ngx_int_t ngx_rtmp_send_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_rtmp_amf_elt_t *elts, size_t nelts);
|
||||
|
||||
/* AMF status sender */
|
||||
ngx_chain_t * ngx_rtmp_create_status(ngx_rtmp_session_t *s, char *code,
|
||||
char* level, char *desc);
|
||||
ngx_chain_t * ngx_rtmp_create_play_status(ngx_rtmp_session_t *s, char *code,
|
||||
char* level, ngx_uint_t duration, ngx_uint_t bytes);
|
||||
ngx_chain_t * ngx_rtmp_create_sample_access(ngx_rtmp_session_t *s);
|
||||
|
||||
ngx_int_t ngx_rtmp_send_status(ngx_rtmp_session_t *s, char *code,
|
||||
char* level, char *desc);
|
||||
ngx_int_t ngx_rtmp_send_play_status(ngx_rtmp_session_t *s, char *code,
|
||||
char* level, ngx_uint_t duration, ngx_uint_t bytes);
|
||||
ngx_int_t ngx_rtmp_send_sample_access(ngx_rtmp_session_t *s);
|
||||
ngx_int_t ngx_rtmp_send_redirect_status(ngx_rtmp_session_t *s,
|
||||
char *callMethod, char *desc, ngx_str_t to_url);
|
||||
ngx_int_t ngx_rtmp_send_close_method(ngx_rtmp_session_t *s, char *methodName);
|
||||
ngx_int_t ngx_rtmp_send_fcpublish(ngx_rtmp_session_t *s, u_char *desc);
|
||||
ngx_int_t ngx_rtmp_send_fcunpublish(ngx_rtmp_session_t *s, u_char *desc);
|
||||
ngx_int_t ngx_rtmp_send_fi(ngx_rtmp_session_t *s);
|
||||
|
||||
|
||||
extern ngx_uint_t ngx_rtmp_max_module;
|
||||
extern ngx_module_t ngx_rtmp_core_module;
|
||||
/* Frame types */
|
||||
#define NGX_RTMP_VIDEO_KEY_FRAME 1
|
||||
#define NGX_RTMP_VIDEO_INTER_FRAME 2
|
||||
#define NGX_RTMP_VIDEO_DISPOSABLE_FRAME 3
|
||||
|
||||
|
||||
static ngx_inline ngx_int_t
|
||||
ngx_rtmp_get_video_frame_type(ngx_chain_t *in)
|
||||
{
|
||||
return (in->buf->pos[0] & 0xf0) >> 4;
|
||||
}
|
||||
|
||||
|
||||
static ngx_inline ngx_int_t
|
||||
ngx_rtmp_is_codec_header(ngx_chain_t *in)
|
||||
{
|
||||
return in->buf->pos + 1 < in->buf->last && in->buf->pos[1] == 0;
|
||||
}
|
||||
|
||||
|
||||
extern ngx_rtmp_bandwidth_t ngx_rtmp_bw_out;
|
||||
extern ngx_rtmp_bandwidth_t ngx_rtmp_bw_in;
|
||||
|
||||
|
||||
extern ngx_uint_t ngx_rtmp_naccepted;
|
||||
#if (nginx_version >= 1007011)
|
||||
extern ngx_queue_t ngx_rtmp_init_queue;
|
||||
#elif (nginx_version >= 1007005)
|
||||
extern ngx_thread_volatile ngx_queue_t ngx_rtmp_init_queue;
|
||||
#else
|
||||
extern ngx_thread_volatile ngx_event_t *ngx_rtmp_init_queue;
|
||||
#endif
|
||||
|
||||
extern ngx_uint_t ngx_rtmp_max_module;
|
||||
extern ngx_module_t ngx_rtmp_core_module;
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_H_INCLUDED_ */
|
||||
|
|
478
ngx_rtmp_access_module.c
Normal file
478
ngx_rtmp_access_module.c
Normal file
|
@ -0,0 +1,478 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
#include "ngx_rtmp_cmd_module.h"
|
||||
|
||||
|
||||
static ngx_rtmp_publish_pt next_publish;
|
||||
static ngx_rtmp_play_pt next_play;
|
||||
|
||||
|
||||
#define NGX_RTMP_ACCESS_PUBLISH 0x01
|
||||
#define NGX_RTMP_ACCESS_PLAY 0x02
|
||||
|
||||
|
||||
static char * ngx_rtmp_access_rule(ngx_conf_t *cf, ngx_command_t *cmd,
|
||||
void *conf);
|
||||
static ngx_int_t ngx_rtmp_access_postconfiguration(ngx_conf_t *cf);
|
||||
static void * ngx_rtmp_access_create_app_conf(ngx_conf_t *cf);
|
||||
static char * ngx_rtmp_access_merge_app_conf(ngx_conf_t *cf,
|
||||
void *parent, void *child);
|
||||
|
||||
|
||||
typedef struct {
|
||||
in_addr_t mask;
|
||||
in_addr_t addr;
|
||||
ngx_uint_t deny;
|
||||
ngx_uint_t flags;
|
||||
} ngx_rtmp_access_rule_t;
|
||||
|
||||
|
||||
#if (NGX_HAVE_INET6)
|
||||
|
||||
typedef struct {
|
||||
struct in6_addr addr;
|
||||
struct in6_addr mask;
|
||||
ngx_uint_t deny;
|
||||
ngx_uint_t flags;
|
||||
} ngx_rtmp_access_rule6_t;
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_array_t rules; /* array of ngx_rtmp_access_rule_t */
|
||||
#if (NGX_HAVE_INET6)
|
||||
ngx_array_t rules6; /* array of ngx_rtmp_access_rule6_t */
|
||||
#endif
|
||||
} ngx_rtmp_access_app_conf_t;
|
||||
|
||||
|
||||
static ngx_command_t ngx_rtmp_access_commands[] = {
|
||||
|
||||
{ ngx_string("allow"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE12,
|
||||
ngx_rtmp_access_rule,
|
||||
NGX_RTMP_APP_CONF_OFFSET,
|
||||
0,
|
||||
NULL },
|
||||
|
||||
{ ngx_string("deny"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE12,
|
||||
ngx_rtmp_access_rule,
|
||||
NGX_RTMP_APP_CONF_OFFSET,
|
||||
0,
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
||||
static ngx_rtmp_module_t ngx_rtmp_access_module_ctx = {
|
||||
NULL, /* preconfiguration */
|
||||
ngx_rtmp_access_postconfiguration, /* postconfiguration */
|
||||
NULL, /* create main configuration */
|
||||
NULL, /* init main configuration */
|
||||
NULL, /* create server configuration */
|
||||
NULL, /* merge server configuration */
|
||||
ngx_rtmp_access_create_app_conf, /* create app configuration */
|
||||
ngx_rtmp_access_merge_app_conf, /* merge app configuration */
|
||||
};
|
||||
|
||||
|
||||
ngx_module_t ngx_rtmp_access_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_rtmp_access_module_ctx, /* module context */
|
||||
ngx_rtmp_access_commands, /* module directives */
|
||||
NGX_RTMP_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
static void *
|
||||
ngx_rtmp_access_create_app_conf(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_rtmp_access_app_conf_t *aacf;
|
||||
|
||||
aacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_access_app_conf_t));
|
||||
if (aacf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ngx_array_init(&aacf->rules, cf->pool, 1,
|
||||
sizeof(ngx_rtmp_access_rule_t))
|
||||
!= NGX_OK)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if (NGX_HAVE_INET6)
|
||||
if (ngx_array_init(&aacf->rules6, cf->pool, 1,
|
||||
sizeof(ngx_rtmp_access_rule6_t))
|
||||
!= NGX_OK)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
return aacf;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_access_merge_rules(ngx_array_t *prev, ngx_array_t *rules)
|
||||
{
|
||||
void *p;
|
||||
|
||||
if (prev->nelts == 0) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (rules->nelts == 0) {
|
||||
*rules = *prev;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
p = ngx_array_push_n(rules, prev->nelts);
|
||||
if (p == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_memcpy(p, prev->elts, prev->size * prev->nelts);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_rtmp_access_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
|
||||
{
|
||||
ngx_rtmp_access_app_conf_t *prev = parent;
|
||||
ngx_rtmp_access_app_conf_t *conf = child;
|
||||
|
||||
if (ngx_rtmp_access_merge_rules(&prev->rules, &conf->rules) != NGX_OK) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
#if (NGX_HAVE_INET6)
|
||||
if (ngx_rtmp_access_merge_rules(&prev->rules6, &conf->rules6) != NGX_OK) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
#endif
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_access_found(ngx_rtmp_session_t *s, ngx_uint_t deny)
|
||||
{
|
||||
if (deny) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"access forbidden by rule");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_access_inet(ngx_rtmp_session_t *s, in_addr_t addr, ngx_uint_t flag)
|
||||
{
|
||||
ngx_uint_t i;
|
||||
ngx_rtmp_access_rule_t *rule;
|
||||
ngx_rtmp_access_app_conf_t *ascf;
|
||||
|
||||
ascf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_access_module);
|
||||
|
||||
rule = ascf->rules.elts;
|
||||
for (i = 0; i < ascf->rules.nelts; i++) {
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, s->connection->log, 0,
|
||||
"access: %08XD %08XD %08XD",
|
||||
addr, rule[i].mask, rule[i].addr);
|
||||
|
||||
if ((addr & rule[i].mask) == rule[i].addr && (flag & rule[i].flags)) {
|
||||
return ngx_rtmp_access_found(s, rule[i].deny);
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
#if (NGX_HAVE_INET6)
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_access_inet6(ngx_rtmp_session_t *s, u_char *p, ngx_uint_t flag)
|
||||
{
|
||||
ngx_uint_t n;
|
||||
ngx_uint_t i;
|
||||
ngx_rtmp_access_rule6_t *rule6;
|
||||
ngx_rtmp_access_app_conf_t *ascf;
|
||||
|
||||
ascf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_access_module);
|
||||
|
||||
rule6 = ascf->rules6.elts;
|
||||
for (i = 0; i < ascf->rules6.nelts; i++) {
|
||||
|
||||
#if (NGX_DEBUG)
|
||||
{
|
||||
size_t cl, ml, al;
|
||||
u_char ct[NGX_INET6_ADDRSTRLEN];
|
||||
u_char mt[NGX_INET6_ADDRSTRLEN];
|
||||
u_char at[NGX_INET6_ADDRSTRLEN];
|
||||
|
||||
cl = ngx_inet6_ntop(p, ct, NGX_INET6_ADDRSTRLEN);
|
||||
ml = ngx_inet6_ntop(rule6[i].mask.s6_addr, mt, NGX_INET6_ADDRSTRLEN);
|
||||
al = ngx_inet6_ntop(rule6[i].addr.s6_addr, at, NGX_INET6_ADDRSTRLEN);
|
||||
|
||||
ngx_log_debug6(NGX_LOG_DEBUG_HTTP, s->connection->log, 0,
|
||||
"access: %*s %*s %*s", cl, ct, ml, mt, al, at);
|
||||
}
|
||||
#endif
|
||||
|
||||
for (n = 0; n < 16; n++) {
|
||||
if ((p[n] & rule6[i].mask.s6_addr[n]) != rule6[i].addr.s6_addr[n]) {
|
||||
goto next;
|
||||
}
|
||||
}
|
||||
|
||||
if (flag & rule6[i].flags) {
|
||||
return ngx_rtmp_access_found(s, rule6[i].deny);
|
||||
}
|
||||
|
||||
next:
|
||||
continue;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_access(ngx_rtmp_session_t *s, ngx_uint_t flag)
|
||||
{
|
||||
struct sockaddr_in *sin;
|
||||
ngx_rtmp_access_app_conf_t *ascf;
|
||||
#if (NGX_HAVE_INET6)
|
||||
u_char *p;
|
||||
in_addr_t addr;
|
||||
struct sockaddr_in6 *sin6;
|
||||
#endif
|
||||
|
||||
ascf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_access_module);
|
||||
if (ascf == NULL) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, s->connection->log, 0,
|
||||
"access: NULL app conf");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/* relay etc */
|
||||
if (s->connection->sockaddr == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
switch (s->connection->sockaddr->sa_family) {
|
||||
|
||||
case AF_INET:
|
||||
sin = (struct sockaddr_in *) s->connection->sockaddr;
|
||||
return ngx_rtmp_access_inet(s, sin->sin_addr.s_addr, flag);
|
||||
|
||||
#if (NGX_HAVE_INET6)
|
||||
|
||||
case AF_INET6:
|
||||
sin6 = (struct sockaddr_in6 *) s->connection->sockaddr;
|
||||
p = sin6->sin6_addr.s6_addr;
|
||||
|
||||
if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
|
||||
addr = p[12] << 24;
|
||||
addr += p[13] << 16;
|
||||
addr += p[14] << 8;
|
||||
addr += p[15];
|
||||
return ngx_rtmp_access_inet(s, htonl(addr), flag);
|
||||
}
|
||||
|
||||
return ngx_rtmp_access_inet6(s, p, flag);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_rtmp_access_rule(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
||||
{
|
||||
ngx_rtmp_access_app_conf_t *ascf = conf;
|
||||
|
||||
ngx_int_t rc;
|
||||
ngx_uint_t all;
|
||||
ngx_str_t *value;
|
||||
ngx_cidr_t cidr;
|
||||
ngx_rtmp_access_rule_t *rule;
|
||||
#if (NGX_HAVE_INET6)
|
||||
ngx_rtmp_access_rule6_t *rule6;
|
||||
#endif
|
||||
size_t n;
|
||||
ngx_uint_t flags;
|
||||
|
||||
ngx_memzero(&cidr, sizeof(ngx_cidr_t));
|
||||
|
||||
value = cf->args->elts;
|
||||
|
||||
n = 1;
|
||||
flags = 0;
|
||||
|
||||
if (cf->args->nelts == 2) {
|
||||
|
||||
flags = NGX_RTMP_ACCESS_PUBLISH | NGX_RTMP_ACCESS_PLAY;
|
||||
|
||||
} else {
|
||||
|
||||
for(; n < cf->args->nelts - 1; ++n) {
|
||||
|
||||
if (value[n].len == sizeof("publish") - 1 &&
|
||||
ngx_strcmp(value[1].data, "publish") == 0)
|
||||
{
|
||||
flags |= NGX_RTMP_ACCESS_PUBLISH;
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
if (value[n].len == sizeof("play") - 1 &&
|
||||
ngx_strcmp(value[1].data, "play") == 0)
|
||||
{
|
||||
flags |= NGX_RTMP_ACCESS_PLAY;
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_ERR, cf->log, 0,
|
||||
"unexpected access specified: '%V'", &value[n]);
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
all = (value[n].len == 3 && ngx_strcmp(value[n].data, "all") == 0);
|
||||
|
||||
if (!all) {
|
||||
|
||||
rc = ngx_ptocidr(&value[n], &cidr);
|
||||
|
||||
if (rc == NGX_ERROR) {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"invalid parameter \"%V\"", &value[1]);
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
if (rc == NGX_DONE) {
|
||||
ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
|
||||
"low address bits of %V are meaningless",
|
||||
&value[1]);
|
||||
}
|
||||
}
|
||||
|
||||
switch (cidr.family) {
|
||||
|
||||
#if (NGX_HAVE_INET6)
|
||||
case AF_INET6:
|
||||
case 0: /* all */
|
||||
|
||||
rule6 = ngx_array_push(&ascf->rules6);
|
||||
if (rule6 == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
rule6->mask = cidr.u.in6.mask;
|
||||
rule6->addr = cidr.u.in6.addr;
|
||||
rule6->deny = (value[0].data[0] == 'd') ? 1 : 0;
|
||||
rule6->flags = flags;
|
||||
|
||||
if (!all) {
|
||||
break;
|
||||
}
|
||||
/* "all" passes through */
|
||||
#endif
|
||||
/* fall through */
|
||||
|
||||
default: /* AF_INET */
|
||||
|
||||
rule = ngx_array_push(&ascf->rules);
|
||||
if (rule == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
rule->mask = cidr.u.in.mask;
|
||||
rule->addr = cidr.u.in.addr;
|
||||
rule->deny = (value[0].data[0] == 'd') ? 1 : 0;
|
||||
rule->flags = flags;
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_access_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
|
||||
{
|
||||
if (s->auto_pushed) {
|
||||
goto next;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_access(s, NGX_RTMP_ACCESS_PUBLISH) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
next:
|
||||
return next_publish(s, v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_access_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"access: ngx_rtmp_access_play");
|
||||
|
||||
if (ngx_rtmp_access(s, NGX_RTMP_ACCESS_PLAY) != NGX_OK) {
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"access: ngx_rtmp_access_play: error");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"access: ngx_rtmp_access_play: next");
|
||||
return next_play(s, v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_access_postconfiguration(ngx_conf_t *cf)
|
||||
{
|
||||
/* chain handlers */
|
||||
next_publish = ngx_rtmp_publish;
|
||||
ngx_rtmp_publish = ngx_rtmp_access_publish;
|
||||
|
||||
next_play = ngx_rtmp_play;
|
||||
ngx_rtmp_play = ngx_rtmp_access_play;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
648
ngx_rtmp_amf.c
Normal file
648
ngx_rtmp_amf.c
Normal file
|
@ -0,0 +1,648 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp_amf.h"
|
||||
#include "ngx_rtmp.h"
|
||||
#include <string.h>
|
||||
|
||||
|
||||
static ngx_inline void*
|
||||
ngx_rtmp_amf_reverse_copy(void *dst, void* src, size_t len)
|
||||
{
|
||||
size_t k;
|
||||
|
||||
if (dst == NULL || src == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for(k = 0; k < len; ++k) {
|
||||
((u_char*)dst)[k] = ((u_char*)src)[len - 1 - k];
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
#define NGX_RTMP_AMF_DEBUG_SIZE 72
|
||||
|
||||
#ifdef NGX_DEBUG
|
||||
static void
|
||||
ngx_rtmp_amf_debug(const char* op, ngx_log_t *log, u_char *p, size_t n)
|
||||
{
|
||||
u_char hstr[3 * NGX_RTMP_AMF_DEBUG_SIZE + 1];
|
||||
u_char str[NGX_RTMP_AMF_DEBUG_SIZE + 1];
|
||||
u_char *hp, *sp;
|
||||
static u_char hex[] = "0123456789ABCDEF";
|
||||
size_t i;
|
||||
|
||||
hp = hstr;
|
||||
sp = str;
|
||||
|
||||
for(i = 0; i < n && i < NGX_RTMP_AMF_DEBUG_SIZE; ++i) {
|
||||
*hp++ = ' ';
|
||||
if (p) {
|
||||
*hp++ = hex[(*p & 0xf0) >> 4];
|
||||
*hp++ = hex[*p & 0x0f];
|
||||
*sp++ = (*p >= 0x20 && *p <= 0x7e) ?
|
||||
*p : (u_char)'?';
|
||||
++p;
|
||||
} else {
|
||||
*hp++ = 'X';
|
||||
*hp++ = 'X';
|
||||
*sp++ = '?';
|
||||
}
|
||||
}
|
||||
*hp = *sp = '\0';
|
||||
|
||||
ngx_log_debug4(NGX_LOG_DEBUG_RTMP, log, 0,
|
||||
"AMF %s (%d)%s '%s'", op, n, hstr, str);
|
||||
}
|
||||
#endif
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_amf_get(ngx_rtmp_amf_ctx_t *ctx, void *p, size_t n)
|
||||
{
|
||||
size_t size;
|
||||
ngx_chain_t *l;
|
||||
size_t offset;
|
||||
u_char *pos, *last;
|
||||
#ifdef NGX_DEBUG
|
||||
void *op = p;
|
||||
size_t on = n;
|
||||
#endif
|
||||
|
||||
if (!n)
|
||||
return NGX_OK;
|
||||
|
||||
for(l = ctx->link, offset = ctx->offset; l; l = l->next, offset = 0) {
|
||||
|
||||
pos = l->buf->pos + offset;
|
||||
last = l->buf->last;
|
||||
|
||||
if (last >= pos + n) {
|
||||
if (p) {
|
||||
p = ngx_cpymem(p, pos, n);
|
||||
}
|
||||
ctx->offset = offset + n;
|
||||
ctx->link = l;
|
||||
|
||||
#ifdef NGX_DEBUG
|
||||
ngx_rtmp_amf_debug("read", ctx->log, (u_char*)op, on);
|
||||
#endif
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
size = last - pos;
|
||||
|
||||
if (p) {
|
||||
p = ngx_cpymem(p, pos, size);
|
||||
}
|
||||
|
||||
n -= size;
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ctx->log, 0,
|
||||
"AMF read eof (%d)", n);
|
||||
|
||||
return NGX_DONE;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_amf_put(ngx_rtmp_amf_ctx_t *ctx, void *p, size_t n)
|
||||
{
|
||||
ngx_buf_t *b;
|
||||
size_t size;
|
||||
ngx_chain_t *l, *ln;
|
||||
|
||||
#ifdef NGX_DEBUG
|
||||
ngx_rtmp_amf_debug("write", ctx->log, (u_char*)p, n);
|
||||
#endif
|
||||
|
||||
l = ctx->link;
|
||||
|
||||
if (ctx->link && ctx->first == NULL) {
|
||||
ctx->first = ctx->link;
|
||||
}
|
||||
|
||||
while(n) {
|
||||
b = l ? l->buf : NULL;
|
||||
|
||||
if (b == NULL || b->last == b->end) {
|
||||
|
||||
ln = ctx->alloc(ctx->arg);
|
||||
if (ln == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ctx->first == NULL) {
|
||||
ctx->first = ln;
|
||||
}
|
||||
|
||||
if (l) {
|
||||
l->next = ln;
|
||||
}
|
||||
|
||||
l = ln;
|
||||
ctx->link = l;
|
||||
b = l->buf;
|
||||
}
|
||||
|
||||
size = b->end - b->last;
|
||||
|
||||
if (size >= n) {
|
||||
b->last = ngx_cpymem(b->last, p, n);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
b->last = ngx_cpymem(b->last, p, size);
|
||||
p = (u_char*)p + size;
|
||||
n -= size;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_amf_read_object(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts,
|
||||
size_t nelts)
|
||||
{
|
||||
uint8_t type;
|
||||
uint16_t len;
|
||||
size_t n, namelen, maxlen;
|
||||
ngx_int_t rc;
|
||||
u_char buf[2];
|
||||
|
||||
maxlen = 0;
|
||||
for(n = 0; n < nelts; ++n) {
|
||||
namelen = elts[n].name.len;
|
||||
if (namelen > maxlen)
|
||||
maxlen = namelen;
|
||||
}
|
||||
|
||||
for( ;; ) {
|
||||
|
||||
#if !(NGX_WIN32)
|
||||
char name[maxlen];
|
||||
#else
|
||||
char name[1024];
|
||||
if (maxlen > sizeof(name)) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
#endif
|
||||
/* read key */
|
||||
switch (ngx_rtmp_amf_get(ctx, buf, 2)) {
|
||||
case NGX_DONE:
|
||||
/* Envivio sends unfinalized arrays */
|
||||
return NGX_OK;
|
||||
case NGX_OK:
|
||||
break;
|
||||
default:
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_rtmp_amf_reverse_copy(&len, buf, 2);
|
||||
|
||||
if (!len)
|
||||
break;
|
||||
|
||||
if (len <= maxlen) {
|
||||
rc = ngx_rtmp_amf_get(ctx, name, len);
|
||||
|
||||
} else {
|
||||
rc = ngx_rtmp_amf_get(ctx, name, maxlen);
|
||||
if (rc != NGX_OK)
|
||||
return NGX_ERROR;
|
||||
rc = ngx_rtmp_amf_get(ctx, 0, len - maxlen);
|
||||
}
|
||||
|
||||
if (rc != NGX_OK)
|
||||
return NGX_ERROR;
|
||||
|
||||
/* TODO: if we require array to be sorted on name
|
||||
* then we could be able to use binary search */
|
||||
for(n = 0; n < nelts
|
||||
&& (len != elts[n].name.len
|
||||
|| ngx_strncmp(name, elts[n].name.data, len));
|
||||
++n);
|
||||
|
||||
if (ngx_rtmp_amf_read(ctx, n < nelts ? &elts[n] : NULL, 1) != NGX_OK)
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_amf_get(ctx, &type, 1) != NGX_OK
|
||||
|| type != NGX_RTMP_AMF_END)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_amf_read_array(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts,
|
||||
size_t nelts)
|
||||
{
|
||||
uint32_t len;
|
||||
size_t n;
|
||||
u_char buf[4];
|
||||
|
||||
/* read length */
|
||||
if (ngx_rtmp_amf_get(ctx, buf, 4) != NGX_OK)
|
||||
return NGX_ERROR;
|
||||
|
||||
ngx_rtmp_amf_reverse_copy(&len, buf, 4);
|
||||
|
||||
for (n = 0; n < len; ++n) {
|
||||
if (ngx_rtmp_amf_read(ctx, n < nelts ? &elts[n] : NULL, 1) != NGX_OK)
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_amf_read_variant(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts,
|
||||
size_t nelts)
|
||||
{
|
||||
uint8_t type;
|
||||
ngx_int_t rc;
|
||||
size_t n;
|
||||
ngx_rtmp_amf_elt_t elt;
|
||||
|
||||
rc = ngx_rtmp_amf_get(ctx, &type, 1);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
ngx_memzero(&elt, sizeof(elt));
|
||||
for (n = 0; n < nelts; ++n, ++elts) {
|
||||
if (type == elts->type) {
|
||||
elt.data = elts->data;
|
||||
elt.len = elts->len;
|
||||
}
|
||||
}
|
||||
|
||||
elt.type = type | NGX_RTMP_AMF_TYPELESS;
|
||||
|
||||
return ngx_rtmp_amf_read(ctx, &elt, 1);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_amf_is_compatible_type(uint8_t t1, uint8_t t2)
|
||||
{
|
||||
return t1 == t2
|
||||
|| (t1 == NGX_RTMP_AMF_OBJECT && t2 == NGX_RTMP_AMF_MIXED_ARRAY)
|
||||
|| (t2 == NGX_RTMP_AMF_OBJECT && t1 == NGX_RTMP_AMF_MIXED_ARRAY);
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_amf_read(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts,
|
||||
size_t nelts)
|
||||
{
|
||||
void *data;
|
||||
ngx_int_t type;
|
||||
uint8_t type8;
|
||||
size_t n;
|
||||
uint16_t len;
|
||||
ngx_int_t rc;
|
||||
u_char buf[8];
|
||||
uint32_t max_index;
|
||||
|
||||
for(n = 0; n < nelts; ++n) {
|
||||
|
||||
if (elts && elts->type & NGX_RTMP_AMF_TYPELESS) {
|
||||
type = elts->type & ~NGX_RTMP_AMF_TYPELESS;
|
||||
data = elts->data;
|
||||
|
||||
} else {
|
||||
switch (ngx_rtmp_amf_get(ctx, &type8, 1)) {
|
||||
case NGX_DONE:
|
||||
if (elts && elts->type & NGX_RTMP_AMF_OPTIONAL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
/* fall through */
|
||||
case NGX_ERROR:
|
||||
return NGX_ERROR;
|
||||
}
|
||||
type = type8;
|
||||
data = (elts &&
|
||||
ngx_rtmp_amf_is_compatible_type(
|
||||
(uint8_t) (elts->type & 0xff), (uint8_t) type))
|
||||
? elts->data
|
||||
: NULL;
|
||||
|
||||
if (elts && (elts->type & NGX_RTMP_AMF_CONTEXT)) {
|
||||
if (data) {
|
||||
*(ngx_rtmp_amf_ctx_t *) data = *ctx;
|
||||
}
|
||||
data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case NGX_RTMP_AMF_NUMBER:
|
||||
if (ngx_rtmp_amf_get(ctx, buf, 8) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
ngx_rtmp_amf_reverse_copy(data, buf, 8);
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF_BOOLEAN:
|
||||
if (ngx_rtmp_amf_get(ctx, data, 1) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF_STRING:
|
||||
if (ngx_rtmp_amf_get(ctx, buf, 2) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
ngx_rtmp_amf_reverse_copy(&len, buf, 2);
|
||||
|
||||
if (data == NULL) {
|
||||
rc = ngx_rtmp_amf_get(ctx, data, len);
|
||||
|
||||
} else if (elts->len <= len) {
|
||||
rc = ngx_rtmp_amf_get(ctx, data, elts->len - 1);
|
||||
if (rc != NGX_OK)
|
||||
return NGX_ERROR;
|
||||
((char*)data)[elts->len - 1] = 0;
|
||||
rc = ngx_rtmp_amf_get(ctx, NULL, len - elts->len + 1);
|
||||
|
||||
} else {
|
||||
rc = ngx_rtmp_amf_get(ctx, data, len);
|
||||
((char*)data)[len] = 0;
|
||||
}
|
||||
|
||||
if (rc != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF_NULL:
|
||||
case NGX_RTMP_AMF_ARRAY_NULL:
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF_MIXED_ARRAY:
|
||||
if (ngx_rtmp_amf_get(ctx, &max_index, 4) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
/* fall through */
|
||||
|
||||
case NGX_RTMP_AMF_OBJECT:
|
||||
if (ngx_rtmp_amf_read_object(ctx, data,
|
||||
data && elts ? elts->len / sizeof(ngx_rtmp_amf_elt_t) : 0
|
||||
) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF_ARRAY:
|
||||
if (ngx_rtmp_amf_read_array(ctx, data,
|
||||
data && elts ? elts->len / sizeof(ngx_rtmp_amf_elt_t) : 0
|
||||
) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF_VARIANT_:
|
||||
if (ngx_rtmp_amf_read_variant(ctx, data,
|
||||
data && elts ? elts->len / sizeof(ngx_rtmp_amf_elt_t) : 0
|
||||
) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF_INT8:
|
||||
if (ngx_rtmp_amf_get(ctx, data, 1) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF_INT16:
|
||||
if (ngx_rtmp_amf_get(ctx, buf, 2) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
ngx_rtmp_amf_reverse_copy(data, buf, 2);
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF_INT32:
|
||||
if (ngx_rtmp_amf_get(ctx, buf, 4) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
ngx_rtmp_amf_reverse_copy(data, buf, 4);
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF_END:
|
||||
return NGX_OK;
|
||||
|
||||
default:
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (elts) {
|
||||
++elts;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_amf_write_object(ngx_rtmp_amf_ctx_t *ctx,
|
||||
ngx_rtmp_amf_elt_t *elts, size_t nelts)
|
||||
{
|
||||
uint16_t len;
|
||||
size_t n;
|
||||
u_char buf[2];
|
||||
|
||||
for(n = 0; n < nelts; ++n) {
|
||||
|
||||
len = (uint16_t) elts[n].name.len;
|
||||
|
||||
if (ngx_rtmp_amf_put(ctx,
|
||||
ngx_rtmp_amf_reverse_copy(buf,
|
||||
&len, 2), 2) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_amf_put(ctx, elts[n].name.data, len) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_amf_write(ctx, &elts[n], 1) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (ngx_rtmp_amf_put(ctx, "\0\0", 2) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_amf_write_array(ngx_rtmp_amf_ctx_t *ctx,
|
||||
ngx_rtmp_amf_elt_t *elts, size_t nelts)
|
||||
{
|
||||
uint32_t len;
|
||||
size_t n;
|
||||
u_char buf[4];
|
||||
|
||||
len = nelts;
|
||||
if (ngx_rtmp_amf_put(ctx,
|
||||
ngx_rtmp_amf_reverse_copy(buf,
|
||||
&len, 4), 4) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
for(n = 0; n < nelts; ++n) {
|
||||
if (ngx_rtmp_amf_write(ctx, &elts[n], 1) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_amf_write(ngx_rtmp_amf_ctx_t *ctx,
|
||||
ngx_rtmp_amf_elt_t *elts, size_t nelts)
|
||||
{
|
||||
size_t n;
|
||||
ngx_int_t type;
|
||||
uint8_t type8;
|
||||
void *data;
|
||||
uint16_t len;
|
||||
uint32_t max_index;
|
||||
u_char buf[8];
|
||||
|
||||
for(n = 0; n < nelts; ++n) {
|
||||
|
||||
type = elts[n].type;
|
||||
data = elts[n].data;
|
||||
len = (uint16_t) elts[n].len;
|
||||
|
||||
if (type & NGX_RTMP_AMF_TYPELESS) {
|
||||
type &= ~NGX_RTMP_AMF_TYPELESS;
|
||||
} else {
|
||||
type8 = (uint8_t)type;
|
||||
if (ngx_rtmp_amf_put(ctx, &type8, 1) != NGX_OK)
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
switch(type) {
|
||||
case NGX_RTMP_AMF_NUMBER:
|
||||
if (ngx_rtmp_amf_put(ctx,
|
||||
ngx_rtmp_amf_reverse_copy(buf,
|
||||
data, 8), 8) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF_BOOLEAN:
|
||||
if (ngx_rtmp_amf_put(ctx, data, 1) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF_STRING:
|
||||
if (len == 0 && data) {
|
||||
len = (uint16_t) ngx_strlen((u_char*) data);
|
||||
}
|
||||
|
||||
if (ngx_rtmp_amf_put(ctx,
|
||||
ngx_rtmp_amf_reverse_copy(buf,
|
||||
&len, 2), 2) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_amf_put(ctx, data, len) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF_NULL:
|
||||
case NGX_RTMP_AMF_ARRAY_NULL:
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF_MIXED_ARRAY:
|
||||
max_index = 0;
|
||||
if (ngx_rtmp_amf_put(ctx, &max_index, 4) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
/* fall through */
|
||||
|
||||
case NGX_RTMP_AMF_OBJECT:
|
||||
type8 = NGX_RTMP_AMF_END;
|
||||
if (ngx_rtmp_amf_write_object(ctx, data,
|
||||
elts[n].len / sizeof(ngx_rtmp_amf_elt_t)) != NGX_OK
|
||||
|| ngx_rtmp_amf_put(ctx, &type8, 1) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF_ARRAY:
|
||||
if (ngx_rtmp_amf_write_array(ctx, data,
|
||||
elts[n].len / sizeof(ngx_rtmp_amf_elt_t)) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF_INT8:
|
||||
if (ngx_rtmp_amf_put(ctx, data, 1) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF_INT16:
|
||||
if (ngx_rtmp_amf_put(ctx,
|
||||
ngx_rtmp_amf_reverse_copy(buf,
|
||||
data, 2), 2) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF_INT32:
|
||||
if (ngx_rtmp_amf_put(ctx,
|
||||
ngx_rtmp_amf_reverse_copy(buf,
|
||||
data, 4), 4) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
71
ngx_rtmp_amf.h
Normal file
71
ngx_rtmp_amf.h
Normal file
|
@ -0,0 +1,71 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_AMF_H_INCLUDED_
|
||||
#define _NGX_RTMP_AMF_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
|
||||
|
||||
/* basic types */
|
||||
#define NGX_RTMP_AMF_NUMBER 0x00
|
||||
#define NGX_RTMP_AMF_BOOLEAN 0x01
|
||||
#define NGX_RTMP_AMF_STRING 0x02
|
||||
#define NGX_RTMP_AMF_OBJECT 0x03
|
||||
#define NGX_RTMP_AMF_NULL 0x05
|
||||
#define NGX_RTMP_AMF_ARRAY_NULL 0x06
|
||||
#define NGX_RTMP_AMF_MIXED_ARRAY 0x08
|
||||
#define NGX_RTMP_AMF_END 0x09
|
||||
#define NGX_RTMP_AMF_ARRAY 0x0a
|
||||
|
||||
/* extended types */
|
||||
#define NGX_RTMP_AMF_INT8 0x0100
|
||||
#define NGX_RTMP_AMF_INT16 0x0101
|
||||
#define NGX_RTMP_AMF_INT32 0x0102
|
||||
#define NGX_RTMP_AMF_VARIANT_ 0x0103
|
||||
|
||||
/* r/w flags */
|
||||
#define NGX_RTMP_AMF_OPTIONAL 0x1000
|
||||
#define NGX_RTMP_AMF_TYPELESS 0x2000
|
||||
#define NGX_RTMP_AMF_CONTEXT 0x4000
|
||||
|
||||
#define NGX_RTMP_AMF_VARIANT (NGX_RTMP_AMF_VARIANT_\
|
||||
|NGX_RTMP_AMF_TYPELESS)
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_int_t type;
|
||||
ngx_str_t name;
|
||||
void *data;
|
||||
size_t len;
|
||||
} ngx_rtmp_amf_elt_t;
|
||||
|
||||
|
||||
typedef ngx_chain_t * (*ngx_rtmp_amf_alloc_pt)(void *arg);
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_chain_t *link, *first;
|
||||
size_t offset;
|
||||
ngx_rtmp_amf_alloc_pt alloc;
|
||||
void *arg;
|
||||
ngx_log_t *log;
|
||||
} ngx_rtmp_amf_ctx_t;
|
||||
|
||||
|
||||
/* reading AMF */
|
||||
ngx_int_t ngx_rtmp_amf_read(ngx_rtmp_amf_ctx_t *ctx,
|
||||
ngx_rtmp_amf_elt_t *elts, size_t nelts);
|
||||
|
||||
/* writing AMF */
|
||||
ngx_int_t ngx_rtmp_amf_write(ngx_rtmp_amf_ctx_t *ctx,
|
||||
ngx_rtmp_amf_elt_t *elts, size_t nelts);
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_AMF_H_INCLUDED_ */
|
||||
|
455
ngx_rtmp_amf0.c
455
ngx_rtmp_amf0.c
|
@ -1,455 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012 Roman Arutyunyan
|
||||
*/
|
||||
|
||||
#include "ngx_rtmp_amf0.h"
|
||||
#include "ngx_rtmp.h"
|
||||
#include <string.h>
|
||||
|
||||
static inline void*
|
||||
ngx_rtmp_amf0_reverse_copy(void *dst, void* src, size_t len)
|
||||
{
|
||||
size_t k;
|
||||
|
||||
if (dst == NULL || src == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for(k = 0; k < len; ++k) {
|
||||
((u_char*)dst)[k] = ((u_char*)src)[len - 1 - k];
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
#define NGX_RTMP_AMF0_DEBUG_SIZE 16
|
||||
|
||||
#ifdef NGX_DEBUG
|
||||
static void
|
||||
ngx_rtmp_amf0_debug(const char* op, ngx_log_t *log, u_char *p, size_t n)
|
||||
{
|
||||
u_char hstr[3 * NGX_RTMP_AMF0_DEBUG_SIZE + 1];
|
||||
u_char str[NGX_RTMP_AMF0_DEBUG_SIZE + 1];
|
||||
u_char *hp, *sp;
|
||||
static u_char hex[] = "0123456789ABCDEF";
|
||||
size_t i;
|
||||
|
||||
hp = hstr;
|
||||
sp = str;
|
||||
|
||||
for(i = 0; i < n && i < NGX_RTMP_AMF0_DEBUG_SIZE; ++i) {
|
||||
*hp++ = ' ';
|
||||
if (p) {
|
||||
*hp++ = hex[(*p & 0xf0) >> 4];
|
||||
*hp++ = hex[*p & 0x0f];
|
||||
*sp++ = (*p >= 0x20 && *p <= 0x7e) ?
|
||||
*p : (u_char)'?';
|
||||
++p;
|
||||
} else {
|
||||
*hp++ = 'X';
|
||||
*hp++ = 'X';
|
||||
*sp++ = '?';
|
||||
}
|
||||
}
|
||||
*hp = *sp = '\0';
|
||||
|
||||
ngx_log_debug4(NGX_LOG_DEBUG_RTMP, log, 0,
|
||||
"AMF0 %s (%d)%s '%s'", op, n, hstr, str);
|
||||
}
|
||||
#endif
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_amf0_get(ngx_rtmp_amf0_ctx_t *ctx, void *p, size_t n)
|
||||
{
|
||||
ngx_buf_t *b;
|
||||
size_t size;
|
||||
ngx_chain_t *l;
|
||||
#ifdef NGX_DEBUG
|
||||
void *op = p;
|
||||
size_t on = n;
|
||||
#endif
|
||||
|
||||
if (!n)
|
||||
return NGX_OK;
|
||||
|
||||
for(l = ctx->link; l; l = l->next) {
|
||||
|
||||
b = l->buf;
|
||||
|
||||
if (b->last >= n + b->pos) {
|
||||
if (p) {
|
||||
p = ngx_cpymem(p, b->pos, n);
|
||||
}
|
||||
b->pos += n;
|
||||
|
||||
ctx->link = l;
|
||||
|
||||
#ifdef NGX_DEBUG
|
||||
ngx_rtmp_amf0_debug("read", ctx->log, (u_char*)op, on);
|
||||
#endif
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
size = b->last - b->pos;
|
||||
|
||||
if (p) {
|
||||
p = ngx_cpymem(p, b->pos, size);
|
||||
}
|
||||
|
||||
n -= size;
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ctx->log, 0,
|
||||
"AMF0 read eof (%d)", n);
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_amf0_put(ngx_rtmp_amf0_ctx_t *ctx, void *p, size_t n)
|
||||
{
|
||||
ngx_buf_t *b;
|
||||
size_t size;
|
||||
ngx_chain_t *l, *ln;
|
||||
|
||||
#ifdef NGX_DEBUG
|
||||
ngx_rtmp_amf0_debug("write", ctx->log, (u_char*)p, n);
|
||||
#endif
|
||||
|
||||
l = ctx->link;
|
||||
|
||||
while(n) {
|
||||
b = l ? l->buf : NULL;
|
||||
|
||||
if (b == NULL || b->last == b->end) {
|
||||
|
||||
ln = ctx->alloc(ctx->cscf);
|
||||
if (ln == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (l == NULL) {
|
||||
l = ln;
|
||||
ctx->first = l;
|
||||
} else {
|
||||
l->next = ln;
|
||||
l = ln;
|
||||
}
|
||||
|
||||
ctx->link = l;
|
||||
b = l->buf;
|
||||
}
|
||||
|
||||
size = b->end - b->last;
|
||||
|
||||
if (size >= n) {
|
||||
b->last = ngx_cpymem(b->last, p, n);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
b->last = ngx_cpymem(b->last, p, size);
|
||||
p = (u_char*)p + size;
|
||||
n -= size;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_amf0_read_object(ngx_rtmp_amf0_ctx_t *ctx, ngx_rtmp_amf0_elt_t *elts,
|
||||
size_t nelts)
|
||||
{
|
||||
uint8_t type;
|
||||
uint16_t len;
|
||||
size_t n, namelen, maxlen;
|
||||
ngx_int_t rc;
|
||||
u_char buf[2];
|
||||
|
||||
maxlen = 0;
|
||||
for(n = 0; n < nelts; ++n) {
|
||||
namelen = strlen(elts[n].name);
|
||||
if (namelen > maxlen)
|
||||
maxlen = namelen;
|
||||
}
|
||||
|
||||
for(;;) {
|
||||
|
||||
char name[maxlen + 1];
|
||||
|
||||
/* read key */
|
||||
if (ngx_rtmp_amf0_get(ctx, buf, 2) != NGX_OK)
|
||||
return NGX_ERROR;
|
||||
|
||||
ngx_rtmp_amf0_reverse_copy(&len, buf, 2);
|
||||
|
||||
if (!len)
|
||||
break;
|
||||
|
||||
if (len <= maxlen) {
|
||||
rc = ngx_rtmp_amf0_get(ctx, name, len);
|
||||
name[len] = 0;
|
||||
|
||||
} else {
|
||||
rc = ngx_rtmp_amf0_get(ctx, name, maxlen);
|
||||
if (rc != NGX_OK)
|
||||
return NGX_ERROR;
|
||||
name[maxlen] = 0;
|
||||
rc = ngx_rtmp_amf0_get(ctx, 0, len - maxlen);
|
||||
}
|
||||
|
||||
if (rc != NGX_OK)
|
||||
return NGX_ERROR;
|
||||
|
||||
/* TODO: if we require array to be sorted on name
|
||||
* then we could be able to use binary search */
|
||||
for(n = 0; n < nelts && strcmp(name, elts[n].name); ++n);
|
||||
|
||||
if (ngx_rtmp_amf0_read(ctx, n < nelts ? &elts[n] : NULL, 1) != NGX_OK)
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_amf0_get(ctx, &type, 1) != NGX_OK
|
||||
|| type != NGX_RTMP_AMF0_END)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
#define NGX_RTMP_AMF0_TILL_END_FLAG ((size_t)1 << (sizeof(size_t) * 8 - 1))
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_amf0_read(ngx_rtmp_amf0_ctx_t *ctx, ngx_rtmp_amf0_elt_t *elts,
|
||||
size_t nelts)
|
||||
{
|
||||
void *data;
|
||||
uint8_t type;
|
||||
size_t n;
|
||||
uint16_t len;
|
||||
ngx_int_t rc;
|
||||
int till_end;
|
||||
u_char buf[8];
|
||||
|
||||
if (nelts & NGX_RTMP_AMF0_TILL_END_FLAG) {
|
||||
till_end = 1;
|
||||
nelts = nelts & ~NGX_RTMP_AMF0_TILL_END_FLAG;
|
||||
} else {
|
||||
till_end = 0;
|
||||
}
|
||||
|
||||
for(n = 0; till_end || n < nelts; ++n) {
|
||||
|
||||
if (ngx_rtmp_amf0_get(ctx, &type, sizeof(type)) != NGX_OK)
|
||||
return NGX_ERROR;
|
||||
|
||||
data = (n >= nelts || elts == NULL || elts->type != type)
|
||||
? NULL
|
||||
: elts->data;
|
||||
|
||||
switch(type) {
|
||||
case NGX_RTMP_AMF0_NUMBER:
|
||||
if (ngx_rtmp_amf0_get(ctx, buf, 8) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
ngx_rtmp_amf0_reverse_copy(data, buf, 8);
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF0_BOOLEAN:
|
||||
if (ngx_rtmp_amf0_get(ctx, data, 1) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF0_STRING:
|
||||
if (ngx_rtmp_amf0_get(ctx, buf, 2) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
ngx_rtmp_amf0_reverse_copy(&len, buf, 2);
|
||||
|
||||
if (data == NULL) {
|
||||
rc = ngx_rtmp_amf0_get(ctx, data, len);
|
||||
|
||||
} else if (elts->len <= len) {
|
||||
rc = ngx_rtmp_amf0_get(ctx, data, elts->len - 1);
|
||||
if (rc != NGX_OK)
|
||||
return NGX_ERROR;
|
||||
((char*)data)[elts->len - 1] = 0;
|
||||
rc = ngx_rtmp_amf0_get(ctx, NULL, len - elts->len + 1);
|
||||
|
||||
} else {
|
||||
rc = ngx_rtmp_amf0_get(ctx, data, len);
|
||||
((char*)data)[len] = 0;
|
||||
}
|
||||
|
||||
if (rc != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF0_NULL:
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF0_OBJECT:
|
||||
if (ngx_rtmp_amf0_read_object(ctx, data,
|
||||
elts ? elts->len / sizeof(ngx_rtmp_amf0_elt_t) : 0
|
||||
) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF0_ARRAY:
|
||||
if (ngx_rtmp_amf0_read(ctx, data,
|
||||
elts ? (elts->len / sizeof(ngx_rtmp_amf0_elt_t))
|
||||
| NGX_RTMP_AMF0_TILL_END_FLAG : 0
|
||||
) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF0_END:
|
||||
return NGX_OK;
|
||||
|
||||
default:
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (elts) {
|
||||
++elts;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_amf0_write_object(ngx_rtmp_amf0_ctx_t *ctx,
|
||||
ngx_rtmp_amf0_elt_t *elts, size_t nelts)
|
||||
{
|
||||
uint16_t len, len_sb;
|
||||
size_t n;
|
||||
char *name;
|
||||
u_char buf[2];
|
||||
|
||||
for(n = 0; n < nelts; ++n) {
|
||||
|
||||
name = elts[n].name;
|
||||
len_sb = len = strlen(name);
|
||||
|
||||
if (ngx_rtmp_amf0_put(ctx,
|
||||
ngx_rtmp_amf0_reverse_copy(buf,
|
||||
&len, 2), 2) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_amf0_put(ctx, name, len) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_amf0_write(ctx, &elts[n], 1) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
len = 0;
|
||||
|
||||
if (ngx_rtmp_amf0_put(ctx, "\00\00", 2) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_amf0_write(ngx_rtmp_amf0_ctx_t *ctx,
|
||||
ngx_rtmp_amf0_elt_t *elts, size_t nelts)
|
||||
{
|
||||
size_t n;
|
||||
uint8_t type;
|
||||
void *data;
|
||||
uint16_t len;
|
||||
u_char buf[8];
|
||||
|
||||
for(n = 0; n < nelts; ++n) {
|
||||
|
||||
type = elts[n].type;
|
||||
data = elts[n].data;
|
||||
len = elts[n].len;
|
||||
|
||||
if (ngx_rtmp_amf0_put(ctx, &type, sizeof(type)) != NGX_OK)
|
||||
return NGX_ERROR;
|
||||
|
||||
switch(type) {
|
||||
case NGX_RTMP_AMF0_NUMBER:
|
||||
if (ngx_rtmp_amf0_put(ctx,
|
||||
ngx_rtmp_amf0_reverse_copy(buf,
|
||||
data, 8), 8) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF0_BOOLEAN:
|
||||
if (ngx_rtmp_amf0_put(ctx, data, 1) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF0_STRING:
|
||||
if (ngx_rtmp_amf0_put(ctx,
|
||||
ngx_rtmp_amf0_reverse_copy(buf,
|
||||
&len, 2), 2) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_amf0_put(ctx, data, len) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF0_NULL:
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF0_OBJECT:
|
||||
type = NGX_RTMP_AMF0_END;
|
||||
if (ngx_rtmp_amf0_write_object(ctx, data,
|
||||
elts[n].len / sizeof(ngx_rtmp_amf0_elt_t)) != NGX_OK
|
||||
|| ngx_rtmp_amf0_put(ctx, &type,
|
||||
sizeof(type)) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF0_ARRAY:
|
||||
type = NGX_RTMP_AMF0_END;
|
||||
if (ngx_rtmp_amf0_write(ctx, data,
|
||||
elts[n].len / sizeof(ngx_rtmp_amf0_elt_t)) != NGX_OK
|
||||
|| ngx_rtmp_amf0_put(ctx, &type,
|
||||
sizeof(type)) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_AMF0_END:
|
||||
return NGX_OK;
|
||||
|
||||
default:
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012 Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_AMF0_H_INCLUDED_
|
||||
#define _NGX_RTMP_AMF0_H_INCLUDED_
|
||||
|
||||
#define NGX_RTMP_AMF0_NUMBER 0x00
|
||||
#define NGX_RTMP_AMF0_BOOLEAN 0x01
|
||||
#define NGX_RTMP_AMF0_STRING 0x02
|
||||
|
||||
#define NGX_RTMP_AMF0_OBJECT 0x03
|
||||
#define NGX_RTMP_AMF0_NULL 0x05
|
||||
#define NGX_RTMP_AMF0_ARRAY 0x08
|
||||
#define NGX_RTMP_AMF0_END 0x09
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_int_t type;
|
||||
char *name;
|
||||
void *data;
|
||||
size_t len;
|
||||
} ngx_rtmp_amf0_elt_t;
|
||||
|
||||
|
||||
struct ngx_rtmp_core_srv_conf_s;
|
||||
|
||||
typedef ngx_chain_t * (*ngx_rtmp_amf0_alloc_pt)(struct ngx_rtmp_core_srv_conf_s
|
||||
*cscf);
|
||||
|
||||
typedef struct {
|
||||
ngx_chain_t *link, *first;
|
||||
ngx_rtmp_amf0_alloc_pt alloc;
|
||||
struct ngx_rtmp_core_srv_conf_s *cscf;
|
||||
ngx_log_t *log;
|
||||
} ngx_rtmp_amf0_ctx_t;
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* Examples:
|
||||
|
||||
struct {
|
||||
char name[32];
|
||||
double trans_id;
|
||||
char app[32];
|
||||
char flashver[32];
|
||||
char v1[8];
|
||||
int locked;
|
||||
} vals;
|
||||
|
||||
ngx_rtmp_amf0_elt_t props[] = {
|
||||
{ NGX_RTMP_AMF0_STRING, "app", vals.app, sizeof(vals.app) },
|
||||
{ NGX_RTMP_AMF0_STRING, "flashver", vals.flashver, sizeof(vals.flashver) }
|
||||
};
|
||||
|
||||
ngx_rtmp_amf0_elt_t list[] = {
|
||||
{ NGX_RTMP_AMF0_STRING, 0, vals.v1, sizeof(vals.v1) },
|
||||
{ NGX_RTMP_AMF0_BOOLEAN, 0, &vals.locked, sizeof(vals.locked) }
|
||||
};
|
||||
|
||||
ngx_rtmp_amf0_elt elts[] = {
|
||||
{ NGX_RTMP_AMF0_STRING, 0 vals.name, sizeof(vals.name) },
|
||||
{ NGX_RTMP_AMF0_NUMBER, 0 &vals.trans_id, sizeof(vals.trans_id) },
|
||||
{ NGX_RTMP_AMF0_OBJECT, 0, props, sizeof(props) },
|
||||
{ NGX_RTMP_AMF0_ARRAY, 0, list, sizeof(list) },
|
||||
{ NGX_RTMP_AMF0_NULL }
|
||||
};
|
||||
|
||||
|
||||
Reading:
|
||||
-------
|
||||
|
||||
memset(&vals, 0, sizeof(vals));
|
||||
ngx_rtmp_amf0_read(l, elts, sizeof(elts));
|
||||
|
||||
|
||||
Writing:
|
||||
-------
|
||||
|
||||
ngx_rtmp_amf0_write(l, free, elts, sizeof(elts));
|
||||
|
||||
*/
|
||||
|
||||
/* reading AMF0 */
|
||||
ngx_int_t ngx_rtmp_amf0_read(ngx_rtmp_amf0_ctx_t *ctx,
|
||||
ngx_rtmp_amf0_elt_t *elts, size_t nelts);
|
||||
|
||||
/* writing AMF0 */
|
||||
ngx_int_t ngx_rtmp_amf0_write(ngx_rtmp_amf0_ctx_t *ctx,
|
||||
ngx_rtmp_amf0_elt_t *elts, size_t nelts);
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_AMF0_H_INCLUDED_ */
|
||||
|
578
ngx_rtmp_auto_push_module.c
Normal file
578
ngx_rtmp_auto_push_module.c
Normal file
|
@ -0,0 +1,578 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp_cmd_module.h"
|
||||
#include "ngx_rtmp_relay_module.h"
|
||||
|
||||
|
||||
static ngx_rtmp_publish_pt next_publish;
|
||||
static ngx_rtmp_delete_stream_pt next_delete_stream;
|
||||
|
||||
|
||||
static ngx_int_t ngx_rtmp_auto_push_init_process(ngx_cycle_t *cycle);
|
||||
static void ngx_rtmp_auto_push_exit_process(ngx_cycle_t *cycle);
|
||||
static void * ngx_rtmp_auto_push_create_conf(ngx_cycle_t *cf);
|
||||
static char * ngx_rtmp_auto_push_init_conf(ngx_cycle_t *cycle, void *conf);
|
||||
#if (NGX_HAVE_UNIX_DOMAIN)
|
||||
static ngx_int_t ngx_rtmp_auto_push_publish(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_publish_t *v);
|
||||
static ngx_int_t ngx_rtmp_auto_push_delete_stream(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_delete_stream_t *v);
|
||||
#endif
|
||||
|
||||
|
||||
typedef struct ngx_rtmp_auto_push_ctx_s ngx_rtmp_auto_push_ctx_t;
|
||||
|
||||
struct ngx_rtmp_auto_push_ctx_s {
|
||||
ngx_int_t *slots; /* NGX_MAX_PROCESSES */
|
||||
u_char name[NGX_RTMP_MAX_NAME];
|
||||
u_char args[NGX_RTMP_MAX_ARGS];
|
||||
ngx_event_t push_evt;
|
||||
};
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_flag_t auto_push;
|
||||
ngx_str_t socket_dir;
|
||||
ngx_msec_t push_reconnect;
|
||||
} ngx_rtmp_auto_push_conf_t;
|
||||
|
||||
|
||||
static ngx_command_t ngx_rtmp_auto_push_commands[] = {
|
||||
|
||||
{ ngx_string("rtmp_auto_push"),
|
||||
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_flag_slot,
|
||||
0,
|
||||
offsetof(ngx_rtmp_auto_push_conf_t, auto_push),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("rtmp_auto_push_reconnect"),
|
||||
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_msec_slot,
|
||||
0,
|
||||
offsetof(ngx_rtmp_auto_push_conf_t, push_reconnect),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("rtmp_socket_dir"),
|
||||
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_str_slot,
|
||||
0,
|
||||
offsetof(ngx_rtmp_auto_push_conf_t, socket_dir),
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
||||
static ngx_core_module_t ngx_rtmp_auto_push_module_ctx = {
|
||||
ngx_string("rtmp_auto_push"),
|
||||
ngx_rtmp_auto_push_create_conf, /* create conf */
|
||||
ngx_rtmp_auto_push_init_conf /* init conf */
|
||||
};
|
||||
|
||||
|
||||
ngx_module_t ngx_rtmp_auto_push_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_rtmp_auto_push_module_ctx, /* module context */
|
||||
ngx_rtmp_auto_push_commands, /* module directives */
|
||||
NGX_CORE_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
ngx_rtmp_auto_push_init_process, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
ngx_rtmp_auto_push_exit_process, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
static ngx_rtmp_module_t ngx_rtmp_auto_push_index_module_ctx = {
|
||||
NULL, /* preconfiguration */
|
||||
NULL, /* postconfiguration */
|
||||
NULL, /* create main configuration */
|
||||
NULL, /* init main configuration */
|
||||
NULL, /* create server configuration */
|
||||
NULL, /* merge server configuration */
|
||||
NULL, /* create app configuration */
|
||||
NULL /* merge app configuration */
|
||||
};
|
||||
|
||||
|
||||
ngx_module_t ngx_rtmp_auto_push_index_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_rtmp_auto_push_index_module_ctx, /* module context */
|
||||
NULL, /* module directives */
|
||||
NGX_RTMP_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
#define NGX_RTMP_AUTO_PUSH_SOCKNAME "nginx-rtmp"
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_auto_push_init_process(ngx_cycle_t *cycle)
|
||||
{
|
||||
#if (NGX_HAVE_UNIX_DOMAIN)
|
||||
ngx_rtmp_auto_push_conf_t *apcf;
|
||||
ngx_listening_t *ls, *lss;
|
||||
struct sockaddr_un *saun;
|
||||
int reuseaddr;
|
||||
ngx_socket_t s;
|
||||
size_t n;
|
||||
ngx_file_info_t fi;
|
||||
|
||||
if (ngx_process != NGX_PROCESS_WORKER) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(cycle->conf_ctx,
|
||||
ngx_rtmp_auto_push_module);
|
||||
if (apcf->auto_push == 0) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
next_publish = ngx_rtmp_publish;
|
||||
ngx_rtmp_publish = ngx_rtmp_auto_push_publish;
|
||||
|
||||
next_delete_stream = ngx_rtmp_delete_stream;
|
||||
ngx_rtmp_delete_stream = ngx_rtmp_auto_push_delete_stream;
|
||||
|
||||
reuseaddr = 1;
|
||||
s = (ngx_socket_t) -1;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, cycle->log, 0,
|
||||
"auto_push: creating sockets");
|
||||
|
||||
/*TODO: clone all RTMP listenings? */
|
||||
ls = cycle->listening.elts;
|
||||
lss = NULL;
|
||||
for (n = 0; n < cycle->listening.nelts; ++n, ++ls) {
|
||||
if (ls->handler == ngx_rtmp_init_connection) {
|
||||
lss = ls;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lss == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ls = ngx_array_push(&cycle->listening);
|
||||
if (ls == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
*ls = *lss;
|
||||
|
||||
/* Disable unix socket client address extraction
|
||||
* from accept call
|
||||
* Nginx generates bad addr_text with this enabled */
|
||||
ls->addr_ntop = 0;
|
||||
|
||||
ls->socklen = sizeof(struct sockaddr_un);
|
||||
saun = ngx_pcalloc(cycle->pool, ls->socklen);
|
||||
ls->sockaddr = (struct sockaddr *) saun;
|
||||
if (ls->sockaddr == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
saun->sun_family = AF_UNIX;
|
||||
*ngx_snprintf((u_char *) saun->sun_path, sizeof(saun->sun_path),
|
||||
"%V/" NGX_RTMP_AUTO_PUSH_SOCKNAME ".%i",
|
||||
&apcf->socket_dir, ngx_process_slot)
|
||||
= 0;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, cycle->log, 0,
|
||||
"auto_push: create socket '%s'",
|
||||
saun->sun_path);
|
||||
|
||||
if (ngx_file_info(saun->sun_path, &fi) != ENOENT) {
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, cycle->log, 0,
|
||||
"auto_push: delete existing socket '%s'",
|
||||
saun->sun_path);
|
||||
ngx_delete_file(saun->sun_path);
|
||||
}
|
||||
|
||||
ngx_str_set(&ls->addr_text, "worker_socket");
|
||||
|
||||
s = ngx_socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (s == -1) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
|
||||
ngx_socket_n " worker_socket failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
|
||||
(const void *) &reuseaddr, sizeof(int))
|
||||
== -1)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
|
||||
"setsockopt(SO_REUSEADDR) worker_socket failed");
|
||||
goto sock_error;
|
||||
}
|
||||
|
||||
if (!(ngx_event_flags & NGX_USE_AIO_EVENT)) {
|
||||
if (ngx_nonblocking(s) == -1) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
|
||||
ngx_nonblocking_n " worker_socket failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (bind(s, (struct sockaddr *) saun, sizeof(*saun)) == -1) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
|
||||
ngx_nonblocking_n " worker_socket bind failed");
|
||||
goto sock_error;
|
||||
}
|
||||
|
||||
if (listen(s, NGX_LISTEN_BACKLOG) == -1) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
|
||||
"listen() to worker_socket, backlog %d failed",
|
||||
NGX_LISTEN_BACKLOG);
|
||||
goto sock_error;
|
||||
}
|
||||
|
||||
ls->fd = s;
|
||||
ls->listen = 1;
|
||||
|
||||
return NGX_OK;
|
||||
|
||||
sock_error:
|
||||
if (s != (ngx_socket_t) -1 && ngx_close_socket(s) == -1) {
|
||||
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
|
||||
ngx_close_socket_n " worker_socket failed");
|
||||
}
|
||||
ngx_delete_file(saun->sun_path);
|
||||
|
||||
return NGX_ERROR;
|
||||
|
||||
#else /* NGX_HAVE_UNIX_DOMAIN */
|
||||
|
||||
return NGX_OK;
|
||||
|
||||
#endif /* NGX_HAVE_UNIX_DOMAIN */
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_auto_push_exit_process(ngx_cycle_t *cycle)
|
||||
{
|
||||
#if (NGX_HAVE_UNIX_DOMAIN)
|
||||
ngx_rtmp_auto_push_conf_t *apcf;
|
||||
u_char path[NGX_MAX_PATH];
|
||||
|
||||
apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(cycle->conf_ctx,
|
||||
ngx_rtmp_auto_push_module);
|
||||
if (apcf->auto_push == 0) {
|
||||
return;
|
||||
}
|
||||
*ngx_snprintf(path, sizeof(path),
|
||||
"%V/" NGX_RTMP_AUTO_PUSH_SOCKNAME ".%i",
|
||||
&apcf->socket_dir, ngx_process_slot)
|
||||
= 0;
|
||||
|
||||
ngx_delete_file(path);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
ngx_rtmp_auto_push_create_conf(ngx_cycle_t *cycle)
|
||||
{
|
||||
ngx_rtmp_auto_push_conf_t *apcf;
|
||||
|
||||
apcf = ngx_pcalloc(cycle->pool, sizeof(ngx_rtmp_auto_push_conf_t));
|
||||
if (apcf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
apcf->auto_push = NGX_CONF_UNSET;
|
||||
apcf->push_reconnect = NGX_CONF_UNSET_MSEC;
|
||||
|
||||
return apcf;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_rtmp_auto_push_init_conf(ngx_cycle_t *cycle, void *conf)
|
||||
{
|
||||
ngx_rtmp_auto_push_conf_t *apcf = conf;
|
||||
|
||||
ngx_conf_init_value(apcf->auto_push, 0);
|
||||
ngx_conf_init_msec_value(apcf->push_reconnect, 100);
|
||||
|
||||
if (apcf->socket_dir.len == 0) {
|
||||
ngx_str_set(&apcf->socket_dir, "/tmp");
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
#if (NGX_HAVE_UNIX_DOMAIN)
|
||||
static void
|
||||
ngx_rtmp_auto_push_reconnect(ngx_event_t *ev)
|
||||
{
|
||||
ngx_rtmp_session_t *s = ev->data;
|
||||
|
||||
ngx_rtmp_auto_push_conf_t *apcf;
|
||||
ngx_rtmp_auto_push_ctx_t *ctx;
|
||||
ngx_int_t *slot;
|
||||
ngx_int_t n;
|
||||
ngx_rtmp_relay_target_t at;
|
||||
u_char path[sizeof("unix:") + NGX_MAX_PATH];
|
||||
u_char flash_ver[sizeof("APSH ,") +
|
||||
NGX_INT_T_LEN * 2];
|
||||
u_char play_path[NGX_RTMP_MAX_NAME];
|
||||
ngx_str_t name;
|
||||
u_char *p;
|
||||
ngx_str_t *u;
|
||||
ngx_pid_t pid;
|
||||
ngx_int_t npushed;
|
||||
ngx_core_conf_t *ccf;
|
||||
ngx_file_info_t fi;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"auto_push: reconnect");
|
||||
|
||||
apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
|
||||
ngx_rtmp_auto_push_module);
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_index_module);
|
||||
if (ctx == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
name.data = ctx->name;
|
||||
name.len = ngx_strlen(name.data);
|
||||
|
||||
ngx_memzero(&at, sizeof(at));
|
||||
ngx_str_set(&at.page_url, "nginx-auto-push");
|
||||
at.tag = &ngx_rtmp_auto_push_module;
|
||||
|
||||
if (ctx->args[0]) {
|
||||
at.play_path.data = play_path;
|
||||
at.play_path.len = ngx_snprintf(play_path, sizeof(play_path),
|
||||
"%s?%s", ctx->name, ctx->args) -
|
||||
play_path;
|
||||
}
|
||||
|
||||
slot = ctx->slots;
|
||||
npushed = 0;
|
||||
|
||||
for (n = 0; n < NGX_MAX_PROCESSES; ++n, ++slot) {
|
||||
if (n == ngx_process_slot) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pid = ngx_processes[n].pid;
|
||||
if (pid == 0 || pid == NGX_INVALID_PID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (*slot) {
|
||||
npushed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
at.data = &ngx_processes[n];
|
||||
|
||||
ngx_memzero(&at.url, sizeof(at.url));
|
||||
u = &at.url.url;
|
||||
p = ngx_snprintf(path, sizeof(path) - 1,
|
||||
"unix:%V/" NGX_RTMP_AUTO_PUSH_SOCKNAME ".%i",
|
||||
&apcf->socket_dir, n);
|
||||
*p = 0;
|
||||
|
||||
if (ngx_file_info(path + sizeof("unix:") - 1, &fi) != NGX_OK) {
|
||||
ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"auto_push: " ngx_file_info_n " failed: "
|
||||
"slot=%i pid=%P socket='%s'" "url='%V' name='%s'",
|
||||
n, pid, path, u, ctx->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
u->data = path;
|
||||
u->len = p - path;
|
||||
if (ngx_parse_url(s->connection->pool, &at.url) != NGX_OK) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"auto_push: auto-push parse_url failed "
|
||||
"url='%V' name='%s'",
|
||||
u, ctx->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
p = ngx_snprintf(flash_ver, sizeof(flash_ver) - 1, "APSH %i,%i",
|
||||
(ngx_int_t) ngx_process_slot, (ngx_int_t) ngx_pid);
|
||||
at.flash_ver.data = flash_ver;
|
||||
at.flash_ver.len = p - flash_ver;
|
||||
|
||||
ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"auto_push: connect slot=%i pid=%P socket='%s' name='%s'",
|
||||
n, pid, path, ctx->name);
|
||||
|
||||
if (ngx_rtmp_relay_push(s, &name, &at) == NGX_OK) {
|
||||
*slot = 1;
|
||||
npushed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"auto_push: connect failed: slot=%i pid=%P socket='%s'"
|
||||
"url='%V' name='%s'",
|
||||
n, pid, path, u, ctx->name);
|
||||
}
|
||||
|
||||
ccf = (ngx_core_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
|
||||
ngx_core_module);
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"auto_push: pushed=%i total=%i failed=%i",
|
||||
npushed, ccf->worker_processes,
|
||||
ccf->worker_processes - 1 - npushed);
|
||||
|
||||
if (ccf->worker_processes == npushed + 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* several workers failed */
|
||||
|
||||
slot = ctx->slots;
|
||||
|
||||
for (n = 0; n < NGX_MAX_PROCESSES; ++n, ++slot) {
|
||||
pid = ngx_processes[n].pid;
|
||||
|
||||
if (n == ngx_process_slot || *slot == 1 ||
|
||||
pid == 0 || pid == NGX_INVALID_PID)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"auto_push: connect failed: slot=%i pid=%P name='%s'",
|
||||
n, pid, ctx->name);
|
||||
}
|
||||
|
||||
if (!ctx->push_evt.timer_set) {
|
||||
ngx_add_timer(&ctx->push_evt, apcf->push_reconnect);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_auto_push_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
|
||||
{
|
||||
ngx_rtmp_auto_push_conf_t *apcf;
|
||||
ngx_rtmp_auto_push_ctx_t *ctx;
|
||||
|
||||
if (s->auto_pushed || (s->relay && !s->static_relay)) {
|
||||
goto next;
|
||||
}
|
||||
|
||||
apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
|
||||
ngx_rtmp_auto_push_module);
|
||||
if (apcf->auto_push == 0) {
|
||||
goto next;
|
||||
}
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_index_module);
|
||||
if (ctx == NULL) {
|
||||
ctx = ngx_palloc(s->connection->pool,
|
||||
sizeof(ngx_rtmp_auto_push_ctx_t));
|
||||
if (ctx == NULL) {
|
||||
goto next;
|
||||
}
|
||||
ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_auto_push_index_module);
|
||||
|
||||
}
|
||||
ngx_memzero(ctx, sizeof(*ctx));
|
||||
|
||||
ctx->push_evt.data = s;
|
||||
ctx->push_evt.log = s->connection->log;
|
||||
ctx->push_evt.handler = ngx_rtmp_auto_push_reconnect;
|
||||
|
||||
ctx->slots = ngx_pcalloc(s->connection->pool,
|
||||
sizeof(ngx_int_t) * NGX_MAX_PROCESSES);
|
||||
if (ctx->slots == NULL) {
|
||||
goto next;
|
||||
}
|
||||
|
||||
ngx_memcpy(ctx->name, v->name, sizeof(ctx->name));
|
||||
ngx_memcpy(ctx->args, v->args, sizeof(ctx->args));
|
||||
|
||||
ngx_rtmp_auto_push_reconnect(&ctx->push_evt);
|
||||
|
||||
next:
|
||||
return next_publish(s, v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_auto_push_delete_stream(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_delete_stream_t *v)
|
||||
{
|
||||
ngx_rtmp_auto_push_conf_t *apcf;
|
||||
ngx_rtmp_auto_push_ctx_t *ctx, *pctx;
|
||||
ngx_rtmp_relay_ctx_t *rctx;
|
||||
ngx_int_t slot;
|
||||
|
||||
apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
|
||||
ngx_rtmp_auto_push_module);
|
||||
if (apcf->auto_push == 0) {
|
||||
goto next;
|
||||
}
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_index_module);
|
||||
if (ctx) {
|
||||
if (ctx->push_evt.timer_set) {
|
||||
ngx_del_timer(&ctx->push_evt);
|
||||
}
|
||||
goto next;
|
||||
}
|
||||
|
||||
/* skip non-relays & publishers */
|
||||
rctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
|
||||
if (rctx == NULL ||
|
||||
rctx->tag != &ngx_rtmp_auto_push_module ||
|
||||
rctx->publish == NULL)
|
||||
{
|
||||
goto next;
|
||||
}
|
||||
|
||||
slot = (ngx_process_t *) rctx->data - &ngx_processes[0];
|
||||
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"auto_push: disconnect slot=%i app='%V' name='%V'",
|
||||
slot, &rctx->app, &rctx->name);
|
||||
|
||||
pctx = ngx_rtmp_get_module_ctx(rctx->publish->session,
|
||||
ngx_rtmp_auto_push_index_module);
|
||||
if (pctx == NULL) {
|
||||
goto next;
|
||||
}
|
||||
|
||||
pctx->slots[slot] = 0;
|
||||
|
||||
/* push reconnect */
|
||||
if (!pctx->push_evt.timer_set) {
|
||||
ngx_add_timer(&pctx->push_evt, apcf->push_reconnect);
|
||||
}
|
||||
|
||||
next:
|
||||
return next_delete_stream(s, v);
|
||||
}
|
||||
#endif /* NGX_HAVE_UNIX_DOMAIN */
|
26
ngx_rtmp_bandwidth.c
Normal file
26
ngx_rtmp_bandwidth.c
Normal file
|
@ -0,0 +1,26 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp_bandwidth.h"
|
||||
|
||||
|
||||
void
|
||||
ngx_rtmp_update_bandwidth(ngx_rtmp_bandwidth_t *bw, uint32_t bytes)
|
||||
{
|
||||
if (ngx_cached_time->sec > bw->intl_end) {
|
||||
bw->bandwidth = ngx_cached_time->sec >
|
||||
bw->intl_end + NGX_RTMP_BANDWIDTH_INTERVAL
|
||||
? 0
|
||||
: bw->intl_bytes / NGX_RTMP_BANDWIDTH_INTERVAL;
|
||||
bw->intl_bytes = 0;
|
||||
bw->intl_end = ngx_cached_time->sec + NGX_RTMP_BANDWIDTH_INTERVAL;
|
||||
}
|
||||
|
||||
bw->bytes += bytes;
|
||||
bw->intl_bytes += bytes;
|
||||
}
|
31
ngx_rtmp_bandwidth.h
Normal file
31
ngx_rtmp_bandwidth.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_BANDWIDTH_H_INCLUDED_
|
||||
#define _NGX_RTMP_BANDWIDTH_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
|
||||
|
||||
/* Bandwidth update interval in seconds */
|
||||
#define NGX_RTMP_BANDWIDTH_INTERVAL 10
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint64_t bytes;
|
||||
uint64_t bandwidth; /* bytes/sec */
|
||||
|
||||
time_t intl_end;
|
||||
uint64_t intl_bytes;
|
||||
} ngx_rtmp_bandwidth_t;
|
||||
|
||||
|
||||
void ngx_rtmp_update_bandwidth(ngx_rtmp_bandwidth_t *bw, uint32_t bytes);
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_BANDWIDTH_H_INCLUDED_ */
|
63
ngx_rtmp_bitop.c
Normal file
63
ngx_rtmp_bitop.c
Normal file
|
@ -0,0 +1,63 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp_bitop.h"
|
||||
|
||||
|
||||
void
|
||||
ngx_rtmp_bit_init_reader(ngx_rtmp_bit_reader_t *br, u_char *pos, u_char *last)
|
||||
{
|
||||
ngx_memzero(br, sizeof(ngx_rtmp_bit_reader_t));
|
||||
|
||||
br->pos = pos;
|
||||
br->last = last;
|
||||
}
|
||||
|
||||
|
||||
uint64_t
|
||||
ngx_rtmp_bit_read(ngx_rtmp_bit_reader_t *br, ngx_uint_t n)
|
||||
{
|
||||
uint64_t v;
|
||||
ngx_uint_t d;
|
||||
|
||||
v = 0;
|
||||
|
||||
while (n) {
|
||||
|
||||
if (br->pos >= br->last) {
|
||||
br->err = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
d = (br->offs + n > 8 ? (ngx_uint_t) (8 - br->offs) : n);
|
||||
|
||||
v <<= d;
|
||||
v += (*br->pos >> (8 - br->offs - d)) & ((u_char) 0xff >> (8 - d));
|
||||
|
||||
br->offs += d;
|
||||
n -= d;
|
||||
|
||||
if (br->offs == 8) {
|
||||
br->pos++;
|
||||
br->offs = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
uint64_t
|
||||
ngx_rtmp_bit_read_golomb(ngx_rtmp_bit_reader_t *br)
|
||||
{
|
||||
ngx_uint_t n;
|
||||
|
||||
for (n = 0; ngx_rtmp_bit_read(br, 1) == 0 && !br->err; n++);
|
||||
|
||||
return ((uint64_t) 1 << n) + ngx_rtmp_bit_read(br, n) - 1;
|
||||
}
|
46
ngx_rtmp_bitop.h
Normal file
46
ngx_rtmp_bitop.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_BITOP_H_INCLUDED_
|
||||
#define _NGX_RTMP_BITOP_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
u_char *pos;
|
||||
u_char *last;
|
||||
ngx_uint_t offs;
|
||||
ngx_uint_t err;
|
||||
} ngx_rtmp_bit_reader_t;
|
||||
|
||||
|
||||
void ngx_rtmp_bit_init_reader(ngx_rtmp_bit_reader_t *br, u_char *pos,
|
||||
u_char *last);
|
||||
uint64_t ngx_rtmp_bit_read(ngx_rtmp_bit_reader_t *br, ngx_uint_t n);
|
||||
uint64_t ngx_rtmp_bit_read_golomb(ngx_rtmp_bit_reader_t *br);
|
||||
|
||||
|
||||
#define ngx_rtmp_bit_read_err(br) ((br)->err)
|
||||
|
||||
#define ngx_rtmp_bit_read_eof(br) ((br)->pos == (br)->last)
|
||||
|
||||
#define ngx_rtmp_bit_read_8(br) \
|
||||
((uint8_t) ngx_rtmp_bit_read(br, 8))
|
||||
|
||||
#define ngx_rtmp_bit_read_16(br) \
|
||||
((uint16_t) ngx_rtmp_bit_read(br, 16))
|
||||
|
||||
#define ngx_rtmp_bit_read_32(br) \
|
||||
((uint32_t) ngx_rtmp_bit_read(br, 32))
|
||||
|
||||
#define ngx_rtmp_bit_read_64(br) \
|
||||
((uint64_t) ngx_rtmp_read(br, 64))
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_BITOP_H_INCLUDED_ */
|
|
@ -1,797 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012 Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
||||
|
||||
/* Standard stream ids for broadcasting */
|
||||
#define NGX_RTMP_BROADCAST_MSID 1
|
||||
#define NGX_RTMP_BROADCAST_CSID_AMF0 5
|
||||
#define NGX_RTMP_BROADCAST_CSID_AUDIO 6
|
||||
#define NGX_RTMP_BROADCAST_CSID_VIDEO 7
|
||||
|
||||
|
||||
/* Frame cutoff */
|
||||
#define NGX_RTMP_CUTOFF_ALL 0
|
||||
#define NGX_RTMP_CUTOFF_KEY 1
|
||||
#define NGX_RTMP_CUTOFF_INTER 2
|
||||
#define NGX_RTMP_CUTOFF_DISPOSABLE 3
|
||||
|
||||
|
||||
static ngx_int_t ngx_rtmp_broadcast_postconfiguration(ngx_conf_t *cf);
|
||||
static void * ngx_rtmp_broadcast_create_srv_conf(ngx_conf_t *cf);
|
||||
static char * ngx_rtmp_broadcast_merge_srv_conf(ngx_conf_t *cf,
|
||||
void *parent, void *child);
|
||||
|
||||
static ngx_int_t ngx_rtmp_broadcast_connect(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
static ngx_int_t ngx_rtmp_broadcast_create_stream(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
static ngx_int_t ngx_rtmp_broadcast_publish(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
static ngx_int_t ngx_rtmp_broadcast_play(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
static ngx_int_t ngx_rtmp_broadcast_set_data_frame(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
static ngx_int_t ngx_rtmp_broadcast_ok(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in);
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t name;
|
||||
ngx_rtmp_event_handler_pt handler;
|
||||
} ngx_rtmp_broadcast_map_t;
|
||||
|
||||
|
||||
static ngx_rtmp_broadcast_map_t ngx_rtmp_broadcast_map[] = {
|
||||
{ ngx_string("connect"), ngx_rtmp_broadcast_connect },
|
||||
{ ngx_string("createStream"), ngx_rtmp_broadcast_create_stream },
|
||||
{ ngx_string("publish"), ngx_rtmp_broadcast_publish },
|
||||
{ ngx_string("play"), ngx_rtmp_broadcast_play },
|
||||
{ ngx_string("@setDataFrame"), ngx_rtmp_broadcast_set_data_frame },
|
||||
{ ngx_string("releaseStream"), ngx_rtmp_broadcast_ok },
|
||||
{ ngx_string("FCPublish"), ngx_rtmp_broadcast_ok },
|
||||
{ ngx_string("FCSubscribe"), ngx_rtmp_broadcast_ok },
|
||||
};
|
||||
|
||||
|
||||
typedef struct {
|
||||
/* use hash-map
|
||||
* stream -> broadcast contexts */
|
||||
ngx_int_t buckets;
|
||||
struct ngx_rtmp_broadcast_ctx_s **contexts;
|
||||
} ngx_rtmp_broadcast_srv_conf_t;
|
||||
|
||||
|
||||
static ngx_command_t ngx_rtmp_broadcast_commands[] = {
|
||||
{ ngx_string("broadcast_buckets"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_str_slot,
|
||||
NGX_RTMP_SRV_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_broadcast_srv_conf_t, buckets),
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
||||
static ngx_rtmp_module_t ngx_rtmp_broadcast_module_ctx = {
|
||||
NULL, /* preconfiguration */
|
||||
ngx_rtmp_broadcast_postconfiguration, /* postconfiguration */
|
||||
NULL, /* create main configuration */
|
||||
NULL, /* init main configuration */
|
||||
ngx_rtmp_broadcast_create_srv_conf, /* create server configuration */
|
||||
ngx_rtmp_broadcast_merge_srv_conf /* merge server configuration */
|
||||
};
|
||||
|
||||
|
||||
ngx_module_t ngx_rtmp_broadcast_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_rtmp_broadcast_module_ctx, /* module context */
|
||||
ngx_rtmp_broadcast_commands, /* module directives */
|
||||
NGX_RTMP_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
#define NGX_RTMP_BROADCAST_PUBLISHER 0x01
|
||||
#define NGX_RTMP_BROADCAST_SUBSCRIBER 0x02
|
||||
#define NGX_RTMP_BROADCAST_KEYFRAME 0x04
|
||||
#define NGX_RTMP_BROADCAST_DATA_FRAME 0x08
|
||||
|
||||
|
||||
typedef struct ngx_rtmp_broadcast_ctx_s {
|
||||
ngx_str_t stream;
|
||||
ngx_rtmp_session_t *session;
|
||||
struct ngx_rtmp_broadcast_ctx_s *next;
|
||||
ngx_uint_t flags;
|
||||
uint32_t csid;
|
||||
ngx_chain_t *data_frame;
|
||||
} ngx_rtmp_broadcast_ctx_t;
|
||||
|
||||
|
||||
static void *
|
||||
ngx_rtmp_broadcast_create_srv_conf(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_rtmp_broadcast_srv_conf_t *cscf;
|
||||
|
||||
cscf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_broadcast_srv_conf_t));
|
||||
if (cscf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cscf->buckets = NGX_CONF_UNSET;
|
||||
|
||||
return cscf;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_rtmp_broadcast_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
|
||||
{
|
||||
ngx_rtmp_broadcast_srv_conf_t *prev = parent;
|
||||
ngx_rtmp_broadcast_srv_conf_t *conf = child;
|
||||
|
||||
ngx_conf_merge_value(conf->buckets, prev->buckets, 1024);
|
||||
|
||||
conf->contexts = ngx_pcalloc(cf->pool,
|
||||
sizeof(ngx_rtmp_broadcast_ctx_t *) * conf->buckets);
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_rtmp_broadcast_ctx_t **
|
||||
ngx_rtmp_broadcast_get_head(ngx_rtmp_session_t *s)
|
||||
{
|
||||
ngx_rtmp_broadcast_srv_conf_t *bscf;
|
||||
ngx_rtmp_broadcast_ctx_t *ctx;
|
||||
|
||||
bscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_broadcast_module);
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_broadcast_module);
|
||||
|
||||
return &bscf->contexts[
|
||||
ngx_hash_key(ctx->stream.data, ctx->stream.len)
|
||||
% bscf->buckets];
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_broadcast_set_flags(ngx_rtmp_session_t *s, ngx_uint_t flags)
|
||||
{
|
||||
ngx_rtmp_broadcast_ctx_t *ctx;
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_broadcast_module);
|
||||
if (ctx == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx->flags |= flags;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_broadcast_join(ngx_rtmp_session_t *s, ngx_str_t *stream,
|
||||
ngx_uint_t flags)
|
||||
{
|
||||
ngx_connection_t *c;
|
||||
ngx_rtmp_broadcast_ctx_t *ctx, **hctx;
|
||||
|
||||
c = s->connection;
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_broadcast_module);
|
||||
if (ctx == NULL) {
|
||||
ctx = ngx_pcalloc(c->pool, sizeof(ngx_rtmp_broadcast_ctx_t));
|
||||
ctx->session = s;
|
||||
ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_broadcast_module);
|
||||
}
|
||||
|
||||
if (ctx->stream.len) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, "already joined");
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0,
|
||||
"join broadcast stream '%V'", stream);
|
||||
|
||||
ctx->stream = *stream;
|
||||
hctx = ngx_rtmp_broadcast_get_head(s);
|
||||
ctx->next = *hctx;
|
||||
ctx->flags = flags;
|
||||
*hctx = ctx;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_broadcast_leave(ngx_rtmp_session_t *s)
|
||||
{
|
||||
ngx_connection_t *c;
|
||||
ngx_rtmp_broadcast_ctx_t *ctx, **hctx;
|
||||
|
||||
c = s->connection;
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_broadcast_module);
|
||||
if (ctx == NULL || !ctx->stream.len) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0,
|
||||
"leave broadcast stream '%V'", &ctx->stream);
|
||||
|
||||
hctx = ngx_rtmp_broadcast_get_head(s);
|
||||
ngx_str_null(&ctx->stream);
|
||||
|
||||
for(; *hctx; hctx = &(*hctx)->next) {
|
||||
if (*hctx == ctx) {
|
||||
*hctx = (*hctx)->next;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
#define NGX_RTMP_VIDEO_KEY_FRAME 1
|
||||
#define NGX_RTMP_VIDEO_INTER_FRAME 2
|
||||
#define NGX_RTMP_VIDEO_DISPOSABLE_FRAME 3
|
||||
#define NGX_RTMP_AUDIO_FRAME NGX_RTMP_VIDEO_KEY_FRAME
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_get_video_frame_type(ngx_chain_t *in)
|
||||
{
|
||||
return (in->buf->pos[0] & 0xf0) >> 4;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_broadcast_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
ngx_connection_t *c;
|
||||
ngx_rtmp_broadcast_ctx_t *ctx, *cctx;
|
||||
ngx_chain_t *out;
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
ngx_rtmp_header_t sh;
|
||||
ngx_rtmp_session_t *ss;
|
||||
ngx_uint_t priority;
|
||||
int keyframe;
|
||||
|
||||
c = s->connection;
|
||||
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_broadcast_module);
|
||||
|
||||
sh = *h;
|
||||
keyframe = 0;
|
||||
if (h->type == NGX_RTMP_MSG_VIDEO) {
|
||||
sh.csid = NGX_RTMP_BROADCAST_CSID_VIDEO;
|
||||
priority = ngx_rtmp_get_video_frame_type(in);
|
||||
if (priority == NGX_RTMP_VIDEO_KEY_FRAME) {
|
||||
keyframe = 1;
|
||||
}
|
||||
|
||||
} else {
|
||||
sh.csid = NGX_RTMP_BROADCAST_CSID_AUDIO;
|
||||
priority = NGX_RTMP_AUDIO_FRAME;
|
||||
}
|
||||
|
||||
if (ctx == NULL
|
||||
|| !(ctx->flags & NGX_RTMP_BROADCAST_PUBLISHER))
|
||||
{
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0,
|
||||
"received audio/video from non-publisher");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (in == NULL || in->buf == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
out = ngx_rtmp_append_shared_bufs(cscf, NULL, in);
|
||||
|
||||
ngx_rtmp_prepare_message(s, &sh, NULL, out);
|
||||
|
||||
/* broadcast to all subscribers */
|
||||
for (cctx = *ngx_rtmp_broadcast_get_head(s);
|
||||
cctx; cctx = cctx->next)
|
||||
{
|
||||
if (cctx != ctx
|
||||
&& cctx->flags & NGX_RTMP_BROADCAST_SUBSCRIBER
|
||||
&& cctx->stream.len == ctx->stream.len
|
||||
&& !ngx_strncmp(cctx->stream.data, ctx->stream.data,
|
||||
ctx->stream.len))
|
||||
{
|
||||
ss = cctx->session;
|
||||
|
||||
/* if we have metadata check if the subscriber
|
||||
* has already received one */
|
||||
if (ctx->data_frame
|
||||
&& !(cctx->flags & NGX_RTMP_BROADCAST_DATA_FRAME))
|
||||
{
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"sending data_frame");
|
||||
|
||||
switch (ngx_rtmp_send_message(ss, ctx->data_frame, 0)) {
|
||||
case NGX_OK:
|
||||
cctx->flags |= NGX_RTMP_BROADCAST_DATA_FRAME;
|
||||
break;
|
||||
case NGX_AGAIN:
|
||||
break;
|
||||
default:
|
||||
ngx_log_error(NGX_LOG_INFO, ss->connection->log, 0,
|
||||
"error sending message");
|
||||
}
|
||||
}
|
||||
|
||||
/* waiting for a keyframe? */
|
||||
if (cscf->wait_key_frame
|
||||
&& sh.type == NGX_RTMP_MSG_VIDEO
|
||||
&& !(cctx->flags & NGX_RTMP_BROADCAST_KEYFRAME)
|
||||
&& !keyframe)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_send_message(ss, out, priority) == NGX_OK) {
|
||||
if (keyframe) {
|
||||
cctx->flags |= NGX_RTMP_BROADCAST_KEYFRAME;
|
||||
}
|
||||
} else {
|
||||
ngx_log_error(NGX_LOG_INFO, ss->connection->log, 0,
|
||||
"error sending message");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngx_rtmp_free_shared_bufs(cscf, out);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_broadcast_connect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
|
||||
static double trans;
|
||||
static u_char app[1024];
|
||||
static u_char url[1024];
|
||||
static u_char acodecs[1024];
|
||||
static ngx_str_t app_str;
|
||||
static double capabilities = 31;
|
||||
static double object_enc;
|
||||
|
||||
static ngx_rtmp_amf0_elt_t in_cmd[] = {
|
||||
{ NGX_RTMP_AMF0_STRING, "app", app, sizeof(app) },
|
||||
{ NGX_RTMP_AMF0_STRING, "tcUrl" , url, sizeof(url) },
|
||||
{ NGX_RTMP_AMF0_STRING, "audiocodecs" , acodecs, sizeof(acodecs) },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf0_elt_t out_obj[] = {
|
||||
{ NGX_RTMP_AMF0_STRING, "fmsVer", "FMS/3,0,1,123" , sizeof("FMS/3,0,1,123")-1 },
|
||||
{ NGX_RTMP_AMF0_NUMBER, "capabilities", &capabilities, sizeof(capabilities) },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf0_elt_t out_inf[] = {
|
||||
{ NGX_RTMP_AMF0_STRING, "level", NULL, 0 },
|
||||
{ NGX_RTMP_AMF0_STRING, "code", NULL, 0 },
|
||||
{ NGX_RTMP_AMF0_STRING, "description", NULL, 0 },
|
||||
{ NGX_RTMP_AMF0_NUMBER, "objectEncoding", &object_enc , sizeof(object_enc) },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf0_elt_t in_elts[] = {
|
||||
{ NGX_RTMP_AMF0_NUMBER, 0, &trans, sizeof(trans) },
|
||||
{ NGX_RTMP_AMF0_OBJECT, NULL, in_cmd, sizeof(in_cmd) },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf0_elt_t out_elts[] = {
|
||||
{ NGX_RTMP_AMF0_STRING, NULL, "_result", sizeof("_result") - 1 },
|
||||
{ NGX_RTMP_AMF0_NUMBER, NULL, &trans, sizeof(trans) },
|
||||
{ NGX_RTMP_AMF0_OBJECT, NULL, out_obj, sizeof(out_obj) },
|
||||
{ NGX_RTMP_AMF0_OBJECT, NULL, out_inf, sizeof(out_inf) },
|
||||
};
|
||||
|
||||
if (ngx_rtmp_receive_amf0(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0])))
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
|
||||
|
||||
ngx_str_set(&out_inf[0], "status");
|
||||
ngx_str_set(&out_inf[1], "NetConnection.Connect.Success");
|
||||
ngx_str_set(&out_inf[2], "Connection succeeded.");
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"connect() called; app='%s' url='%s'",
|
||||
app, url);
|
||||
|
||||
/*FIXME: app_str allocation!!!!!!! */
|
||||
/*FIXME: add memsetting input data */
|
||||
/* join stream */
|
||||
ngx_str_set(&app_str, "preved");
|
||||
/*
|
||||
app_str.data = app;
|
||||
app_str.len = ngx_strlen(app);
|
||||
*/
|
||||
ngx_rtmp_broadcast_join(s, &app_str, 0);
|
||||
|
||||
return ngx_rtmp_send_ack_size(s, cscf->ack_window)
|
||||
|| ngx_rtmp_send_bandwidth(s, cscf->ack_window, NGX_RTMP_LIMIT_DYNAMIC)
|
||||
|| ngx_rtmp_send_user_stream_begin(s, 0)
|
||||
|| ngx_rtmp_send_chunk_size(s, cscf->chunk_size)
|
||||
|| ngx_rtmp_send_amf0(s, h, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0]))
|
||||
? NGX_OK
|
||||
: NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_broadcast_create_stream(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
static double trans;
|
||||
static double stream;
|
||||
|
||||
static ngx_rtmp_amf0_elt_t in_elts[] = {
|
||||
{ NGX_RTMP_AMF0_NUMBER, 0, &trans, sizeof(trans) },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf0_elt_t out_elts[] = {
|
||||
{ NGX_RTMP_AMF0_STRING, NULL, "_result", sizeof("_result") - 1 },
|
||||
{ NGX_RTMP_AMF0_NUMBER, NULL, &trans, sizeof(trans) },
|
||||
{ NGX_RTMP_AMF0_NULL , NULL, NULL, 0 },
|
||||
{ NGX_RTMP_AMF0_NUMBER, NULL, &stream, sizeof(stream) },
|
||||
};
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"createStream() called");
|
||||
|
||||
if (ngx_rtmp_receive_amf0(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0])))
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
stream = NGX_RTMP_BROADCAST_MSID;
|
||||
|
||||
return ngx_rtmp_send_amf0(s, h, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0]));
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_broadcast_publish(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
ngx_rtmp_header_t sh;
|
||||
|
||||
static double trans;
|
||||
static u_char pub_name[1024];
|
||||
static u_char pub_type[1024];
|
||||
|
||||
static ngx_rtmp_amf0_elt_t out_inf[] = {
|
||||
{ NGX_RTMP_AMF0_STRING, "code", NULL, 0 },
|
||||
{ NGX_RTMP_AMF0_STRING, "level", NULL, 0 },
|
||||
{ NGX_RTMP_AMF0_STRING, "description", NULL, 0 },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf0_elt_t in_elts[] = {
|
||||
{ NGX_RTMP_AMF0_NUMBER, 0, &trans, sizeof(trans) },
|
||||
{ NGX_RTMP_AMF0_NULL, NULL, NULL, 0 },
|
||||
{ NGX_RTMP_AMF0_STRING, NULL, pub_name, sizeof(pub_name) },
|
||||
{ NGX_RTMP_AMF0_STRING, NULL, pub_type, sizeof(pub_type) },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf0_elt_t out_elts[] = {
|
||||
{ NGX_RTMP_AMF0_STRING, NULL, "onStatus", sizeof("onStatus") - 1 },
|
||||
{ NGX_RTMP_AMF0_NUMBER, NULL, &trans, sizeof(trans) },
|
||||
{ NGX_RTMP_AMF0_NULL , NULL, NULL, 0 },
|
||||
{ NGX_RTMP_AMF0_OBJECT, NULL, out_inf, sizeof(out_inf) },
|
||||
};
|
||||
|
||||
if (ngx_rtmp_receive_amf0(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0])))
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"publish() called; pubName='%s' pubType='%s'",
|
||||
pub_name, pub_type);
|
||||
|
||||
if (ngx_rtmp_send_user_stream_begin(s,
|
||||
NGX_RTMP_BROADCAST_MSID) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_rtmp_broadcast_set_flags(s, NGX_RTMP_BROADCAST_PUBLISHER);
|
||||
|
||||
ngx_str_set(&out_inf[0], "NetStream.Publish.Start");
|
||||
ngx_str_set(&out_inf[1], "status");
|
||||
ngx_str_set(&out_inf[2], "Publish succeeded.");
|
||||
|
||||
memset(&sh, 0, sizeof(sh));
|
||||
sh.type = NGX_RTMP_MSG_AMF0_CMD;
|
||||
sh.csid = NGX_RTMP_BROADCAST_CSID_AMF0;
|
||||
sh.msid = h->msid;
|
||||
|
||||
if (ngx_rtmp_send_amf0(s, &sh, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0])) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_broadcast_play(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
ngx_rtmp_header_t sh;
|
||||
|
||||
static double trans;
|
||||
static u_char play_name[1024];
|
||||
static int bfalse;
|
||||
|
||||
static ngx_rtmp_amf0_elt_t out_inf[] = {
|
||||
{ NGX_RTMP_AMF0_STRING, "code", NULL, 0 },
|
||||
{ NGX_RTMP_AMF0_STRING, "level", NULL, 0 },
|
||||
{ NGX_RTMP_AMF0_STRING, "description", NULL, 0 },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf0_elt_t out2_inf[] = {
|
||||
{ NGX_RTMP_AMF0_STRING, "code", NULL, 0 },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf0_elt_t in_elts[] = {
|
||||
{ NGX_RTMP_AMF0_NUMBER, 0, &trans, sizeof(trans) },
|
||||
{ NGX_RTMP_AMF0_NULL, NULL, NULL, 0 },
|
||||
{ NGX_RTMP_AMF0_STRING, NULL, play_name, sizeof(play_name) },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf0_elt_t out_elts[] = {
|
||||
{ NGX_RTMP_AMF0_STRING, NULL, "onStatus", sizeof("onStatus") - 1 },
|
||||
{ NGX_RTMP_AMF0_NUMBER, NULL, &trans, sizeof(trans) },
|
||||
{ NGX_RTMP_AMF0_NULL , NULL, NULL, 0 },
|
||||
{ NGX_RTMP_AMF0_OBJECT, NULL, out_inf, sizeof(out_inf) },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf0_elt_t out2_elts[] = {
|
||||
{ NGX_RTMP_AMF0_STRING, NULL, "onStatus", sizeof("onStatus") - 1 },
|
||||
{ NGX_RTMP_AMF0_OBJECT, NULL, out2_inf, sizeof(out2_inf) },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf0_elt_t out3_elts[] = {
|
||||
{ NGX_RTMP_AMF0_STRING, NULL, "|RtmpSampleAccess", sizeof("|RtmpSampleAccess") - 1 },
|
||||
{ NGX_RTMP_AMF0_BOOLEAN, NULL, &bfalse, sizeof(bfalse) },
|
||||
};
|
||||
|
||||
|
||||
if (ngx_rtmp_receive_amf0(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0])))
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
play_name[0] = 0;
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"play() called; playame='%s'",
|
||||
play_name);
|
||||
|
||||
if (ngx_rtmp_send_user_stream_begin(s,
|
||||
NGX_RTMP_BROADCAST_MSID) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_rtmp_broadcast_set_flags(s, NGX_RTMP_BROADCAST_SUBSCRIBER);
|
||||
|
||||
memset(&sh, 0, sizeof(sh));
|
||||
sh.type = NGX_RTMP_MSG_AMF0_CMD;
|
||||
sh.csid = NGX_RTMP_BROADCAST_CSID_AMF0;
|
||||
sh.msid = h->msid;
|
||||
|
||||
ngx_str_set(&out_inf[0], "NetStream.Play.Reset");
|
||||
ngx_str_set(&out_inf[1], "status");
|
||||
ngx_str_set(&out_inf[2], "Playing and resetting.");
|
||||
|
||||
if (ngx_rtmp_send_amf0(s, &sh, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0])) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_str_set(&out_inf[0], "NetStream.Play.Start");
|
||||
ngx_str_set(&out_inf[1], "status");
|
||||
ngx_str_set(&out_inf[2], "Started playing.");
|
||||
|
||||
if (ngx_rtmp_send_amf0(s, &sh, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0])) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_str_set(&out2_inf[0], "NetStream.Data.Start");
|
||||
sh.type = NGX_RTMP_MSG_AMF0_META;
|
||||
|
||||
if (ngx_rtmp_send_amf0(s, &sh, out3_elts,
|
||||
sizeof(out3_elts) / sizeof(out3_elts[0])) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_send_amf0(s, &sh, out2_elts,
|
||||
sizeof(out2_elts) / sizeof(out2_elts[0])) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_broadcast_set_data_frame(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
ngx_connection_t *c;
|
||||
ngx_rtmp_broadcast_ctx_t *ctx;
|
||||
ngx_rtmp_amf0_ctx_t act;
|
||||
ngx_rtmp_header_t sh;
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
|
||||
static ngx_rtmp_amf0_elt_t out_elts[] = {
|
||||
{ NGX_RTMP_AMF0_STRING, NULL,
|
||||
"@setDataFrame", sizeof("@setDataFrame") - 1 },
|
||||
};
|
||||
|
||||
c = s->connection;
|
||||
|
||||
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_broadcast_module);
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, "data_frame arrived");
|
||||
|
||||
if (ctx->data_frame) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0,
|
||||
"duplicate data_frame");
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
/* create full metadata chain for output */
|
||||
memset(&act, 0, sizeof(act));
|
||||
act.cscf = cscf;
|
||||
act.alloc = ngx_rtmp_alloc_shared_buf;
|
||||
act.log = c->log;
|
||||
|
||||
if (ngx_rtmp_amf0_write(&act, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0])) != NGX_OK)
|
||||
{
|
||||
if (act.first) {
|
||||
ngx_rtmp_free_shared_bufs(cscf, act.first);
|
||||
}
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (act.first == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ctx->data_frame = act.first;
|
||||
|
||||
if (ngx_rtmp_append_shared_bufs(cscf, ctx->data_frame, in) == NULL) {
|
||||
if (ctx->data_frame) {
|
||||
ngx_rtmp_free_shared_bufs(cscf, ctx->data_frame);
|
||||
}
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
memset(&sh, 0, sizeof(sh));
|
||||
sh.csid = NGX_RTMP_BROADCAST_CSID_AMF0;
|
||||
sh.msid = h->msid;
|
||||
sh.type = h->type;
|
||||
|
||||
ngx_rtmp_prepare_message(s, h, NULL, ctx->data_frame);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_broadcast_ok(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
static double trans;
|
||||
|
||||
static ngx_rtmp_amf0_elt_t in_elts[] = {
|
||||
{ NGX_RTMP_AMF0_NUMBER, 0, &trans, sizeof(trans) },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf0_elt_t out_inf[] = {
|
||||
{ NGX_RTMP_AMF0_STRING, "code", NULL, 0 },
|
||||
{ NGX_RTMP_AMF0_STRING, "level", NULL, 0 },
|
||||
{ NGX_RTMP_AMF0_STRING, "description", NULL, 0 },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf0_elt_t out_elts[] = {
|
||||
{ NGX_RTMP_AMF0_STRING, NULL, "onStatus", sizeof("onStatus") - 1 },
|
||||
{ NGX_RTMP_AMF0_NUMBER, NULL, &trans, sizeof(trans) },
|
||||
{ NGX_RTMP_AMF0_NULL , NULL, NULL, 0 },
|
||||
{ NGX_RTMP_AMF0_OBJECT, NULL, out_inf, sizeof(out_inf) },
|
||||
};
|
||||
|
||||
if (ngx_rtmp_receive_amf0(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0])))
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return ngx_rtmp_send_amf0(s, h, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0]));
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_broadcast_postconfiguration(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_rtmp_core_main_conf_t *cmcf;
|
||||
ngx_hash_key_t *h;
|
||||
ngx_rtmp_disconnect_handler_pt *dh;
|
||||
ngx_rtmp_event_handler_pt *avh;
|
||||
ngx_rtmp_broadcast_map_t *bm;
|
||||
size_t n, ncalls;
|
||||
|
||||
cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);
|
||||
|
||||
/* register audio/video broadcast handler */
|
||||
avh = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]);
|
||||
*avh = ngx_rtmp_broadcast_av;
|
||||
|
||||
avh = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]);
|
||||
*avh = ngx_rtmp_broadcast_av;
|
||||
|
||||
/* register disconnect handler */
|
||||
dh = ngx_array_push(&cmcf->disconnect);
|
||||
|
||||
if (dh == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
*dh = ngx_rtmp_broadcast_leave;
|
||||
|
||||
/* register AMF0 call handlers */
|
||||
ncalls = sizeof(ngx_rtmp_broadcast_map)
|
||||
/ sizeof(ngx_rtmp_broadcast_map[0]);
|
||||
h = ngx_array_push_n(&cmcf->amf0, ncalls);
|
||||
if (h == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
bm = ngx_rtmp_broadcast_map;
|
||||
for(n = 0; n < ncalls; ++n, ++h, ++bm) {
|
||||
h->key = bm->name;
|
||||
h->value = bm->handler;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
870
ngx_rtmp_cmd_module.c
Normal file
870
ngx_rtmp_cmd_module.c
Normal file
|
@ -0,0 +1,870 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp_cmd_module.h"
|
||||
#include "ngx_rtmp_streams.h"
|
||||
|
||||
|
||||
#define NGX_RTMP_FMS_VERSION "FMS/3,0,1,123"
|
||||
#define NGX_RTMP_CAPABILITIES 31
|
||||
|
||||
|
||||
static ngx_int_t ngx_rtmp_cmd_connect(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_connect_t *v);
|
||||
static ngx_int_t ngx_rtmp_cmd_disconnect(ngx_rtmp_session_t *s);
|
||||
static ngx_int_t ngx_rtmp_cmd_create_stream(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_create_stream_t *v);
|
||||
static ngx_int_t ngx_rtmp_cmd_close_stream(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_close_stream_t *v);
|
||||
static ngx_int_t ngx_rtmp_cmd_delete_stream(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_delete_stream_t *v);
|
||||
static ngx_int_t ngx_rtmp_cmd_publish(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_publish_t *v);
|
||||
static ngx_int_t ngx_rtmp_cmd_play(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_play_t *v);
|
||||
static ngx_int_t ngx_rtmp_cmd_seek(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_seek_t *v);
|
||||
static ngx_int_t ngx_rtmp_cmd_pause(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_pause_t *v);
|
||||
|
||||
|
||||
static ngx_int_t ngx_rtmp_cmd_stream_begin(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_stream_begin_t *v);
|
||||
static ngx_int_t ngx_rtmp_cmd_stream_eof(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_stream_eof_t *v);
|
||||
static ngx_int_t ngx_rtmp_cmd_stream_dry(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_stream_dry_t *v);
|
||||
static ngx_int_t ngx_rtmp_cmd_recorded(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_recorded_t *v);
|
||||
static ngx_int_t ngx_rtmp_cmd_set_buflen(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_set_buflen_t *v);
|
||||
|
||||
static ngx_int_t ngx_rtmp_cmd_playlist(ngx_rtmp_session_t *s, ngx_rtmp_playlist_t *v);
|
||||
|
||||
ngx_rtmp_connect_pt ngx_rtmp_connect;
|
||||
ngx_rtmp_disconnect_pt ngx_rtmp_disconnect;
|
||||
ngx_rtmp_create_stream_pt ngx_rtmp_create_stream;
|
||||
ngx_rtmp_close_stream_pt ngx_rtmp_close_stream;
|
||||
ngx_rtmp_delete_stream_pt ngx_rtmp_delete_stream;
|
||||
ngx_rtmp_publish_pt ngx_rtmp_publish;
|
||||
ngx_rtmp_play_pt ngx_rtmp_play;
|
||||
ngx_rtmp_seek_pt ngx_rtmp_seek;
|
||||
ngx_rtmp_pause_pt ngx_rtmp_pause;
|
||||
|
||||
|
||||
ngx_rtmp_stream_begin_pt ngx_rtmp_stream_begin;
|
||||
ngx_rtmp_stream_eof_pt ngx_rtmp_stream_eof;
|
||||
ngx_rtmp_stream_dry_pt ngx_rtmp_stream_dry;
|
||||
ngx_rtmp_recorded_pt ngx_rtmp_recorded;
|
||||
ngx_rtmp_set_buflen_pt ngx_rtmp_set_buflen;
|
||||
|
||||
ngx_rtmp_playlist_pt ngx_rtmp_playlist;
|
||||
|
||||
static ngx_int_t ngx_rtmp_cmd_postconfiguration(ngx_conf_t *cf);
|
||||
|
||||
|
||||
static ngx_rtmp_module_t ngx_rtmp_cmd_module_ctx = {
|
||||
NULL, /* preconfiguration */
|
||||
ngx_rtmp_cmd_postconfiguration, /* postconfiguration */
|
||||
NULL, /* create main configuration */
|
||||
NULL, /* init main configuration */
|
||||
NULL, /* create server configuration */
|
||||
NULL, /* merge server configuration */
|
||||
NULL, /* create app configuration */
|
||||
NULL /* merge app configuration */
|
||||
};
|
||||
|
||||
|
||||
ngx_module_t ngx_rtmp_cmd_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_rtmp_cmd_module_ctx, /* module context */
|
||||
NULL, /* module directives */
|
||||
NGX_RTMP_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
void
|
||||
ngx_rtmp_cmd_fill_args(u_char name[NGX_RTMP_MAX_NAME],
|
||||
u_char args[NGX_RTMP_MAX_ARGS])
|
||||
{
|
||||
u_char *p;
|
||||
|
||||
p = (u_char *)ngx_strchr(name, '?');
|
||||
if (p == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
*p++ = 0;
|
||||
ngx_cpystrn(args, p, NGX_RTMP_MAX_ARGS);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_connect_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
size_t len;
|
||||
|
||||
static ngx_rtmp_connect_t v;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_cmd[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("app"),
|
||||
v.app, sizeof(v.app) },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("flashVer"),
|
||||
v.flashver, sizeof(v.flashver) },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("swfUrl"),
|
||||
v.swf_url, sizeof(v.swf_url) },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("tcUrl"),
|
||||
v.tc_url, sizeof(v.tc_url) },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("audioCodecs"),
|
||||
&v.acodecs, sizeof(v.acodecs) },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("videoCodecs"),
|
||||
&v.vcodecs, sizeof(v.vcodecs) },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("pageUrl"),
|
||||
v.page_url, sizeof(v.page_url) },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("objectEncoding"),
|
||||
&v.object_encoding, 0},
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&v.trans, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_OBJECT,
|
||||
ngx_null_string,
|
||||
in_cmd, sizeof(in_cmd) },
|
||||
};
|
||||
|
||||
ngx_memzero(&v, sizeof(v));
|
||||
if (ngx_rtmp_receive_amf(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0])))
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
len = ngx_strlen(v.app);
|
||||
if (len > 10 && !ngx_memcmp(v.app + len - 10, "/_definst_", 10)) {
|
||||
v.app[len - 10] = 0;
|
||||
} else if (len && v.app[len - 1] == '/') {
|
||||
v.app[len - 1] = 0;
|
||||
}
|
||||
|
||||
ngx_rtmp_cmd_fill_args(v.app, v.args);
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"connect: app='%s' args='%s' flashver='%s' swf_url='%s' "
|
||||
"tc_url='%s' page_url='%s' acodecs=%uD vcodecs=%uD "
|
||||
"object_encoding=%ui",
|
||||
v.app, v.args, v.flashver, v.swf_url, v.tc_url, v.page_url,
|
||||
(uint32_t)v.acodecs, (uint32_t)v.vcodecs,
|
||||
(ngx_int_t)v.object_encoding);
|
||||
|
||||
return ngx_rtmp_connect(s, &v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_connect(ngx_rtmp_session_t *s, ngx_rtmp_connect_t *v)
|
||||
{
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
ngx_rtmp_core_app_conf_t **cacfp;
|
||||
ngx_uint_t n;
|
||||
ngx_rtmp_header_t h;
|
||||
u_char *p;
|
||||
|
||||
static double trans;
|
||||
static double capabilities = NGX_RTMP_CAPABILITIES;
|
||||
static double object_encoding = 0;
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_obj[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("fmsVer"),
|
||||
NGX_RTMP_FMS_VERSION, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("capabilities"),
|
||||
&capabilities, 0 },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_inf[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("level"),
|
||||
"status", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("code"),
|
||||
"NetConnection.Connect.Success", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("description"),
|
||||
"Connection succeeded.", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("objectEncoding"),
|
||||
&object_encoding, 0 }
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
"_result", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&trans, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_OBJECT,
|
||||
ngx_null_string,
|
||||
out_obj, sizeof(out_obj) },
|
||||
|
||||
{ NGX_RTMP_AMF_OBJECT,
|
||||
ngx_null_string,
|
||||
out_inf, sizeof(out_inf) },
|
||||
};
|
||||
|
||||
if (s->connected) {
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"connect: duplicate connection");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
|
||||
|
||||
trans = v->trans;
|
||||
|
||||
/* fill session parameters */
|
||||
s->connected = 1;
|
||||
|
||||
ngx_memzero(&h, sizeof(h));
|
||||
h.csid = NGX_RTMP_CSID_AMF_INI;
|
||||
h.type = NGX_RTMP_MSG_AMF_CMD;
|
||||
|
||||
|
||||
#define NGX_RTMP_SET_STRPAR(name) \
|
||||
s->name.len = ngx_strlen(v->name); \
|
||||
s->name.data = ngx_palloc(s->connection->pool, s->name.len); \
|
||||
ngx_memcpy(s->name.data, v->name, s->name.len)
|
||||
|
||||
NGX_RTMP_SET_STRPAR(app);
|
||||
NGX_RTMP_SET_STRPAR(args);
|
||||
NGX_RTMP_SET_STRPAR(flashver);
|
||||
NGX_RTMP_SET_STRPAR(swf_url);
|
||||
NGX_RTMP_SET_STRPAR(tc_url);
|
||||
NGX_RTMP_SET_STRPAR(page_url);
|
||||
|
||||
#undef NGX_RTMP_SET_STRPAR
|
||||
|
||||
p = ngx_strlchr(s->app.data, s->app.data + s->app.len, '?');
|
||||
if (p) {
|
||||
s->app.len = (p - s->app.data);
|
||||
}
|
||||
|
||||
s->acodecs = (uint32_t) v->acodecs;
|
||||
s->vcodecs = (uint32_t) v->vcodecs;
|
||||
|
||||
/* find application & set app_conf */
|
||||
cacfp = cscf->applications.elts;
|
||||
for(n = 0; n < cscf->applications.nelts; ++n, ++cacfp) {
|
||||
if ((*cacfp)->name.len == s->app.len &&
|
||||
ngx_strncmp((*cacfp)->name.data, s->app.data, s->app.len) == 0)
|
||||
{
|
||||
/* found app! */
|
||||
s->app_conf = (*cacfp)->app_conf;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (s->app_conf == NULL) {
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"connect: application not found: '%V'", &s->app);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
object_encoding = v->object_encoding;
|
||||
|
||||
return ngx_rtmp_send_ack_size(s, cscf->ack_window) != NGX_OK ||
|
||||
ngx_rtmp_send_bandwidth(s, cscf->ack_window,
|
||||
NGX_RTMP_LIMIT_DYNAMIC) != NGX_OK ||
|
||||
ngx_rtmp_send_chunk_size(s, cscf->chunk_size) != NGX_OK ||
|
||||
ngx_rtmp_send_amf(s, &h, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0]))
|
||||
!= NGX_OK ? NGX_ERROR : NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_create_stream_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
static ngx_rtmp_create_stream_t v;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&v.trans, sizeof(v.trans) },
|
||||
};
|
||||
|
||||
if (ngx_rtmp_receive_amf(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0])))
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "createStream");
|
||||
|
||||
return ngx_rtmp_create_stream(s, &v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_create_stream(ngx_rtmp_session_t *s, ngx_rtmp_create_stream_t *v)
|
||||
{
|
||||
/* support one message stream per connection */
|
||||
static double stream;
|
||||
static double trans;
|
||||
ngx_rtmp_header_t h;
|
||||
|
||||
static ngx_rtmp_amf_elt_t out_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
"_result", 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&trans, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&stream, sizeof(stream) },
|
||||
};
|
||||
|
||||
trans = v->trans;
|
||||
stream = NGX_RTMP_MSID;
|
||||
|
||||
ngx_memzero(&h, sizeof(h));
|
||||
|
||||
h.csid = NGX_RTMP_CSID_AMF_INI;
|
||||
h.type = NGX_RTMP_MSG_AMF_CMD;
|
||||
|
||||
return ngx_rtmp_send_amf(s, &h, out_elts,
|
||||
sizeof(out_elts) / sizeof(out_elts[0])) == NGX_OK ?
|
||||
NGX_DONE : NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_close_stream_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
static ngx_rtmp_close_stream_t v;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&v.stream, 0 },
|
||||
};
|
||||
|
||||
if (ngx_rtmp_receive_amf(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0])))
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "closeStream");
|
||||
|
||||
return ngx_rtmp_close_stream(s, &v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_delete_stream_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
static ngx_rtmp_delete_stream_t v;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&v.stream, 0 },
|
||||
};
|
||||
|
||||
if (ngx_rtmp_receive_amf(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0])))
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return ngx_rtmp_delete_stream(s, &v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_delete_stream(ngx_rtmp_session_t *s, ngx_rtmp_delete_stream_t *v)
|
||||
{
|
||||
ngx_rtmp_close_stream_t cv;
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "deleteStream");
|
||||
|
||||
cv.stream = 0;
|
||||
|
||||
return ngx_rtmp_close_stream(s, &cv);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_publish_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
static ngx_rtmp_publish_t v;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
/* transaction is always 0 */
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
&v.name, sizeof(v.name) },
|
||||
|
||||
{ NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
&v.type, sizeof(v.type) },
|
||||
};
|
||||
|
||||
ngx_memzero(&v, sizeof(v));
|
||||
|
||||
if (ngx_rtmp_receive_amf(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0])))
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_rtmp_cmd_fill_args(v.name, v.args);
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"publish: name='%s' args='%s' type=%s silent=%d",
|
||||
v.name, v.args, v.type, v.silent);
|
||||
|
||||
return ngx_rtmp_publish(s, &v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_play_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
static ngx_rtmp_play_t v;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
/* transaction is always 0 */
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
&v.name, sizeof(v.name) },
|
||||
|
||||
{ NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&v.start, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&v.duration, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_BOOLEAN,
|
||||
ngx_null_string,
|
||||
&v.reset, 0 }
|
||||
};
|
||||
|
||||
ngx_memzero(&v, sizeof(v));
|
||||
|
||||
if (ngx_rtmp_receive_amf(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0])))
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_rtmp_cmd_fill_args(v.name, v.args);
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"play: name='%s' args='%s' start=%i duration=%i "
|
||||
"reset=%i silent=%i",
|
||||
v.name, v.args, (ngx_int_t) v.start,
|
||||
(ngx_int_t) v.duration, (ngx_int_t) v.reset,
|
||||
(ngx_int_t) v.silent);
|
||||
|
||||
return ngx_rtmp_play(s, &v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
|
||||
"cmd: ngx_rtmp_cmd_play");
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_play2_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
static ngx_rtmp_play_t v;
|
||||
static ngx_rtmp_close_stream_t vc;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_obj[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_string("start"),
|
||||
&v.start, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_string("streamName"),
|
||||
&v.name, sizeof(v.name) },
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
/* transaction is always 0 */
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_OBJECT,
|
||||
ngx_null_string,
|
||||
&in_obj, sizeof(in_obj) }
|
||||
};
|
||||
|
||||
ngx_memzero(&v, sizeof(v));
|
||||
|
||||
if (ngx_rtmp_receive_amf(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0])))
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_rtmp_cmd_fill_args(v.name, v.args);
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"play2: name='%s' args='%s' start=%i",
|
||||
v.name, v.args, (ngx_int_t) v.start);
|
||||
|
||||
/* continue from current timestamp */
|
||||
|
||||
if (v.start < 0) {
|
||||
v.start = s->current_time;
|
||||
}
|
||||
|
||||
ngx_memzero(&vc, sizeof(vc));
|
||||
|
||||
/* close_stream should be synchronous */
|
||||
ngx_rtmp_close_stream(s, &vc);
|
||||
|
||||
return ngx_rtmp_play(s, &v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_pause_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
static ngx_rtmp_pause_t v;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_BOOLEAN,
|
||||
ngx_null_string,
|
||||
&v.pause, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&v.position, 0 },
|
||||
};
|
||||
|
||||
ngx_memzero(&v, sizeof(v));
|
||||
|
||||
if (ngx_rtmp_receive_amf(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0])))
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"pause: pause=%i position=%i",
|
||||
(ngx_int_t) v.pause, (ngx_int_t) v.position);
|
||||
|
||||
return ngx_rtmp_pause(s, &v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_pause(ngx_rtmp_session_t *s, ngx_rtmp_pause_t *v)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_disconnect_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "disconnect");
|
||||
|
||||
return ngx_rtmp_disconnect(s);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_disconnect(ngx_rtmp_session_t *s)
|
||||
{
|
||||
return ngx_rtmp_delete_stream(s, NULL);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_seek_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
static ngx_rtmp_seek_t v;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
/* transaction is always 0 */
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NULL,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_NUMBER,
|
||||
ngx_null_string,
|
||||
&v.offset, sizeof(v.offset) },
|
||||
};
|
||||
|
||||
ngx_memzero(&v, sizeof(v));
|
||||
|
||||
if (ngx_rtmp_receive_amf(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0])))
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"seek: offset=%i", (ngx_int_t) v.offset);
|
||||
|
||||
return ngx_rtmp_seek(s, &v);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_seek(ngx_rtmp_session_t *s, ngx_rtmp_seek_t *v)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_stream_begin(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_eof_t *v)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_stream_dry(ngx_rtmp_session_t *s, ngx_rtmp_stream_dry_t *v)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_recorded(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_recorded_t *v)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_set_buflen(ngx_rtmp_session_t *s, ngx_rtmp_set_buflen_t *v)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_playlist(ngx_rtmp_session_t *s, ngx_rtmp_playlist_t *v)
|
||||
{
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static ngx_rtmp_amf_handler_t ngx_rtmp_cmd_map[] = {
|
||||
{ ngx_string("connect"), ngx_rtmp_cmd_connect_init },
|
||||
{ ngx_string("createStream"), ngx_rtmp_cmd_create_stream_init },
|
||||
{ ngx_string("closeStream"), ngx_rtmp_cmd_close_stream_init },
|
||||
{ ngx_string("deleteStream"), ngx_rtmp_cmd_delete_stream_init },
|
||||
{ ngx_string("publish"), ngx_rtmp_cmd_publish_init },
|
||||
{ ngx_string("play"), ngx_rtmp_cmd_play_init },
|
||||
{ ngx_string("play2"), ngx_rtmp_cmd_play2_init },
|
||||
{ ngx_string("seek"), ngx_rtmp_cmd_seek_init },
|
||||
{ ngx_string("pause"), ngx_rtmp_cmd_pause_init },
|
||||
{ ngx_string("pauseraw"), ngx_rtmp_cmd_pause_init },
|
||||
};
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_cmd_postconfiguration(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_rtmp_core_main_conf_t *cmcf;
|
||||
ngx_rtmp_handler_pt *h;
|
||||
ngx_rtmp_amf_handler_t *ch, *bh;
|
||||
size_t n, ncalls;
|
||||
|
||||
cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);
|
||||
|
||||
/* redirect disconnects to deleteStream
|
||||
* to free client modules from registering
|
||||
* disconnect callback */
|
||||
|
||||
h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]);
|
||||
if (h == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
*h = ngx_rtmp_cmd_disconnect_init;
|
||||
|
||||
/* register AMF callbacks */
|
||||
|
||||
ncalls = sizeof(ngx_rtmp_cmd_map) / sizeof(ngx_rtmp_cmd_map[0]);
|
||||
|
||||
ch = ngx_array_push_n(&cmcf->amf, ncalls);
|
||||
if (ch == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
bh = ngx_rtmp_cmd_map;
|
||||
|
||||
for(n = 0; n < ncalls; ++n, ++ch, ++bh) {
|
||||
*ch = *bh;
|
||||
}
|
||||
|
||||
ngx_rtmp_connect = ngx_rtmp_cmd_connect;
|
||||
ngx_rtmp_disconnect = ngx_rtmp_cmd_disconnect;
|
||||
ngx_rtmp_create_stream = ngx_rtmp_cmd_create_stream;
|
||||
ngx_rtmp_close_stream = ngx_rtmp_cmd_close_stream;
|
||||
ngx_rtmp_delete_stream = ngx_rtmp_cmd_delete_stream;
|
||||
ngx_rtmp_publish = ngx_rtmp_cmd_publish;
|
||||
ngx_rtmp_play = ngx_rtmp_cmd_play;
|
||||
ngx_rtmp_seek = ngx_rtmp_cmd_seek;
|
||||
ngx_rtmp_pause = ngx_rtmp_cmd_pause;
|
||||
|
||||
ngx_rtmp_stream_begin = ngx_rtmp_cmd_stream_begin;
|
||||
ngx_rtmp_stream_eof = ngx_rtmp_cmd_stream_eof;
|
||||
ngx_rtmp_stream_dry = ngx_rtmp_cmd_stream_dry;
|
||||
ngx_rtmp_recorded = ngx_rtmp_cmd_recorded;
|
||||
ngx_rtmp_set_buflen = ngx_rtmp_cmd_set_buflen;
|
||||
|
||||
ngx_rtmp_playlist = ngx_rtmp_cmd_playlist;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
159
ngx_rtmp_cmd_module.h
Normal file
159
ngx_rtmp_cmd_module.h
Normal file
|
@ -0,0 +1,159 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_CMD_H_INCLUDED_
|
||||
#define _NGX_RTMP_CMD_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
||||
|
||||
#define NGX_RTMP_MAX_NAME 2048
|
||||
#define NGX_RTMP_MAX_URL 4096
|
||||
#define NGX_RTMP_MAX_ARGS NGX_RTMP_MAX_NAME
|
||||
|
||||
|
||||
/* Basic RTMP call support */
|
||||
|
||||
typedef struct {
|
||||
double trans;
|
||||
u_char app[NGX_RTMP_MAX_NAME];
|
||||
u_char args[NGX_RTMP_MAX_ARGS];
|
||||
u_char flashver[64];
|
||||
u_char swf_url[NGX_RTMP_MAX_URL];
|
||||
u_char tc_url[NGX_RTMP_MAX_URL];
|
||||
double acodecs;
|
||||
double vcodecs;
|
||||
u_char page_url[NGX_RTMP_MAX_URL];
|
||||
double object_encoding;
|
||||
} ngx_rtmp_connect_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
double trans;
|
||||
double stream;
|
||||
} ngx_rtmp_create_stream_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
double stream;
|
||||
} ngx_rtmp_delete_stream_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
double stream;
|
||||
} ngx_rtmp_close_stream_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
u_char name[NGX_RTMP_MAX_NAME];
|
||||
u_char args[NGX_RTMP_MAX_ARGS];
|
||||
u_char type[16];
|
||||
int silent;
|
||||
} ngx_rtmp_publish_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t playlist;
|
||||
ngx_str_t module;
|
||||
} ngx_rtmp_playlist_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
u_char name[NGX_RTMP_MAX_NAME];
|
||||
u_char args[NGX_RTMP_MAX_ARGS];
|
||||
double start;
|
||||
double duration;
|
||||
int reset;
|
||||
int silent;
|
||||
} ngx_rtmp_play_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
double offset;
|
||||
} ngx_rtmp_seek_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8_t pause;
|
||||
double position;
|
||||
} ngx_rtmp_pause_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint32_t msid;
|
||||
} ngx_rtmp_msid_t;
|
||||
|
||||
|
||||
typedef ngx_rtmp_msid_t ngx_rtmp_stream_begin_t;
|
||||
typedef ngx_rtmp_msid_t ngx_rtmp_stream_eof_t;
|
||||
typedef ngx_rtmp_msid_t ngx_rtmp_stream_dry_t;
|
||||
typedef ngx_rtmp_msid_t ngx_rtmp_recorded_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint32_t msid;
|
||||
uint32_t buflen;
|
||||
} ngx_rtmp_set_buflen_t;
|
||||
|
||||
|
||||
void ngx_rtmp_cmd_fill_args(u_char name[NGX_RTMP_MAX_NAME],
|
||||
u_char args[NGX_RTMP_MAX_ARGS]);
|
||||
|
||||
|
||||
typedef ngx_int_t (*ngx_rtmp_connect_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_connect_t *v);
|
||||
typedef ngx_int_t (*ngx_rtmp_disconnect_pt)(ngx_rtmp_session_t *s);
|
||||
typedef ngx_int_t (*ngx_rtmp_create_stream_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_create_stream_t *v);
|
||||
typedef ngx_int_t (*ngx_rtmp_close_stream_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_close_stream_t *v);
|
||||
typedef ngx_int_t (*ngx_rtmp_delete_stream_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_delete_stream_t *v);
|
||||
typedef ngx_int_t (*ngx_rtmp_publish_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_publish_t *v);
|
||||
typedef ngx_int_t (*ngx_rtmp_play_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_play_t *v);
|
||||
typedef ngx_int_t (*ngx_rtmp_seek_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_seek_t *v);
|
||||
typedef ngx_int_t (*ngx_rtmp_pause_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_pause_t *v);
|
||||
|
||||
typedef ngx_int_t (*ngx_rtmp_stream_begin_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_stream_begin_t *v);
|
||||
typedef ngx_int_t (*ngx_rtmp_stream_eof_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_stream_eof_t *v);
|
||||
typedef ngx_int_t (*ngx_rtmp_stream_dry_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_stream_dry_t *v);
|
||||
typedef ngx_int_t (*ngx_rtmp_recorded_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_recorded_t *v);
|
||||
typedef ngx_int_t (*ngx_rtmp_set_buflen_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_set_buflen_t *v);
|
||||
|
||||
typedef ngx_int_t (*ngx_rtmp_playlist_pt)(ngx_rtmp_session_t *s, ngx_rtmp_playlist_t *v);
|
||||
|
||||
extern ngx_rtmp_connect_pt ngx_rtmp_connect;
|
||||
extern ngx_rtmp_disconnect_pt ngx_rtmp_disconnect;
|
||||
extern ngx_rtmp_create_stream_pt ngx_rtmp_create_stream;
|
||||
extern ngx_rtmp_close_stream_pt ngx_rtmp_close_stream;
|
||||
extern ngx_rtmp_delete_stream_pt ngx_rtmp_delete_stream;
|
||||
extern ngx_rtmp_publish_pt ngx_rtmp_publish;
|
||||
extern ngx_rtmp_play_pt ngx_rtmp_play;
|
||||
extern ngx_rtmp_seek_pt ngx_rtmp_seek;
|
||||
extern ngx_rtmp_pause_pt ngx_rtmp_pause;
|
||||
|
||||
extern ngx_rtmp_stream_begin_pt ngx_rtmp_stream_begin;
|
||||
extern ngx_rtmp_stream_eof_pt ngx_rtmp_stream_eof;
|
||||
extern ngx_rtmp_stream_dry_pt ngx_rtmp_stream_dry;
|
||||
extern ngx_rtmp_set_buflen_pt ngx_rtmp_set_buflen;
|
||||
extern ngx_rtmp_recorded_pt ngx_rtmp_recorded;
|
||||
|
||||
extern ngx_rtmp_playlist_pt ngx_rtmp_playlist;
|
||||
|
||||
#endif /*_NGX_RTMP_CMD_H_INCLUDED_ */
|
1016
ngx_rtmp_codec_module.c
Normal file
1016
ngx_rtmp_codec_module.c
Normal file
File diff suppressed because it is too large
Load diff
88
ngx_rtmp_codec_module.h
Normal file
88
ngx_rtmp_codec_module.h
Normal file
|
@ -0,0 +1,88 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_CODEC_H_INCLUDED_
|
||||
#define _NGX_RTMP_CODEC_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
||||
|
||||
/* Audio codecs */
|
||||
enum {
|
||||
/* Uncompressed codec id is actually 0,
|
||||
* but we use another value for consistency */
|
||||
NGX_RTMP_AUDIO_UNCOMPRESSED = 16,
|
||||
NGX_RTMP_AUDIO_ADPCM = 1,
|
||||
NGX_RTMP_AUDIO_MP3 = 2,
|
||||
NGX_RTMP_AUDIO_LINEAR_LE = 3,
|
||||
NGX_RTMP_AUDIO_NELLY16 = 4,
|
||||
NGX_RTMP_AUDIO_NELLY8 = 5,
|
||||
NGX_RTMP_AUDIO_NELLY = 6,
|
||||
NGX_RTMP_AUDIO_G711A = 7,
|
||||
NGX_RTMP_AUDIO_G711U = 8,
|
||||
NGX_RTMP_AUDIO_AAC = 10,
|
||||
NGX_RTMP_AUDIO_SPEEX = 11,
|
||||
NGX_RTMP_AUDIO_MP3_8 = 14,
|
||||
NGX_RTMP_AUDIO_DEVSPEC = 15,
|
||||
};
|
||||
|
||||
|
||||
/* Video codecs */
|
||||
enum {
|
||||
NGX_RTMP_VIDEO_JPEG = 1,
|
||||
NGX_RTMP_VIDEO_SORENSON_H263 = 2,
|
||||
NGX_RTMP_VIDEO_SCREEN = 3,
|
||||
NGX_RTMP_VIDEO_ON2_VP6 = 4,
|
||||
NGX_RTMP_VIDEO_ON2_VP6_ALPHA = 5,
|
||||
NGX_RTMP_VIDEO_SCREEN2 = 6,
|
||||
NGX_RTMP_VIDEO_H264 = 7
|
||||
};
|
||||
|
||||
|
||||
u_char * ngx_rtmp_get_audio_codec_name(ngx_uint_t id);
|
||||
u_char * ngx_rtmp_get_video_codec_name(ngx_uint_t id);
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_uint_t width;
|
||||
ngx_uint_t height;
|
||||
double duration;
|
||||
double frame_rate;
|
||||
double video_data_rate;
|
||||
double video_keyframe_frequency;
|
||||
ngx_uint_t video_codec_id;
|
||||
double audio_data_rate;
|
||||
ngx_uint_t audio_codec_id;
|
||||
ngx_uint_t aac_profile;
|
||||
ngx_uint_t aac_chan_conf;
|
||||
ngx_uint_t aac_sbr;
|
||||
ngx_uint_t aac_ps;
|
||||
ngx_uint_t avc_profile;
|
||||
ngx_uint_t avc_compat;
|
||||
ngx_uint_t avc_level;
|
||||
ngx_uint_t avc_nal_bytes;
|
||||
ngx_uint_t avc_ref_frames;
|
||||
ngx_uint_t sample_rate; /* 5512, 11025, 22050, 44100 */
|
||||
ngx_uint_t sample_size; /* 1=8bit, 2=16bit */
|
||||
ngx_uint_t audio_channels; /* 1, 2 */
|
||||
u_char profile[32];
|
||||
u_char level[32];
|
||||
|
||||
ngx_chain_t *avc_header;
|
||||
ngx_chain_t *aac_header;
|
||||
|
||||
ngx_chain_t *meta;
|
||||
ngx_uint_t meta_version;
|
||||
} ngx_rtmp_codec_ctx_t;
|
||||
|
||||
|
||||
extern ngx_module_t ngx_rtmp_codec_module;
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_LIVE_H_INCLUDED_ */
|
732
ngx_rtmp_control_module.c
Normal file
732
ngx_rtmp_control_module.c
Normal file
|
@ -0,0 +1,732 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_http.h>
|
||||
#include "ngx_rtmp.h"
|
||||
#include "ngx_rtmp_live_module.h"
|
||||
#include "ngx_rtmp_record_module.h"
|
||||
|
||||
|
||||
static char *ngx_rtmp_control(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
|
||||
static void * ngx_rtmp_control_create_loc_conf(ngx_conf_t *cf);
|
||||
static char * ngx_rtmp_control_merge_loc_conf(ngx_conf_t *cf,
|
||||
void *parent, void *child);
|
||||
|
||||
|
||||
typedef const char * (*ngx_rtmp_control_handler_t)(ngx_http_request_t *r,
|
||||
ngx_rtmp_session_t *);
|
||||
|
||||
|
||||
#define NGX_RTMP_CONTROL_ALL 0xff
|
||||
#define NGX_RTMP_CONTROL_RECORD 0x01
|
||||
#define NGX_RTMP_CONTROL_DROP 0x02
|
||||
#define NGX_RTMP_CONTROL_REDIRECT 0x04
|
||||
|
||||
|
||||
enum {
|
||||
NGX_RTMP_CONTROL_FILTER_CLIENT = 0,
|
||||
NGX_RTMP_CONTROL_FILTER_PUBLISHER,
|
||||
NGX_RTMP_CONTROL_FILTER_SUBSCRIBER
|
||||
};
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_uint_t count;
|
||||
ngx_str_t path;
|
||||
ngx_uint_t filter;
|
||||
ngx_str_t method;
|
||||
ngx_array_t sessions; /* ngx_rtmp_session_t * */
|
||||
} ngx_rtmp_control_ctx_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_uint_t control;
|
||||
} ngx_rtmp_control_loc_conf_t;
|
||||
|
||||
|
||||
static ngx_conf_bitmask_t ngx_rtmp_control_masks[] = {
|
||||
{ ngx_string("all"), NGX_RTMP_CONTROL_ALL },
|
||||
{ ngx_string("record"), NGX_RTMP_CONTROL_RECORD },
|
||||
{ ngx_string("drop"), NGX_RTMP_CONTROL_DROP },
|
||||
{ ngx_string("redirect"), NGX_RTMP_CONTROL_REDIRECT },
|
||||
{ ngx_null_string, 0 }
|
||||
};
|
||||
|
||||
|
||||
static ngx_command_t ngx_rtmp_control_commands[] = {
|
||||
|
||||
{ ngx_string("rtmp_control"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
|
||||
ngx_rtmp_control,
|
||||
NGX_HTTP_LOC_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_control_loc_conf_t, control),
|
||||
ngx_rtmp_control_masks },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
||||
static ngx_http_module_t ngx_rtmp_control_module_ctx = {
|
||||
NULL, /* preconfiguration */
|
||||
NULL, /* postconfiguration */
|
||||
|
||||
NULL, /* create main configuration */
|
||||
NULL, /* init main configuration */
|
||||
|
||||
NULL, /* create server configuration */
|
||||
NULL, /* merge server configuration */
|
||||
|
||||
ngx_rtmp_control_create_loc_conf, /* create location configuration */
|
||||
ngx_rtmp_control_merge_loc_conf, /* merge location configuration */
|
||||
};
|
||||
|
||||
|
||||
ngx_module_t ngx_rtmp_control_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_rtmp_control_module_ctx, /* module context */
|
||||
ngx_rtmp_control_commands, /* module directives */
|
||||
NGX_HTTP_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
static const char *
|
||||
ngx_rtmp_control_record_handler(ngx_http_request_t *r, ngx_rtmp_session_t *s)
|
||||
{
|
||||
ngx_int_t rc;
|
||||
ngx_str_t rec;
|
||||
ngx_uint_t rn;
|
||||
ngx_rtmp_control_ctx_t *ctx;
|
||||
ngx_rtmp_core_app_conf_t *cacf;
|
||||
ngx_rtmp_record_app_conf_t *racf;
|
||||
|
||||
cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module);
|
||||
racf = cacf->app_conf[ngx_rtmp_record_module.ctx_index];
|
||||
|
||||
if (ngx_http_arg(r, (u_char *) "rec", sizeof("rec") - 1, &rec) != NGX_OK) {
|
||||
rec.len = 0;
|
||||
}
|
||||
|
||||
rn = ngx_rtmp_record_find(racf, &rec);
|
||||
if (rn == NGX_CONF_UNSET_UINT) {
|
||||
return "Recorder not found";
|
||||
}
|
||||
|
||||
ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
|
||||
|
||||
if (ctx->method.len == sizeof("start") - 1 &&
|
||||
ngx_strncmp(ctx->method.data, "start", ctx->method.len) == 0)
|
||||
{
|
||||
rc = ngx_rtmp_record_open(s, rn, &ctx->path);
|
||||
|
||||
} else if (ctx->method.len == sizeof("stop") - 1 &&
|
||||
ngx_strncmp(ctx->method.data, "stop", ctx->method.len) == 0)
|
||||
{
|
||||
rc = ngx_rtmp_record_close(s, rn, &ctx->path);
|
||||
|
||||
} else {
|
||||
return "Undefined method";
|
||||
}
|
||||
|
||||
if (rc == NGX_ERROR) {
|
||||
return "Recorder error";
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static const char *
|
||||
ngx_rtmp_control_drop_handler(ngx_http_request_t *r, ngx_rtmp_session_t *s)
|
||||
{
|
||||
ngx_rtmp_control_ctx_t *ctx;
|
||||
|
||||
ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
|
||||
|
||||
ngx_rtmp_finalize_session(s);
|
||||
|
||||
++ctx->count;
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static const char *
|
||||
ngx_rtmp_control_redirect_handler(ngx_http_request_t *r, ngx_rtmp_session_t *s)
|
||||
{
|
||||
ngx_str_t name;
|
||||
ngx_rtmp_play_t vplay;
|
||||
ngx_rtmp_publish_t vpublish;
|
||||
ngx_rtmp_live_ctx_t *lctx;
|
||||
ngx_rtmp_control_ctx_t *ctx;
|
||||
ngx_rtmp_close_stream_t vc;
|
||||
|
||||
if (ngx_http_arg(r, (u_char *) "newname", sizeof("newname") - 1, &name)
|
||||
!= NGX_OK)
|
||||
{
|
||||
return "newname not specified";
|
||||
}
|
||||
|
||||
if (name.len >= NGX_RTMP_MAX_NAME) {
|
||||
name.len = NGX_RTMP_MAX_NAME - 1;
|
||||
}
|
||||
|
||||
ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
|
||||
ctx->count++;
|
||||
|
||||
ngx_memzero(&vc, sizeof(ngx_rtmp_close_stream_t));
|
||||
|
||||
/* close_stream should be synchronous */
|
||||
ngx_rtmp_close_stream(s, &vc);
|
||||
|
||||
lctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);
|
||||
|
||||
if (lctx && lctx->publishing) {
|
||||
/* publish */
|
||||
|
||||
ngx_memzero(&vpublish, sizeof(ngx_rtmp_publish_t));
|
||||
|
||||
ngx_memcpy(vpublish.name, name.data, name.len);
|
||||
|
||||
ngx_rtmp_cmd_fill_args(vpublish.name, vpublish.args);
|
||||
|
||||
if (ngx_rtmp_publish(s, &vpublish) != NGX_OK) {
|
||||
return "publish failed";
|
||||
}
|
||||
|
||||
} else {
|
||||
/* play */
|
||||
|
||||
ngx_memzero(&vplay, sizeof(ngx_rtmp_play_t));
|
||||
|
||||
ngx_memcpy(vplay.name, name.data, name.len);
|
||||
|
||||
ngx_rtmp_cmd_fill_args(vplay.name, vplay.args);
|
||||
|
||||
if (ngx_rtmp_play(s, &vplay) != NGX_OK) {
|
||||
return "play failed";
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static const char *
|
||||
ngx_rtmp_control_walk_session(ngx_http_request_t *r,
|
||||
ngx_rtmp_live_ctx_t *lctx)
|
||||
{
|
||||
ngx_str_t addr, *paddr, clientid;
|
||||
ngx_rtmp_session_t *s, **ss;
|
||||
ngx_rtmp_control_ctx_t *ctx;
|
||||
|
||||
s = lctx->session;
|
||||
|
||||
if (s == NULL || s->connection == NULL) {
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
if (ngx_http_arg(r, (u_char *) "addr", sizeof("addr") - 1, &addr)
|
||||
== NGX_OK)
|
||||
{
|
||||
paddr = &s->connection->addr_text;
|
||||
if (paddr->len != addr.len ||
|
||||
ngx_strncmp(paddr->data, addr.data, addr.len))
|
||||
{
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
}
|
||||
|
||||
if (ngx_http_arg(r, (u_char *) "clientid", sizeof("clientid") - 1,
|
||||
&clientid)
|
||||
== NGX_OK)
|
||||
{
|
||||
if (s->connection->number !=
|
||||
(ngx_uint_t) ngx_atoi(clientid.data, clientid.len))
|
||||
{
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
}
|
||||
|
||||
ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
|
||||
|
||||
switch (ctx->filter) {
|
||||
case NGX_RTMP_CONTROL_FILTER_PUBLISHER:
|
||||
if (!lctx->publishing) {
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_CONTROL_FILTER_SUBSCRIBER:
|
||||
if (lctx->publishing) {
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_CONTROL_FILTER_CLIENT:
|
||||
break;
|
||||
}
|
||||
|
||||
ss = ngx_array_push(&ctx->sessions);
|
||||
if (ss == NULL) {
|
||||
return "allocation error";
|
||||
}
|
||||
|
||||
*ss = s;
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static const char *
|
||||
ngx_rtmp_control_walk_stream(ngx_http_request_t *r,
|
||||
ngx_rtmp_live_stream_t *ls)
|
||||
{
|
||||
const char *s;
|
||||
ngx_rtmp_live_ctx_t *lctx;
|
||||
|
||||
for (lctx = ls->ctx; lctx; lctx = lctx->next) {
|
||||
s = ngx_rtmp_control_walk_session(r, lctx);
|
||||
if (s != NGX_CONF_OK) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static const char *
|
||||
ngx_rtmp_control_walk_app(ngx_http_request_t *r,
|
||||
ngx_rtmp_core_app_conf_t *cacf)
|
||||
{
|
||||
size_t len;
|
||||
ngx_str_t name;
|
||||
const char *s;
|
||||
ngx_uint_t n;
|
||||
ngx_rtmp_live_stream_t *ls;
|
||||
ngx_rtmp_live_app_conf_t *lacf;
|
||||
|
||||
lacf = cacf->app_conf[ngx_rtmp_live_module.ctx_index];
|
||||
|
||||
if (ngx_http_arg(r, (u_char *) "name", sizeof("name") - 1, &name) != NGX_OK)
|
||||
{
|
||||
for (n = 0; n < (ngx_uint_t) lacf->nbuckets; ++n) {
|
||||
for (ls = lacf->streams[n]; ls; ls = ls->next) {
|
||||
s = ngx_rtmp_control_walk_stream(r, ls);
|
||||
if (s != NGX_CONF_OK) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
for (ls = lacf->streams[ngx_hash_key(name.data, name.len) % lacf->nbuckets];
|
||||
ls; ls = ls->next)
|
||||
{
|
||||
len = ngx_strlen(ls->name);
|
||||
if (name.len != len || ngx_strncmp(name.data, ls->name, name.len)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
s = ngx_rtmp_control_walk_stream(r, ls);
|
||||
if (s != NGX_CONF_OK) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static const char *
|
||||
ngx_rtmp_control_walk_server(ngx_http_request_t *r,
|
||||
ngx_rtmp_core_srv_conf_t *cscf)
|
||||
{
|
||||
ngx_str_t app;
|
||||
ngx_uint_t n;
|
||||
const char *s;
|
||||
ngx_rtmp_core_app_conf_t **pcacf;
|
||||
|
||||
if (ngx_http_arg(r, (u_char *) "app", sizeof("app") - 1, &app) != NGX_OK) {
|
||||
app.len = 0;
|
||||
}
|
||||
|
||||
pcacf = cscf->applications.elts;
|
||||
|
||||
for (n = 0; n < cscf->applications.nelts; ++n, ++pcacf) {
|
||||
if (app.len && ((*pcacf)->name.len != app.len ||
|
||||
ngx_strncmp((*pcacf)->name.data, app.data, app.len)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
s = ngx_rtmp_control_walk_app(r, *pcacf);
|
||||
if (s != NGX_CONF_OK) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static const char *
|
||||
ngx_rtmp_control_walk(ngx_http_request_t *r, ngx_rtmp_control_handler_t h)
|
||||
{
|
||||
ngx_rtmp_core_main_conf_t *cmcf = ngx_rtmp_core_main_conf;
|
||||
|
||||
ngx_str_t srv;
|
||||
ngx_uint_t sn, n;
|
||||
const char *msg;
|
||||
ngx_rtmp_session_t **s;
|
||||
ngx_rtmp_control_ctx_t *ctx;
|
||||
ngx_rtmp_core_srv_conf_t **pcscf;
|
||||
|
||||
sn = 0;
|
||||
if (ngx_http_arg(r, (u_char *) "srv", sizeof("srv") - 1, &srv) == NGX_OK) {
|
||||
sn = ngx_atoi(srv.data, srv.len);
|
||||
}
|
||||
|
||||
if (sn >= cmcf->servers.nelts) {
|
||||
return "Server index out of range";
|
||||
}
|
||||
|
||||
pcscf = cmcf->servers.elts;
|
||||
pcscf += sn;
|
||||
|
||||
msg = ngx_rtmp_control_walk_server(r, *pcscf);
|
||||
if (msg != NGX_CONF_OK) {
|
||||
return msg;
|
||||
}
|
||||
|
||||
ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
|
||||
|
||||
s = ctx->sessions.elts;
|
||||
for (n = 0; n < ctx->sessions.nelts; n++) {
|
||||
msg = h(r, s[n]);
|
||||
if (msg != NGX_CONF_OK) {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_control_record(ngx_http_request_t *r, ngx_str_t *method)
|
||||
{
|
||||
ngx_buf_t *b;
|
||||
const char *msg;
|
||||
ngx_chain_t cl;
|
||||
ngx_rtmp_control_ctx_t *ctx;
|
||||
|
||||
ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
|
||||
ctx->filter = NGX_RTMP_CONTROL_FILTER_PUBLISHER;
|
||||
|
||||
msg = ngx_rtmp_control_walk(r, ngx_rtmp_control_record_handler);
|
||||
if (msg != NGX_CONF_OK) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (ctx->path.len == 0) {
|
||||
return NGX_HTTP_NO_CONTENT;
|
||||
}
|
||||
|
||||
/* output record path */
|
||||
|
||||
r->headers_out.status = NGX_HTTP_OK;
|
||||
r->headers_out.content_length_n = ctx->path.len;
|
||||
|
||||
b = ngx_create_temp_buf(r->pool, ctx->path.len);
|
||||
if (b == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
ngx_memzero(&cl, sizeof(cl));
|
||||
cl.buf = b;
|
||||
|
||||
b->last = ngx_cpymem(b->pos, ctx->path.data, ctx->path.len);
|
||||
b->last_buf = 1;
|
||||
|
||||
ngx_http_send_header(r);
|
||||
|
||||
return ngx_http_output_filter(r, &cl);
|
||||
|
||||
error:
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_control_drop(ngx_http_request_t *r, ngx_str_t *method)
|
||||
{
|
||||
size_t len;
|
||||
u_char *p;
|
||||
ngx_buf_t *b;
|
||||
ngx_chain_t cl;
|
||||
const char *msg;
|
||||
ngx_rtmp_control_ctx_t *ctx;
|
||||
|
||||
ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
|
||||
|
||||
if (ctx->method.len == sizeof("publisher") - 1 &&
|
||||
ngx_memcmp(ctx->method.data, "publisher", ctx->method.len) == 0)
|
||||
{
|
||||
ctx->filter = NGX_RTMP_CONTROL_FILTER_PUBLISHER;
|
||||
|
||||
} else if (ctx->method.len == sizeof("subscriber") - 1 &&
|
||||
ngx_memcmp(ctx->method.data, "subscriber", ctx->method.len)
|
||||
== 0)
|
||||
{
|
||||
ctx->filter = NGX_RTMP_CONTROL_FILTER_SUBSCRIBER;
|
||||
|
||||
} else if (method->len == sizeof("client") - 1 &&
|
||||
ngx_memcmp(ctx->method.data, "client", ctx->method.len) == 0)
|
||||
{
|
||||
ctx->filter = NGX_RTMP_CONTROL_FILTER_CLIENT;
|
||||
|
||||
} else {
|
||||
msg = "Undefined filter";
|
||||
goto error;
|
||||
}
|
||||
|
||||
msg = ngx_rtmp_control_walk(r, ngx_rtmp_control_drop_handler);
|
||||
if (msg != NGX_CONF_OK) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* output count */
|
||||
|
||||
len = NGX_INT_T_LEN;
|
||||
|
||||
p = ngx_palloc(r->connection->pool, len);
|
||||
if (p == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
len = (size_t) (ngx_snprintf(p, len, "%ui", ctx->count) - p);
|
||||
|
||||
r->headers_out.status = NGX_HTTP_OK;
|
||||
r->headers_out.content_length_n = len;
|
||||
|
||||
b = ngx_calloc_buf(r->pool);
|
||||
if (b == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
b->start = b->pos = p;
|
||||
b->end = b->last = p + len;
|
||||
b->temporary = 1;
|
||||
b->last_buf = 1;
|
||||
|
||||
ngx_memzero(&cl, sizeof(cl));
|
||||
cl.buf = b;
|
||||
|
||||
ngx_http_send_header(r);
|
||||
|
||||
return ngx_http_output_filter(r, &cl);
|
||||
|
||||
error:
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_control_redirect(ngx_http_request_t *r, ngx_str_t *method)
|
||||
{
|
||||
size_t len;
|
||||
u_char *p;
|
||||
ngx_buf_t *b;
|
||||
ngx_chain_t cl;
|
||||
const char *msg;
|
||||
ngx_rtmp_control_ctx_t *ctx;
|
||||
|
||||
ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
|
||||
|
||||
if (ctx->method.len == sizeof("publisher") - 1 &&
|
||||
ngx_memcmp(ctx->method.data, "publisher", ctx->method.len) == 0)
|
||||
{
|
||||
ctx->filter = NGX_RTMP_CONTROL_FILTER_PUBLISHER;
|
||||
|
||||
} else if (ctx->method.len == sizeof("subscriber") - 1 &&
|
||||
ngx_memcmp(ctx->method.data, "subscriber", ctx->method.len)
|
||||
== 0)
|
||||
{
|
||||
ctx->filter = NGX_RTMP_CONTROL_FILTER_SUBSCRIBER;
|
||||
|
||||
} else if (ctx->method.len == sizeof("client") - 1 &&
|
||||
ngx_memcmp(ctx->method.data, "client", ctx->method.len) == 0)
|
||||
{
|
||||
ctx->filter = NGX_RTMP_CONTROL_FILTER_CLIENT;
|
||||
|
||||
} else {
|
||||
msg = "Undefined filter";
|
||||
goto error;
|
||||
}
|
||||
|
||||
msg = ngx_rtmp_control_walk(r, ngx_rtmp_control_redirect_handler);
|
||||
if (msg != NGX_CONF_OK) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* output count */
|
||||
|
||||
len = NGX_INT_T_LEN;
|
||||
|
||||
p = ngx_palloc(r->connection->pool, len);
|
||||
if (p == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
len = (size_t) (ngx_snprintf(p, len, "%ui", ctx->count) - p);
|
||||
|
||||
r->headers_out.status = NGX_HTTP_OK;
|
||||
r->headers_out.content_length_n = len;
|
||||
|
||||
b = ngx_calloc_buf(r->pool);
|
||||
if (b == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
b->start = b->pos = p;
|
||||
b->end = b->last = p + len;
|
||||
b->temporary = 1;
|
||||
b->last_buf = 1;
|
||||
|
||||
ngx_memzero(&cl, sizeof(cl));
|
||||
cl.buf = b;
|
||||
|
||||
ngx_http_send_header(r);
|
||||
|
||||
return ngx_http_output_filter(r, &cl);
|
||||
|
||||
error:
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_control_handler(ngx_http_request_t *r)
|
||||
{
|
||||
u_char *p;
|
||||
ngx_str_t section, method;
|
||||
ngx_uint_t n;
|
||||
ngx_rtmp_control_ctx_t *ctx;
|
||||
ngx_rtmp_control_loc_conf_t *llcf;
|
||||
|
||||
llcf = ngx_http_get_module_loc_conf(r, ngx_rtmp_control_module);
|
||||
if (llcf->control == 0) {
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
/* uri format: .../section/method?args */
|
||||
|
||||
ngx_str_null(§ion);
|
||||
ngx_str_null(&method);
|
||||
|
||||
for (n = r->uri.len; n; --n) {
|
||||
p = &r->uri.data[n - 1];
|
||||
|
||||
if (*p != '/') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (method.data) {
|
||||
section.data = p + 1;
|
||||
section.len = method.data - section.data - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
method.data = p + 1;
|
||||
method.len = r->uri.data + r->uri.len - method.data;
|
||||
}
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, r->connection->log, 0,
|
||||
"rtmp_control: section='%V' method='%V'",
|
||||
§ion, &method);
|
||||
|
||||
ctx = ngx_pcalloc(r->pool, sizeof(ngx_rtmp_control_ctx_t));
|
||||
if (ctx == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_http_set_ctx(r, ctx, ngx_rtmp_control_module);
|
||||
|
||||
if (ngx_array_init(&ctx->sessions, r->pool, 1, sizeof(void *)) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ctx->method = method;
|
||||
|
||||
#define NGX_RTMP_CONTROL_SECTION(flag, secname) \
|
||||
if (llcf->control & NGX_RTMP_CONTROL_##flag && \
|
||||
section.len == sizeof(#secname) - 1 && \
|
||||
ngx_strncmp(section.data, #secname, sizeof(#secname) - 1) == 0) \
|
||||
{ \
|
||||
return ngx_rtmp_control_##secname(r, &method); \
|
||||
}
|
||||
|
||||
NGX_RTMP_CONTROL_SECTION(RECORD, record);
|
||||
NGX_RTMP_CONTROL_SECTION(DROP, drop);
|
||||
NGX_RTMP_CONTROL_SECTION(REDIRECT, redirect);
|
||||
|
||||
#undef NGX_RTMP_CONTROL_SECTION
|
||||
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
ngx_rtmp_control_create_loc_conf(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_rtmp_control_loc_conf_t *conf;
|
||||
|
||||
conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_control_loc_conf_t));
|
||||
if (conf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
conf->control = 0;
|
||||
|
||||
return conf;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_rtmp_control_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
|
||||
{
|
||||
ngx_rtmp_control_loc_conf_t *prev = parent;
|
||||
ngx_rtmp_control_loc_conf_t *conf = child;
|
||||
|
||||
ngx_conf_merge_bitmask_value(conf->control, prev->control, 0);
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_rtmp_control(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
||||
{
|
||||
ngx_http_core_loc_conf_t *clcf;
|
||||
|
||||
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
|
||||
clcf->handler = ngx_rtmp_control_handler;
|
||||
|
||||
return ngx_conf_set_bitmask_slot(cf, cmd, conf);
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
|
||||
/*
|
||||
* Copyright (c) 2012 Roman Arutyunyan
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_event.h>
|
||||
#include <nginx.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
||||
|
||||
|
@ -14,14 +15,22 @@ static void *ngx_rtmp_core_create_main_conf(ngx_conf_t *cf);
|
|||
static void *ngx_rtmp_core_create_srv_conf(ngx_conf_t *cf);
|
||||
static char *ngx_rtmp_core_merge_srv_conf(ngx_conf_t *cf, void *parent,
|
||||
void *child);
|
||||
static void *ngx_rtmp_core_create_app_conf(ngx_conf_t *cf);
|
||||
static char *ngx_rtmp_core_merge_app_conf(ngx_conf_t *cf, void *parent,
|
||||
void *child);
|
||||
static char *ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd,
|
||||
void *conf);
|
||||
static char *ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd,
|
||||
void *conf);
|
||||
static char *ngx_rtmp_core_application(ngx_conf_t *cf, ngx_command_t *cmd,
|
||||
void *conf);
|
||||
|
||||
|
||||
ngx_rtmp_core_main_conf_t *ngx_rtmp_core_main_conf;
|
||||
|
||||
|
||||
static ngx_conf_deprecated_t ngx_conf_deprecated_so_keepalive = {
|
||||
ngx_conf_deprecated, "so_keepalive",
|
||||
ngx_conf_deprecated, "so_keepalive",
|
||||
"so_keepalive\" parameter of the \"listen"
|
||||
};
|
||||
|
||||
|
@ -36,12 +45,19 @@ static ngx_command_t ngx_rtmp_core_commands[] = {
|
|||
NULL },
|
||||
|
||||
{ ngx_string("listen"),
|
||||
NGX_RTMP_SRV_CONF|NGX_CONF_TAKE12,
|
||||
NGX_RTMP_SRV_CONF|NGX_CONF_1MORE,
|
||||
ngx_rtmp_core_listen,
|
||||
NGX_RTMP_SRV_CONF_OFFSET,
|
||||
0,
|
||||
NULL },
|
||||
|
||||
{ ngx_string("application"),
|
||||
NGX_RTMP_SRV_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1,
|
||||
ngx_rtmp_core_application,
|
||||
NGX_RTMP_SRV_CONF_OFFSET,
|
||||
0,
|
||||
NULL },
|
||||
|
||||
{ ngx_string("so_keepalive"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_FLAG,
|
||||
ngx_conf_set_flag_slot,
|
||||
|
@ -56,6 +72,20 @@ static ngx_command_t ngx_rtmp_core_commands[] = {
|
|||
offsetof(ngx_rtmp_core_srv_conf_t, timeout),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("ping"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_msec_slot,
|
||||
NGX_RTMP_SRV_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_core_srv_conf_t, ping),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("ping_timeout"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_msec_slot,
|
||||
NGX_RTMP_SRV_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_core_srv_conf_t, ping_timeout),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("max_streams"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_num_slot,
|
||||
|
@ -77,18 +107,54 @@ static ngx_command_t ngx_rtmp_core_commands[] = {
|
|||
offsetof(ngx_rtmp_core_srv_conf_t, chunk_size),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("max_buf"),
|
||||
{ ngx_string("max_message"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_size_slot,
|
||||
NGX_RTMP_SRV_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_core_srv_conf_t, max_buf),
|
||||
offsetof(ngx_rtmp_core_srv_conf_t, max_message),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("wait_key_frame"),
|
||||
{ ngx_string("out_queue"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_size_slot,
|
||||
NGX_RTMP_SRV_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_core_srv_conf_t, out_queue),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("out_cork"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_size_slot,
|
||||
NGX_RTMP_SRV_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_core_srv_conf_t, out_cork),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("busy"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_flag_slot,
|
||||
NGX_RTMP_SRV_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_core_srv_conf_t, wait_key_frame),
|
||||
offsetof(ngx_rtmp_core_srv_conf_t, busy),
|
||||
NULL },
|
||||
|
||||
/* time fixes are needed for flash clients */
|
||||
{ ngx_string("play_time_fix"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_flag_slot,
|
||||
NGX_RTMP_SRV_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_core_srv_conf_t, play_time_fix),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("publish_time_fix"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_flag_slot,
|
||||
NGX_RTMP_SRV_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_core_srv_conf_t, publish_time_fix),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("buflen"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_msec_slot,
|
||||
NGX_RTMP_SRV_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_core_srv_conf_t, buflen),
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
|
@ -101,7 +167,9 @@ static ngx_rtmp_module_t ngx_rtmp_core_module_ctx = {
|
|||
ngx_rtmp_core_create_main_conf, /* create main configuration */
|
||||
NULL, /* init main configuration */
|
||||
ngx_rtmp_core_create_srv_conf, /* create server configuration */
|
||||
ngx_rtmp_core_merge_srv_conf /* merge server configuration */
|
||||
ngx_rtmp_core_merge_srv_conf, /* merge server configuration */
|
||||
ngx_rtmp_core_create_app_conf, /* create app configuration */
|
||||
ngx_rtmp_core_merge_app_conf /* merge app configuration */
|
||||
};
|
||||
|
||||
|
||||
|
@ -131,6 +199,8 @@ ngx_rtmp_core_create_main_conf(ngx_conf_t *cf)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
ngx_rtmp_core_main_conf = cmcf;
|
||||
|
||||
if (ngx_array_init(&cmcf->servers, cf->pool, 4,
|
||||
sizeof(ngx_rtmp_core_srv_conf_t *))
|
||||
!= NGX_OK)
|
||||
|
@ -158,13 +228,27 @@ ngx_rtmp_core_create_srv_conf(ngx_conf_t *cf)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
if (ngx_array_init(&conf->applications, cf->pool, 4,
|
||||
sizeof(ngx_rtmp_core_app_conf_t *))
|
||||
!= NGX_OK)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
conf->timeout = NGX_CONF_UNSET_MSEC;
|
||||
conf->ping = NGX_CONF_UNSET_MSEC;
|
||||
conf->ping_timeout = NGX_CONF_UNSET_MSEC;
|
||||
conf->so_keepalive = NGX_CONF_UNSET;
|
||||
conf->max_streams = NGX_CONF_UNSET;
|
||||
conf->chunk_size = NGX_CONF_UNSET;
|
||||
conf->ack_window = NGX_CONF_UNSET;
|
||||
conf->max_buf = NGX_CONF_UNSET;
|
||||
conf->wait_key_frame = NGX_CONF_UNSET;
|
||||
conf->ack_window = NGX_CONF_UNSET_UINT;
|
||||
conf->max_message = NGX_CONF_UNSET_SIZE;
|
||||
conf->out_queue = NGX_CONF_UNSET_SIZE;
|
||||
conf->out_cork = NGX_CONF_UNSET_SIZE;
|
||||
conf->play_time_fix = NGX_CONF_UNSET;
|
||||
conf->publish_time_fix = NGX_CONF_UNSET;
|
||||
conf->buflen = NGX_CONF_UNSET_MSEC;
|
||||
conf->busy = NGX_CONF_UNSET;
|
||||
|
||||
return conf;
|
||||
}
|
||||
|
@ -177,16 +261,25 @@ ngx_rtmp_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
|
|||
ngx_rtmp_core_srv_conf_t *conf = child;
|
||||
|
||||
ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000);
|
||||
ngx_conf_merge_msec_value(conf->ping, prev->ping, 60000);
|
||||
ngx_conf_merge_msec_value(conf->ping_timeout, prev->ping_timeout, 30000);
|
||||
|
||||
ngx_conf_merge_value(conf->so_keepalive, prev->so_keepalive, 0);
|
||||
ngx_conf_merge_value(conf->max_streams, prev->max_streams, 16);
|
||||
ngx_conf_merge_value(conf->max_streams, prev->max_streams, 32);
|
||||
ngx_conf_merge_value(conf->chunk_size, prev->chunk_size, 4096);
|
||||
ngx_conf_merge_uint_value(conf->ack_window, prev->ack_window, 5000000);
|
||||
ngx_conf_merge_size_value(conf->max_buf, prev->max_buf, 128 * 1024);
|
||||
ngx_conf_merge_value(conf->wait_key_frame, prev->wait_key_frame, 1);
|
||||
ngx_conf_merge_size_value(conf->max_message, prev->max_message,
|
||||
1 * 1024 * 1024);
|
||||
ngx_conf_merge_size_value(conf->out_queue, prev->out_queue, 256);
|
||||
ngx_conf_merge_size_value(conf->out_cork, prev->out_cork,
|
||||
conf->out_queue / 8);
|
||||
ngx_conf_merge_value(conf->play_time_fix, prev->play_time_fix, 1);
|
||||
ngx_conf_merge_value(conf->publish_time_fix, prev->publish_time_fix, 1);
|
||||
ngx_conf_merge_msec_value(conf->buflen, prev->buflen, 1000);
|
||||
ngx_conf_merge_value(conf->busy, prev->busy, 0);
|
||||
|
||||
if (prev->pool == NULL) {
|
||||
prev->pool = ngx_create_pool(8192, cf->log);
|
||||
prev->pool = ngx_create_pool(4096, &cf->cycle->new_log);
|
||||
if (prev->pool == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
@ -198,6 +291,40 @@ ngx_rtmp_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
|
|||
}
|
||||
|
||||
|
||||
static void *
|
||||
ngx_rtmp_core_create_app_conf(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_rtmp_core_app_conf_t *conf;
|
||||
|
||||
conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_core_app_conf_t));
|
||||
if (conf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ngx_array_init(&conf->applications, cf->pool, 1,
|
||||
sizeof(ngx_rtmp_core_app_conf_t *))
|
||||
!= NGX_OK)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return conf;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_rtmp_core_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
|
||||
{
|
||||
ngx_rtmp_core_app_conf_t *prev = parent;
|
||||
ngx_rtmp_core_app_conf_t *conf = child;
|
||||
|
||||
(void)prev;
|
||||
(void)conf;
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
||||
{
|
||||
|
@ -205,6 +332,7 @@ ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
void *mconf;
|
||||
ngx_uint_t m;
|
||||
ngx_conf_t pcf;
|
||||
ngx_module_t **modules;
|
||||
ngx_rtmp_module_t *module;
|
||||
ngx_rtmp_conf_ctx_t *ctx, *rtmp_ctx;
|
||||
ngx_rtmp_core_srv_conf_t *cscf, **cscfp;
|
||||
|
@ -225,12 +353,22 @@ ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
for (m = 0; ngx_modules[m]; m++) {
|
||||
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
|
||||
ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module);
|
||||
if (ctx->app_conf == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
#if defined(nginx_version) && nginx_version >= 1009011
|
||||
modules = cf->cycle->modules;
|
||||
#else
|
||||
modules = ngx_modules;
|
||||
#endif
|
||||
for (m = 0; modules[m]; m++) {
|
||||
if (modules[m]->type != NGX_RTMP_MODULE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
module = ngx_modules[m]->ctx;
|
||||
module = modules[m]->ctx;
|
||||
|
||||
if (module->create_srv_conf) {
|
||||
mconf = module->create_srv_conf(cf);
|
||||
|
@ -238,7 +376,16 @@ ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
ctx->srv_conf[ngx_modules[m]->ctx_index] = mconf;
|
||||
ctx->srv_conf[modules[m]->ctx_index] = mconf;
|
||||
}
|
||||
|
||||
if (module->create_app_conf) {
|
||||
mconf = module->create_app_conf(cf);
|
||||
if (mconf == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
ctx->app_conf[modules[m]->ctx_index] = mconf;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -271,6 +418,80 @@ ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_rtmp_core_application(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
||||
{
|
||||
char *rv;
|
||||
ngx_int_t i;
|
||||
ngx_str_t *value;
|
||||
ngx_conf_t save;
|
||||
ngx_module_t **modules;
|
||||
ngx_rtmp_module_t *module;
|
||||
ngx_rtmp_conf_ctx_t *ctx, *pctx;
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
ngx_rtmp_core_app_conf_t *cacf, **cacfp;
|
||||
|
||||
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t));
|
||||
if (ctx == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
pctx = cf->ctx;
|
||||
ctx->main_conf = pctx->main_conf;
|
||||
ctx->srv_conf = pctx->srv_conf;
|
||||
|
||||
ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module);
|
||||
if (ctx->app_conf == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
#if defined(nginx_version) && nginx_version >= 1009011
|
||||
modules = cf->cycle->modules;
|
||||
#else
|
||||
modules = ngx_modules;
|
||||
#endif
|
||||
for (i = 0; modules[i]; i++) {
|
||||
if (modules[i]->type != NGX_RTMP_MODULE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
module = modules[i]->ctx;
|
||||
|
||||
if (module->create_app_conf) {
|
||||
ctx->app_conf[modules[i]->ctx_index] = module->create_app_conf(cf);
|
||||
if (ctx->app_conf[modules[i]->ctx_index] == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cacf = ctx->app_conf[ngx_rtmp_core_module.ctx_index];
|
||||
cacf->app_conf = ctx->app_conf;
|
||||
|
||||
value = cf->args->elts;
|
||||
|
||||
cacf->name = value[1];
|
||||
cscf = pctx->srv_conf[ngx_rtmp_core_module.ctx_index];
|
||||
|
||||
cacfp = ngx_array_push(&cscf->applications);
|
||||
if (cacfp == NULL) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
*cacfp = cacf;
|
||||
|
||||
save = *cf;
|
||||
cf->ctx = ctx;
|
||||
cf->cmd_type = NGX_RTMP_APP_CONF;
|
||||
|
||||
rv = ngx_conf_parse(cf, NULL);
|
||||
|
||||
*cf= save;
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
||||
{
|
||||
|
@ -278,10 +499,9 @@ ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
in_port_t port;
|
||||
ngx_str_t *value;
|
||||
ngx_url_t u;
|
||||
ngx_uint_t i, m;
|
||||
ngx_uint_t i;
|
||||
struct sockaddr *sa;
|
||||
ngx_rtmp_listen_t *ls;
|
||||
ngx_rtmp_module_t *module;
|
||||
struct sockaddr_in *sin;
|
||||
ngx_rtmp_core_main_conf_t *cmcf;
|
||||
#if (NGX_HAVE_INET6)
|
||||
|
@ -336,7 +556,11 @@ ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
break;
|
||||
}
|
||||
|
||||
#if (nginx_version >= 1011000)
|
||||
if (ngx_memcmp(ls[i].sockaddr + off, (u_char *) &u.sockaddr + off, len) != 0) {
|
||||
#else
|
||||
if (ngx_memcmp(ls[i].sockaddr + off, u.sockaddr + off, len) != 0) {
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -356,20 +580,16 @@ ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
|
||||
ngx_memzero(ls, sizeof(ngx_rtmp_listen_t));
|
||||
|
||||
#if (nginx_version >= 1011000)
|
||||
ngx_memcpy(ls->sockaddr, (u_char *) &u.sockaddr, u.socklen);
|
||||
#else
|
||||
ngx_memcpy(ls->sockaddr, u.sockaddr, u.socklen);
|
||||
#endif
|
||||
|
||||
ls->socklen = u.socklen;
|
||||
ls->wildcard = u.wildcard;
|
||||
ls->ctx = cf->ctx;
|
||||
|
||||
for (m = 0; ngx_modules[m]; m++) {
|
||||
if (ngx_modules[m]->type != NGX_RTMP_MODULE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
module = ngx_modules[m]->ctx;
|
||||
}
|
||||
|
||||
for (i = 2; i < cf->args->nelts; i++) {
|
||||
|
||||
if (ngx_strcmp(value[i].data, "bind") == 0) {
|
||||
|
@ -379,7 +599,6 @@ ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
|
||||
if (ngx_strncmp(value[i].data, "ipv6only=o", 10) == 0) {
|
||||
#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
|
||||
struct sockaddr *sa;
|
||||
u_char buf[NGX_SOCKADDR_STRLEN];
|
||||
|
||||
sa = (struct sockaddr *) ls->sockaddr;
|
||||
|
@ -390,7 +609,7 @@ ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
ls->ipv6only = 1;
|
||||
|
||||
} else if (ngx_strcmp(&value[i].data[10], "ff") == 0) {
|
||||
ls->ipv6only = 2;
|
||||
ls->ipv6only = 0;
|
||||
|
||||
} else {
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
|
@ -402,7 +621,11 @@ ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
ls->bind = 1;
|
||||
|
||||
} else {
|
||||
len = ngx_sock_ntop(sa, buf, NGX_SOCKADDR_STRLEN, 1);
|
||||
len = ngx_sock_ntop(sa,
|
||||
#if (nginx_version >= 1005003)
|
||||
ls->socklen,
|
||||
#endif
|
||||
buf, NGX_SOCKADDR_STRLEN, 1);
|
||||
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"ipv6only is not supported "
|
||||
|
@ -508,6 +731,11 @@ ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
#endif
|
||||
}
|
||||
|
||||
if (ngx_strcmp(value[i].data, "proxy_protocol") == 0) {
|
||||
ls->proxy_protocol = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
|
||||
"the invalid \"%V\" parameter", &value[i]);
|
||||
return NGX_CONF_ERROR;
|
||||
|
@ -515,4 +743,3 @@ ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
|||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
|
287
ngx_rtmp_eval.c
Normal file
287
ngx_rtmp_eval.c
Normal file
|
@ -0,0 +1,287 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp_eval.h"
|
||||
|
||||
|
||||
#define NGX_RTMP_EVAL_BUFLEN 16
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_eval_session_str(void *ctx, ngx_rtmp_eval_t *e, ngx_str_t *ret)
|
||||
{
|
||||
*ret = *(ngx_str_t *) ((u_char *) ctx + e->offset);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_eval_connection_str(void *ctx, ngx_rtmp_eval_t *e, ngx_str_t *ret)
|
||||
{
|
||||
ngx_rtmp_session_t *s = ctx;
|
||||
|
||||
*ret = *(ngx_str_t *) ((u_char *) s->connection + e->offset);
|
||||
}
|
||||
|
||||
|
||||
ngx_rtmp_eval_t ngx_rtmp_eval_session[] = {
|
||||
|
||||
{ ngx_string("app"),
|
||||
ngx_rtmp_eval_session_str,
|
||||
offsetof(ngx_rtmp_session_t, app) },
|
||||
|
||||
{ ngx_string("flashver"),
|
||||
ngx_rtmp_eval_session_str,
|
||||
offsetof(ngx_rtmp_session_t, flashver) },
|
||||
|
||||
{ ngx_string("swfurl"),
|
||||
ngx_rtmp_eval_session_str,
|
||||
offsetof(ngx_rtmp_session_t, swf_url) },
|
||||
|
||||
{ ngx_string("tcurl"),
|
||||
ngx_rtmp_eval_session_str,
|
||||
offsetof(ngx_rtmp_session_t, tc_url) },
|
||||
|
||||
{ ngx_string("pageurl"),
|
||||
ngx_rtmp_eval_session_str,
|
||||
offsetof(ngx_rtmp_session_t, page_url) },
|
||||
|
||||
{ ngx_string("addr"),
|
||||
ngx_rtmp_eval_connection_str,
|
||||
offsetof(ngx_connection_t, addr_text) },
|
||||
|
||||
ngx_rtmp_null_eval
|
||||
};
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_eval_append(ngx_buf_t *b, void *data, size_t len, ngx_log_t *log)
|
||||
{
|
||||
size_t buf_len;
|
||||
|
||||
if (b->last + len > b->end) {
|
||||
buf_len = 2 * (b->last - b->pos) + len;
|
||||
|
||||
b->start = ngx_alloc(buf_len, log);
|
||||
if (b->start == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
b->last = ngx_cpymem(b->start, b->pos, b->last - b->pos);
|
||||
b->pos = b->start;
|
||||
b->end = b->start + buf_len;
|
||||
}
|
||||
|
||||
b->last = ngx_cpymem(b->last, data, len);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_eval_append_var(void *ctx, ngx_buf_t *b, ngx_rtmp_eval_t **e,
|
||||
ngx_str_t *name, ngx_log_t *log)
|
||||
{
|
||||
ngx_uint_t k;
|
||||
ngx_str_t v;
|
||||
ngx_rtmp_eval_t *ee;
|
||||
|
||||
for (; *e; ++e) {
|
||||
for (k = 0, ee = *e; ee->handler; ++k, ++ee) {
|
||||
if (ee->name.len == name->len &&
|
||||
ngx_memcmp(ee->name.data, name->data, name->len) == 0)
|
||||
{
|
||||
ee->handler(ctx, ee, &v);
|
||||
ngx_rtmp_eval_append(b, v.data, v.len, log);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_eval(void *ctx, ngx_str_t *in, ngx_rtmp_eval_t **e, ngx_str_t *out,
|
||||
ngx_log_t *log)
|
||||
{
|
||||
u_char c, *p;
|
||||
ngx_str_t name;
|
||||
ngx_buf_t b;
|
||||
ngx_uint_t n;
|
||||
|
||||
enum {
|
||||
NORMAL,
|
||||
ESCAPE,
|
||||
NAME,
|
||||
SNAME
|
||||
} state = NORMAL;
|
||||
|
||||
b.pos = b.last = b.start = ngx_alloc(NGX_RTMP_EVAL_BUFLEN, log);
|
||||
if (b.pos == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
b.end = b.pos + NGX_RTMP_EVAL_BUFLEN;
|
||||
name.data = NULL;
|
||||
|
||||
for (n = 0; n < in->len; ++n) {
|
||||
p = &in->data[n];
|
||||
c = *p;
|
||||
|
||||
switch (state) {
|
||||
case SNAME:
|
||||
if (c != '}') {
|
||||
continue;
|
||||
}
|
||||
|
||||
name.len = p - name.data;
|
||||
ngx_rtmp_eval_append_var(ctx, &b, e, &name, log);
|
||||
|
||||
state = NORMAL;
|
||||
|
||||
continue;
|
||||
|
||||
case NAME:
|
||||
if (c == '{' && name.data == p) {
|
||||
++name.data;
|
||||
state = SNAME;
|
||||
continue;
|
||||
}
|
||||
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
name.len = p - name.data;
|
||||
ngx_rtmp_eval_append_var(ctx, &b, e, &name, log);
|
||||
/* fall through */
|
||||
|
||||
case NORMAL:
|
||||
switch (c) {
|
||||
case '$':
|
||||
name.data = p + 1;
|
||||
state = NAME;
|
||||
continue;
|
||||
case '\\':
|
||||
state = ESCAPE;
|
||||
continue;
|
||||
/* fall through */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
/* fall through */
|
||||
|
||||
case ESCAPE:
|
||||
ngx_rtmp_eval_append(&b, &c, 1, log);
|
||||
state = NORMAL;
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (state == NAME) {
|
||||
p = &in->data[n];
|
||||
name.len = p - name.data;
|
||||
ngx_rtmp_eval_append_var(ctx, &b, e, &name, log);
|
||||
}
|
||||
|
||||
c = 0;
|
||||
ngx_rtmp_eval_append(&b, &c, 1, log);
|
||||
|
||||
out->data = b.pos;
|
||||
out->len = b.last - b.pos - 1;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_eval_streams(ngx_str_t *in)
|
||||
{
|
||||
#if !(NGX_WIN32)
|
||||
ngx_int_t mode, create, v, close_src;
|
||||
ngx_fd_t dst, src;
|
||||
u_char *path;
|
||||
|
||||
path = in->data;
|
||||
|
||||
while (*path >= '0' && *path <= '9') {
|
||||
path++;
|
||||
}
|
||||
|
||||
switch ((char) *path) {
|
||||
|
||||
case '>':
|
||||
|
||||
v = (path == in->data ? 1 : ngx_atoi(in->data, path - in->data));
|
||||
if (v == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
dst = (ngx_fd_t) v;
|
||||
mode = NGX_FILE_WRONLY;
|
||||
create = NGX_FILE_TRUNCATE;
|
||||
path++;
|
||||
|
||||
if (*path == (u_char) '>') {
|
||||
mode = NGX_FILE_APPEND;
|
||||
create = NGX_FILE_CREATE_OR_OPEN;
|
||||
path++;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case '<':
|
||||
|
||||
v = (path == in->data ? 0 : ngx_atoi(in->data, path - in->data));
|
||||
if (v == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
dst = (ngx_fd_t) v;
|
||||
mode = NGX_FILE_RDONLY;
|
||||
create = NGX_FILE_OPEN;
|
||||
path++;
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
return NGX_DONE;
|
||||
}
|
||||
|
||||
if (*path == (u_char) '&') {
|
||||
|
||||
path++;
|
||||
v = ngx_atoi(path, in->data + in->len - path);
|
||||
if (v == NGX_ERROR) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
src = (ngx_fd_t) v;
|
||||
close_src = 0;
|
||||
|
||||
} else {
|
||||
|
||||
src = ngx_open_file(path, mode, create, NGX_FILE_DEFAULT_ACCESS);
|
||||
if (src == NGX_INVALID_FILE) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
close_src = 1;
|
||||
|
||||
}
|
||||
|
||||
if (src == dst) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
dup2(src, dst);
|
||||
|
||||
if (close_src) {
|
||||
ngx_close_file(src);
|
||||
}
|
||||
return NGX_OK;
|
||||
|
||||
#else
|
||||
return NGX_DONE;
|
||||
#endif
|
||||
}
|
44
ngx_rtmp_eval.h
Normal file
44
ngx_rtmp_eval.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_EVAL_H_INCLUDED_
|
||||
#define _NGX_RTMP_EVAL_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
||||
|
||||
typedef struct ngx_rtmp_eval_s ngx_rtmp_eval_t;
|
||||
|
||||
|
||||
typedef void (* ngx_rtmp_eval_pt)(void *ctx, ngx_rtmp_eval_t *e,
|
||||
ngx_str_t *ret);
|
||||
|
||||
|
||||
struct ngx_rtmp_eval_s {
|
||||
ngx_str_t name;
|
||||
ngx_rtmp_eval_pt handler;
|
||||
ngx_uint_t offset;
|
||||
};
|
||||
|
||||
|
||||
#define ngx_rtmp_null_eval { ngx_null_string, NULL, 0 }
|
||||
|
||||
|
||||
/* standard session eval variables */
|
||||
extern ngx_rtmp_eval_t ngx_rtmp_eval_session[];
|
||||
|
||||
|
||||
ngx_int_t ngx_rtmp_eval(void *ctx, ngx_str_t *in, ngx_rtmp_eval_t **e,
|
||||
ngx_str_t *out, ngx_log_t *log);
|
||||
|
||||
|
||||
ngx_int_t ngx_rtmp_eval_streams(ngx_str_t *in);
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_EVAL_H_INCLUDED_ */
|
1649
ngx_rtmp_exec_module.c
Normal file
1649
ngx_rtmp_exec_module.c
Normal file
File diff suppressed because it is too large
Load diff
675
ngx_rtmp_flv_module.c
Normal file
675
ngx_rtmp_flv_module.c
Normal file
|
@ -0,0 +1,675 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp_play_module.h"
|
||||
#include "ngx_rtmp_codec_module.h"
|
||||
#include "ngx_rtmp_streams.h"
|
||||
|
||||
|
||||
static ngx_int_t ngx_rtmp_flv_postconfiguration(ngx_conf_t *cf);
|
||||
static void ngx_rtmp_flv_read_meta(ngx_rtmp_session_t *s, ngx_file_t *f);
|
||||
static ngx_int_t ngx_rtmp_flv_timestamp_to_offset(ngx_rtmp_session_t *s,
|
||||
ngx_file_t *f, ngx_int_t timestamp);
|
||||
static ngx_int_t ngx_rtmp_flv_init(ngx_rtmp_session_t *s, ngx_file_t *f,
|
||||
ngx_int_t aindex, ngx_int_t vindex);
|
||||
static ngx_int_t ngx_rtmp_flv_start(ngx_rtmp_session_t *s, ngx_file_t *f);
|
||||
static ngx_int_t ngx_rtmp_flv_seek(ngx_rtmp_session_t *s, ngx_file_t *f,
|
||||
ngx_uint_t offset);
|
||||
static ngx_int_t ngx_rtmp_flv_stop(ngx_rtmp_session_t *s, ngx_file_t *f);
|
||||
static ngx_int_t ngx_rtmp_flv_send(ngx_rtmp_session_t *s, ngx_file_t *f,
|
||||
ngx_uint_t *ts);
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_uint_t nelts;
|
||||
ngx_uint_t offset;
|
||||
} ngx_rtmp_flv_index_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_int_t offset;
|
||||
ngx_int_t start_timestamp;
|
||||
ngx_event_t write_evt;
|
||||
uint32_t last_audio;
|
||||
uint32_t last_video;
|
||||
ngx_uint_t msg_mask;
|
||||
uint32_t epoch;
|
||||
|
||||
unsigned meta_read:1;
|
||||
ngx_rtmp_flv_index_t filepositions;
|
||||
ngx_rtmp_flv_index_t times;
|
||||
} ngx_rtmp_flv_ctx_t;
|
||||
|
||||
|
||||
#define NGX_RTMP_FLV_BUFFER (1024*1024)
|
||||
#define NGX_RTMP_FLV_BUFLEN_ADDON 1000
|
||||
#define NGX_RTMP_FLV_TAG_HEADER 11
|
||||
#define NGX_RTMP_FLV_DATA_OFFSET 13
|
||||
|
||||
|
||||
static u_char ngx_rtmp_flv_buffer[
|
||||
NGX_RTMP_FLV_BUFFER];
|
||||
static u_char ngx_rtmp_flv_header[
|
||||
NGX_RTMP_FLV_TAG_HEADER];
|
||||
|
||||
|
||||
static ngx_rtmp_module_t ngx_rtmp_flv_module_ctx = {
|
||||
NULL, /* preconfiguration */
|
||||
ngx_rtmp_flv_postconfiguration, /* postconfiguration */
|
||||
NULL, /* create main configuration */
|
||||
NULL, /* init main configuration */
|
||||
NULL, /* create server configuration */
|
||||
NULL, /* merge server configuration */
|
||||
NULL, /* create app configuration */
|
||||
NULL /* merge app configuration */
|
||||
};
|
||||
|
||||
|
||||
ngx_module_t ngx_rtmp_flv_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_rtmp_flv_module_ctx, /* module context */
|
||||
NULL, /* module directives */
|
||||
NGX_RTMP_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_flv_fill_index(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_flv_index_t *idx)
|
||||
{
|
||||
uint32_t nelts;
|
||||
ngx_buf_t *b;
|
||||
|
||||
/* we have AMF array pointed by context;
|
||||
* need to extract its size (4 bytes) &
|
||||
* save offset of actual array data */
|
||||
|
||||
b = ctx->link->buf;
|
||||
|
||||
if (b->last - b->pos < (ngx_int_t) ctx->offset + 4) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_rtmp_rmemcpy(&nelts, b->pos + ctx->offset, 4);
|
||||
|
||||
idx->nelts = nelts;
|
||||
idx->offset = ctx->offset + 4;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_flv_init_index(ngx_rtmp_session_t *s, ngx_chain_t *in)
|
||||
{
|
||||
ngx_rtmp_flv_ctx_t *ctx;
|
||||
|
||||
static ngx_rtmp_amf_ctx_t filepositions_ctx;
|
||||
static ngx_rtmp_amf_ctx_t times_ctx;
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_keyframes[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_ARRAY | NGX_RTMP_AMF_CONTEXT,
|
||||
ngx_string("filepositions"),
|
||||
&filepositions_ctx, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_ARRAY | NGX_RTMP_AMF_CONTEXT,
|
||||
ngx_string("times"),
|
||||
×_ctx, 0 }
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_inf[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_OBJECT,
|
||||
ngx_string("keyframes"),
|
||||
in_keyframes, sizeof(in_keyframes) }
|
||||
};
|
||||
|
||||
static ngx_rtmp_amf_elt_t in_elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
NULL, 0 },
|
||||
|
||||
{ NGX_RTMP_AMF_OBJECT,
|
||||
ngx_null_string,
|
||||
in_inf, sizeof(in_inf) },
|
||||
};
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
|
||||
|
||||
if (ctx == NULL || in == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: init index");
|
||||
|
||||
ngx_memzero(&filepositions_ctx, sizeof(filepositions_ctx));
|
||||
ngx_memzero(×_ctx, sizeof(times_ctx));
|
||||
|
||||
if (ngx_rtmp_receive_amf(s, in, in_elts,
|
||||
sizeof(in_elts) / sizeof(in_elts[0])))
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: init index error");
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (filepositions_ctx.link && ngx_rtmp_flv_fill_index(&filepositions_ctx,
|
||||
&ctx->filepositions)
|
||||
!= NGX_OK)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: failed to init filepositions");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: filepositions nelts=%ui offset=%ui",
|
||||
ctx->filepositions.nelts, ctx->filepositions.offset);
|
||||
|
||||
if (times_ctx.link && ngx_rtmp_flv_fill_index(×_ctx,
|
||||
&ctx->times)
|
||||
!= NGX_OK)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: failed to init times");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: times nelts=%ui offset=%ui",
|
||||
ctx->times.nelts, ctx->times.offset);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static double
|
||||
ngx_rtmp_flv_index_value(void *src)
|
||||
{
|
||||
double v;
|
||||
|
||||
ngx_rtmp_rmemcpy(&v, src, 8);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_flv_timestamp_to_offset(ngx_rtmp_session_t *s, ngx_file_t *f,
|
||||
ngx_int_t timestamp)
|
||||
{
|
||||
ngx_rtmp_flv_ctx_t *ctx;
|
||||
ssize_t n, size;
|
||||
ngx_uint_t offset, index, ret, nelts;
|
||||
double v;
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
|
||||
|
||||
if (ctx == NULL) {
|
||||
goto rewind;
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: lookup index start timestamp=%i",
|
||||
timestamp);
|
||||
|
||||
if (ctx->meta_read == 0) {
|
||||
ngx_rtmp_flv_read_meta(s, f);
|
||||
ctx->meta_read = 1;
|
||||
}
|
||||
|
||||
if (timestamp <= 0 || ctx->filepositions.nelts == 0
|
||||
|| ctx->times.nelts == 0)
|
||||
{
|
||||
goto rewind;
|
||||
}
|
||||
|
||||
/* read index table from file given offset */
|
||||
offset = NGX_RTMP_FLV_DATA_OFFSET + NGX_RTMP_FLV_TAG_HEADER +
|
||||
ctx->times.offset;
|
||||
|
||||
/* index should fit in the buffer */
|
||||
nelts = ngx_min(ctx->times.nelts, sizeof(ngx_rtmp_flv_buffer) / 9);
|
||||
size = nelts * 9;
|
||||
|
||||
n = ngx_read_file(f, ngx_rtmp_flv_buffer, size, offset);
|
||||
|
||||
if (n != size) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: could not read times index");
|
||||
goto rewind;
|
||||
}
|
||||
|
||||
/*TODO: implement binary search */
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: lookup times nelts=%ui", nelts);
|
||||
|
||||
for (index = 0; index < nelts - 1; ++index) {
|
||||
v = ngx_rtmp_flv_index_value(ngx_rtmp_flv_buffer +
|
||||
index * 9 + 1) * 1000;
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: lookup times index=%ui value=%ui",
|
||||
index, (ngx_uint_t) v);
|
||||
|
||||
if (timestamp < v) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index >= ctx->filepositions.nelts) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: index out of bounds: %ui>=%ui",
|
||||
index, ctx->filepositions.nelts);
|
||||
goto rewind;
|
||||
}
|
||||
|
||||
/* take value from filepositions */
|
||||
offset = NGX_RTMP_FLV_DATA_OFFSET + NGX_RTMP_FLV_TAG_HEADER +
|
||||
ctx->filepositions.offset + index * 9;
|
||||
|
||||
n = ngx_read_file(f, ngx_rtmp_flv_buffer, 8, offset + 1);
|
||||
|
||||
if (n != 8) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: could not read filepositions index");
|
||||
goto rewind;
|
||||
}
|
||||
|
||||
ret = (ngx_uint_t) ngx_rtmp_flv_index_value(ngx_rtmp_flv_buffer);
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: lookup index timestamp=%i offset=%ui",
|
||||
timestamp, ret);
|
||||
|
||||
return ret;
|
||||
|
||||
rewind:
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: lookup index timestamp=%i offset=begin",
|
||||
timestamp);
|
||||
|
||||
return NGX_RTMP_FLV_DATA_OFFSET;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_flv_read_meta(ngx_rtmp_session_t *s, ngx_file_t *f)
|
||||
{
|
||||
ngx_rtmp_flv_ctx_t *ctx;
|
||||
ssize_t n;
|
||||
ngx_rtmp_header_t h;
|
||||
ngx_chain_t *out, in;
|
||||
ngx_buf_t in_buf;
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
uint32_t size;
|
||||
|
||||
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
|
||||
|
||||
if (ctx == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: read meta");
|
||||
|
||||
/* read tag header */
|
||||
n = ngx_read_file(f, ngx_rtmp_flv_header, sizeof(ngx_rtmp_flv_header),
|
||||
NGX_RTMP_FLV_DATA_OFFSET);
|
||||
|
||||
if (n != sizeof(ngx_rtmp_flv_header)) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: could not read metadata tag header");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_flv_header[0] != NGX_RTMP_MSG_AMF_META) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: first tag is not metadata, giving up");
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_memzero(&h, sizeof(h));
|
||||
|
||||
h.type = NGX_RTMP_MSG_AMF_META;
|
||||
h.msid = NGX_RTMP_MSID;
|
||||
h.csid = NGX_RTMP_CSID_AMF;
|
||||
|
||||
size = 0;
|
||||
ngx_rtmp_rmemcpy(&size, ngx_rtmp_flv_header + 1, 3);
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: metadata size=%D", size);
|
||||
|
||||
if (size > sizeof(ngx_rtmp_flv_buffer)) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: too big metadata");
|
||||
return;
|
||||
}
|
||||
|
||||
/* read metadata */
|
||||
n = ngx_read_file(f, ngx_rtmp_flv_buffer, size,
|
||||
sizeof(ngx_rtmp_flv_header) +
|
||||
NGX_RTMP_FLV_DATA_OFFSET);
|
||||
|
||||
if (n != (ssize_t) size) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: could not read metadata");
|
||||
return;
|
||||
}
|
||||
|
||||
/* prepare input chain */
|
||||
ngx_memzero(&in, sizeof(in));
|
||||
ngx_memzero(&in_buf, sizeof(in_buf));
|
||||
|
||||
in.buf = &in_buf;
|
||||
in_buf.pos = ngx_rtmp_flv_buffer;
|
||||
in_buf.last = ngx_rtmp_flv_buffer + size;
|
||||
|
||||
ngx_rtmp_flv_init_index(s, &in);
|
||||
|
||||
/* output chain */
|
||||
out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in);
|
||||
|
||||
ngx_rtmp_prepare_message(s, &h, NULL, out);
|
||||
ngx_rtmp_send_message(s, out, 0);
|
||||
ngx_rtmp_free_shared_chain(cscf, out);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_flv_send(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t *ts)
|
||||
{
|
||||
ngx_rtmp_flv_ctx_t *ctx;
|
||||
uint32_t last_timestamp;
|
||||
ngx_rtmp_header_t h, lh;
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
ngx_chain_t *out, in;
|
||||
ngx_buf_t in_buf;
|
||||
ngx_int_t rc;
|
||||
ssize_t n;
|
||||
uint32_t buflen, end_timestamp, size;
|
||||
|
||||
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
|
||||
|
||||
if (ctx == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ctx->offset == -1) {
|
||||
ctx->offset = ngx_rtmp_flv_timestamp_to_offset(s, f,
|
||||
ctx->start_timestamp);
|
||||
ctx->start_timestamp = -1; /* set later from actual timestamp */
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: read tag at offset=%i", ctx->offset);
|
||||
|
||||
/* read tag header */
|
||||
n = ngx_read_file(f, ngx_rtmp_flv_header,
|
||||
sizeof(ngx_rtmp_flv_header), ctx->offset);
|
||||
|
||||
if (n != sizeof(ngx_rtmp_flv_header)) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: could not read flv tag header");
|
||||
return NGX_DONE;
|
||||
}
|
||||
|
||||
/* parse header fields */
|
||||
ngx_memzero(&h, sizeof(h));
|
||||
|
||||
h.msid = NGX_RTMP_MSID;
|
||||
h.type = ngx_rtmp_flv_header[0];
|
||||
|
||||
size = 0;
|
||||
|
||||
ngx_rtmp_rmemcpy(&size, ngx_rtmp_flv_header + 1, 3);
|
||||
ngx_rtmp_rmemcpy(&h.timestamp, ngx_rtmp_flv_header + 4, 3);
|
||||
|
||||
((u_char *) &h.timestamp)[3] = ngx_rtmp_flv_header[7];
|
||||
|
||||
ctx->offset += (sizeof(ngx_rtmp_flv_header) + size + 4);
|
||||
|
||||
last_timestamp = 0;
|
||||
|
||||
switch (h.type) {
|
||||
|
||||
case NGX_RTMP_MSG_AUDIO:
|
||||
h.csid = NGX_RTMP_CSID_AUDIO;
|
||||
last_timestamp = ctx->last_audio;
|
||||
ctx->last_audio = h.timestamp;
|
||||
break;
|
||||
|
||||
case NGX_RTMP_MSG_VIDEO:
|
||||
h.csid = NGX_RTMP_CSID_VIDEO;
|
||||
last_timestamp = ctx->last_video;
|
||||
ctx->last_video = h.timestamp;
|
||||
break;
|
||||
|
||||
default:
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: read tag type=%i size=%uD timestamp=%uD "
|
||||
"last_timestamp=%uD",
|
||||
(ngx_int_t) h.type,size, h.timestamp, last_timestamp);
|
||||
|
||||
lh = h;
|
||||
lh.timestamp = last_timestamp;
|
||||
|
||||
if (size > sizeof(ngx_rtmp_flv_buffer)) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: too big message: %D>%uz", size,
|
||||
sizeof(ngx_rtmp_flv_buffer));
|
||||
goto next;
|
||||
}
|
||||
|
||||
/* read tag body */
|
||||
n = ngx_read_file(f, ngx_rtmp_flv_buffer, size,
|
||||
ctx->offset - size - 4);
|
||||
|
||||
if (n != (ssize_t) size) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"flv: could not read flv tag");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/* prepare input chain */
|
||||
ngx_memzero(&in, sizeof(in));
|
||||
ngx_memzero(&in_buf, sizeof(in_buf));
|
||||
|
||||
in.buf = &in_buf;
|
||||
in_buf.pos = ngx_rtmp_flv_buffer;
|
||||
in_buf.last = ngx_rtmp_flv_buffer + size;
|
||||
|
||||
/* output chain */
|
||||
out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in);
|
||||
|
||||
ngx_rtmp_prepare_message(s, &h, ctx->msg_mask & (1 << h.type) ?
|
||||
&lh : NULL, out);
|
||||
rc = ngx_rtmp_send_message(s, out, 0);
|
||||
ngx_rtmp_free_shared_chain(cscf, out);
|
||||
|
||||
if (rc == NGX_AGAIN) {
|
||||
return NGX_AGAIN;
|
||||
}
|
||||
|
||||
if (rc != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ctx->msg_mask |= (1 << h.type);
|
||||
|
||||
next:
|
||||
if (ctx->start_timestamp == -1) {
|
||||
ctx->start_timestamp = h.timestamp;
|
||||
ctx->epoch = ngx_current_msec;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: start_timestamp=%i", ctx->start_timestamp);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
buflen = s->buflen + NGX_RTMP_FLV_BUFLEN_ADDON;
|
||||
|
||||
end_timestamp = (ngx_current_msec - ctx->epoch) +
|
||||
ctx->start_timestamp + buflen;
|
||||
|
||||
ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: %s wait=%D timestamp=%D end_timestamp=%D bufen=%i",
|
||||
h.timestamp > end_timestamp ? "schedule" : "advance",
|
||||
h.timestamp > end_timestamp ? h.timestamp - end_timestamp : 0,
|
||||
h.timestamp, end_timestamp, (ngx_int_t) buflen);
|
||||
|
||||
s->current_time = h.timestamp;
|
||||
|
||||
/* too much data sent; schedule timeout */
|
||||
if (h.timestamp > end_timestamp) {
|
||||
return h.timestamp - end_timestamp;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_flv_init(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_int_t aindex,
|
||||
ngx_int_t vindex)
|
||||
{
|
||||
ngx_rtmp_flv_ctx_t *ctx;
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
|
||||
|
||||
if (ctx == NULL) {
|
||||
ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_flv_ctx_t));
|
||||
|
||||
if (ctx == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_flv_module);
|
||||
}
|
||||
|
||||
ngx_memzero(ctx, sizeof(*ctx));
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_flv_start(ngx_rtmp_session_t *s, ngx_file_t *f)
|
||||
{
|
||||
ngx_rtmp_flv_ctx_t *ctx;
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
|
||||
|
||||
if (ctx == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: start");
|
||||
|
||||
ctx->offset = -1;
|
||||
ctx->msg_mask = 0;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_flv_seek(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t timestamp)
|
||||
{
|
||||
ngx_rtmp_flv_ctx_t *ctx;
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
|
||||
|
||||
if (ctx == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: seek timestamp=%ui", timestamp);
|
||||
|
||||
ctx->start_timestamp = timestamp;
|
||||
ctx->epoch = ngx_current_msec;
|
||||
ctx->offset = -1;
|
||||
ctx->msg_mask = 0;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_flv_stop(ngx_rtmp_session_t *s, ngx_file_t *f)
|
||||
{
|
||||
ngx_rtmp_flv_ctx_t *ctx;
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
|
||||
|
||||
if (ctx == NULL) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"flv: stop");
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_flv_postconfiguration(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_rtmp_play_main_conf_t *pmcf;
|
||||
ngx_rtmp_play_fmt_t **pfmt, *fmt;
|
||||
|
||||
pmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_play_module);
|
||||
|
||||
pfmt = ngx_array_push(&pmcf->fmts);
|
||||
|
||||
if (pfmt == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
fmt = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_fmt_t));
|
||||
|
||||
if (fmt == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
*pfmt = fmt;
|
||||
|
||||
ngx_str_set(&fmt->name, "flv-format");
|
||||
|
||||
ngx_str_null(&fmt->pfx); /* default fmt */
|
||||
ngx_str_set(&fmt->sfx, ".flv");
|
||||
|
||||
fmt->init = ngx_rtmp_flv_init;
|
||||
fmt->start = ngx_rtmp_flv_start;
|
||||
fmt->seek = ngx_rtmp_flv_seek;
|
||||
fmt->stop = ngx_rtmp_flv_stop;
|
||||
fmt->send = ngx_rtmp_flv_send;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
File diff suppressed because it is too large
Load diff
631
ngx_rtmp_handshake.c
Normal file
631
ngx_rtmp_handshake.c
Normal file
|
@ -0,0 +1,631 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
|
||||
static void ngx_rtmp_handshake_send(ngx_event_t *wev);
|
||||
static void ngx_rtmp_handshake_recv(ngx_event_t *rev);
|
||||
static void ngx_rtmp_handshake_done(ngx_rtmp_session_t *s);
|
||||
|
||||
|
||||
/* RTMP handshake :
|
||||
*
|
||||
* =peer1= =peer2=
|
||||
* challenge ----> (.....[digest1]......) ----> 1537 bytes
|
||||
* response <---- (...........[digest2]) <---- 1536 bytes
|
||||
*
|
||||
*
|
||||
* - both packets contain random bytes except for digests
|
||||
* - digest1 position is calculated on random packet bytes
|
||||
* - digest2 is always at the end of the packet
|
||||
*
|
||||
* digest1: HMAC_SHA256(packet, peer1_partial_key)
|
||||
* digest2: HMAC_SHA256(packet, HMAC_SHA256(digest1, peer2_full_key))
|
||||
*/
|
||||
|
||||
|
||||
/* Handshake keys */
|
||||
static u_char
|
||||
ngx_rtmp_server_key[] = {
|
||||
'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ',
|
||||
'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ',
|
||||
'S', 'e', 'r', 'v', 'e', 'r', ' ',
|
||||
'0', '0', '1',
|
||||
|
||||
0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1,
|
||||
0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,
|
||||
0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE
|
||||
};
|
||||
|
||||
|
||||
static u_char
|
||||
ngx_rtmp_client_key[] = {
|
||||
'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ',
|
||||
'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ',
|
||||
'0', '0', '1',
|
||||
|
||||
0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1,
|
||||
0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,
|
||||
0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE
|
||||
};
|
||||
|
||||
|
||||
static const u_char
|
||||
ngx_rtmp_server_version[4] = {
|
||||
0x0D, 0x0E, 0x0A, 0x0D
|
||||
};
|
||||
|
||||
|
||||
static const u_char
|
||||
ngx_rtmp_client_version[4] = {
|
||||
0x0C, 0x00, 0x0D, 0x0E
|
||||
};
|
||||
|
||||
|
||||
#define NGX_RTMP_HANDSHAKE_KEYLEN SHA256_DIGEST_LENGTH
|
||||
#define NGX_RTMP_HANDSHAKE_BUFSIZE 1537
|
||||
|
||||
|
||||
#define NGX_RTMP_HANDSHAKE_SERVER_RECV_CHALLENGE 1
|
||||
#define NGX_RTMP_HANDSHAKE_SERVER_SEND_CHALLENGE 2
|
||||
#define NGX_RTMP_HANDSHAKE_SERVER_SEND_RESPONSE 3
|
||||
#define NGX_RTMP_HANDSHAKE_SERVER_RECV_RESPONSE 4
|
||||
#define NGX_RTMP_HANDSHAKE_SERVER_DONE 5
|
||||
|
||||
|
||||
#define NGX_RTMP_HANDSHAKE_CLIENT_SEND_CHALLENGE 6
|
||||
#define NGX_RTMP_HANDSHAKE_CLIENT_RECV_CHALLENGE 7
|
||||
#define NGX_RTMP_HANDSHAKE_CLIENT_RECV_RESPONSE 8
|
||||
#define NGX_RTMP_HANDSHAKE_CLIENT_SEND_RESPONSE 9
|
||||
#define NGX_RTMP_HANDSHAKE_CLIENT_DONE 10
|
||||
|
||||
|
||||
static ngx_str_t ngx_rtmp_server_full_key
|
||||
= { sizeof(ngx_rtmp_server_key), ngx_rtmp_server_key };
|
||||
static ngx_str_t ngx_rtmp_server_partial_key
|
||||
= { 36, ngx_rtmp_server_key };
|
||||
|
||||
static ngx_str_t ngx_rtmp_client_full_key
|
||||
= { sizeof(ngx_rtmp_client_key), ngx_rtmp_client_key };
|
||||
static ngx_str_t ngx_rtmp_client_partial_key
|
||||
= { 30, ngx_rtmp_client_key };
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_make_digest(ngx_str_t *key, ngx_buf_t *src,
|
||||
u_char *skip, u_char *dst, ngx_log_t *log)
|
||||
{
|
||||
static HMAC_CTX *hmac;
|
||||
unsigned int len;
|
||||
|
||||
if (hmac == NULL) {
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
static HMAC_CTX shmac;
|
||||
hmac = &shmac;
|
||||
HMAC_CTX_init(hmac);
|
||||
#else
|
||||
hmac = HMAC_CTX_new();
|
||||
if (hmac == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
HMAC_Init_ex(hmac, key->data, key->len, EVP_sha256(), NULL);
|
||||
|
||||
if (skip && src->pos <= skip && skip <= src->last) {
|
||||
if (skip != src->pos) {
|
||||
HMAC_Update(hmac, src->pos, skip - src->pos);
|
||||
}
|
||||
if (src->last != skip + NGX_RTMP_HANDSHAKE_KEYLEN) {
|
||||
HMAC_Update(hmac, skip + NGX_RTMP_HANDSHAKE_KEYLEN,
|
||||
src->last - skip - NGX_RTMP_HANDSHAKE_KEYLEN);
|
||||
}
|
||||
} else {
|
||||
HMAC_Update(hmac, src->pos, src->last - src->pos);
|
||||
}
|
||||
|
||||
HMAC_Final(hmac, dst, &len);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_find_digest(ngx_buf_t *b, ngx_str_t *key, size_t base, ngx_log_t *log)
|
||||
{
|
||||
size_t n, offs;
|
||||
u_char digest[NGX_RTMP_HANDSHAKE_KEYLEN];
|
||||
u_char *p;
|
||||
|
||||
offs = 0;
|
||||
for (n = 0; n < 4; ++n) {
|
||||
offs += b->pos[base + n];
|
||||
}
|
||||
offs = (offs % 728) + base + 4;
|
||||
p = b->pos + offs;
|
||||
|
||||
if (ngx_rtmp_make_digest(key, b, p, digest, log) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_memcmp(digest, p, NGX_RTMP_HANDSHAKE_KEYLEN) == 0) {
|
||||
return offs;
|
||||
}
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_write_digest(ngx_buf_t *b, ngx_str_t *key, size_t base,
|
||||
ngx_log_t *log)
|
||||
{
|
||||
size_t n, offs;
|
||||
u_char *p;
|
||||
|
||||
offs = 0;
|
||||
for (n = 8; n < 12; ++n) {
|
||||
offs += b->pos[base + n];
|
||||
}
|
||||
offs = (offs % 728) + base + 12;
|
||||
p = b->pos + offs;
|
||||
|
||||
if (ngx_rtmp_make_digest(key, b, p, p, log) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_fill_random_buffer(ngx_buf_t *b)
|
||||
{
|
||||
for (; b->last != b->end; ++b->last) {
|
||||
*b->last = (u_char) rand();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static ngx_buf_t *
|
||||
ngx_rtmp_alloc_handshake_buffer(ngx_rtmp_session_t *s)
|
||||
{
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
ngx_chain_t *cl;
|
||||
ngx_buf_t *b;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"handshake: allocating buffer");
|
||||
|
||||
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
|
||||
|
||||
if (cscf->free_hs) {
|
||||
cl = cscf->free_hs;
|
||||
b = cl->buf;
|
||||
cscf->free_hs = cl->next;
|
||||
ngx_free_chain(cscf->pool, cl);
|
||||
|
||||
} else {
|
||||
b = ngx_pcalloc(cscf->pool, sizeof(ngx_buf_t));
|
||||
if (b == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
b->memory = 1;
|
||||
b->start = ngx_pcalloc(cscf->pool, NGX_RTMP_HANDSHAKE_BUFSIZE);
|
||||
if (b->start == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
b->end = b->start + NGX_RTMP_HANDSHAKE_BUFSIZE;
|
||||
}
|
||||
|
||||
b->pos = b->last = b->start;
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_rtmp_free_handshake_buffers(ngx_rtmp_session_t *s)
|
||||
{
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
ngx_chain_t *cl;
|
||||
|
||||
if (s->hs_buf == NULL) {
|
||||
return;
|
||||
}
|
||||
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
|
||||
cl = ngx_alloc_chain_link(cscf->pool);
|
||||
if (cl == NULL) {
|
||||
return;
|
||||
}
|
||||
cl->buf = s->hs_buf;
|
||||
cl->next = cscf->free_hs;
|
||||
cscf->free_hs = cl;
|
||||
s->hs_buf = NULL;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_handshake_create_challenge(ngx_rtmp_session_t *s,
|
||||
const u_char version[4], ngx_str_t *key)
|
||||
{
|
||||
ngx_buf_t *b;
|
||||
|
||||
b = s->hs_buf;
|
||||
b->last = b->pos = b->start;
|
||||
*b->last++ = '\x03';
|
||||
b->last = ngx_rtmp_rcpymem(b->last, &s->epoch, 4);
|
||||
b->last = ngx_cpymem(b->last, version, 4);
|
||||
ngx_rtmp_fill_random_buffer(b);
|
||||
++b->pos;
|
||||
if (ngx_rtmp_write_digest(b, key, 0, s->connection->log) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
--b->pos;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_handshake_parse_challenge(ngx_rtmp_session_t *s,
|
||||
ngx_str_t *peer_key, ngx_str_t *key)
|
||||
{
|
||||
ngx_buf_t *b;
|
||||
u_char *p;
|
||||
ngx_int_t offs;
|
||||
|
||||
b = s->hs_buf;
|
||||
if (*b->pos != '\x03') {
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"handshake: unexpected RTMP version: %i",
|
||||
(ngx_int_t)*b->pos);
|
||||
return NGX_ERROR;
|
||||
}
|
||||
++b->pos;
|
||||
s->peer_epoch = 0;
|
||||
ngx_rtmp_rmemcpy(&s->peer_epoch, b->pos, 4);
|
||||
|
||||
p = b->pos + 4;
|
||||
ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"handshake: peer version=%i.%i.%i.%i epoch=%uD",
|
||||
(ngx_int_t)p[3], (ngx_int_t)p[2],
|
||||
(ngx_int_t)p[1], (ngx_int_t)p[0],
|
||||
(uint32_t)s->peer_epoch);
|
||||
if (*(uint32_t *)p == 0) {
|
||||
s->hs_old = 1;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
offs = ngx_rtmp_find_digest(b, peer_key, 772, s->connection->log);
|
||||
if (offs == NGX_ERROR) {
|
||||
offs = ngx_rtmp_find_digest(b, peer_key, 8, s->connection->log);
|
||||
}
|
||||
if (offs == NGX_ERROR) {
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"handshake: digest not found");
|
||||
s->hs_old = 1;
|
||||
return NGX_OK;
|
||||
}
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"handshake: digest found at pos=%i", offs);
|
||||
b->pos += offs;
|
||||
b->last = b->pos + NGX_RTMP_HANDSHAKE_KEYLEN;
|
||||
s->hs_digest = ngx_palloc(s->connection->pool, NGX_RTMP_HANDSHAKE_KEYLEN);
|
||||
if (ngx_rtmp_make_digest(key, b, NULL, s->hs_digest, s->connection->log)
|
||||
!= NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_handshake_create_response(ngx_rtmp_session_t *s)
|
||||
{
|
||||
ngx_buf_t *b;
|
||||
u_char *p;
|
||||
ngx_str_t key;
|
||||
|
||||
b = s->hs_buf;
|
||||
b->pos = b->last = b->start + 1;
|
||||
ngx_rtmp_fill_random_buffer(b);
|
||||
if (s->hs_digest) {
|
||||
p = b->last - NGX_RTMP_HANDSHAKE_KEYLEN;
|
||||
key.data = s->hs_digest;
|
||||
key.len = NGX_RTMP_HANDSHAKE_KEYLEN;
|
||||
if (ngx_rtmp_make_digest(&key, b, p, p, s->connection->log) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_handshake_done(ngx_rtmp_session_t *s)
|
||||
{
|
||||
ngx_rtmp_free_handshake_buffers(s);
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"handshake: done");
|
||||
|
||||
if (ngx_rtmp_fire_event(s, NGX_RTMP_HANDSHAKE_DONE,
|
||||
NULL, NULL) != NGX_OK)
|
||||
{
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_rtmp_cycle(s);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_handshake_recv(ngx_event_t *rev)
|
||||
{
|
||||
ssize_t n;
|
||||
ngx_connection_t *c;
|
||||
ngx_rtmp_session_t *s;
|
||||
ngx_buf_t *b;
|
||||
|
||||
c = rev->data;
|
||||
s = c->data;
|
||||
|
||||
if (c->destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (rev->timedout) {
|
||||
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
|
||||
"handshake: recv: client timed out");
|
||||
c->timedout = 1;
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rev->timer_set) {
|
||||
ngx_del_timer(rev);
|
||||
}
|
||||
|
||||
b = s->hs_buf;
|
||||
|
||||
while (b->last != b->end) {
|
||||
n = c->recv(c, b->last, b->end - b->last);
|
||||
|
||||
if (n == NGX_ERROR || n == 0) {
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return;
|
||||
}
|
||||
|
||||
if (n == NGX_AGAIN) {
|
||||
ngx_add_timer(rev, s->timeout);
|
||||
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
|
||||
ngx_rtmp_finalize_session(s);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
b->last += n;
|
||||
}
|
||||
|
||||
if (rev->active) {
|
||||
ngx_del_event(rev, NGX_READ_EVENT, 0);
|
||||
}
|
||||
|
||||
++s->hs_stage;
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"handshake: stage %ui", s->hs_stage);
|
||||
|
||||
switch (s->hs_stage) {
|
||||
case NGX_RTMP_HANDSHAKE_SERVER_SEND_CHALLENGE:
|
||||
if (ngx_rtmp_handshake_parse_challenge(s,
|
||||
&ngx_rtmp_client_partial_key,
|
||||
&ngx_rtmp_server_full_key) != NGX_OK)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_INFO, c->log, 0,
|
||||
"handshake: error parsing challenge");
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return;
|
||||
}
|
||||
if (s->hs_old) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"handshake: old-style challenge");
|
||||
s->hs_buf->pos = s->hs_buf->start;
|
||||
s->hs_buf->last = s->hs_buf->end;
|
||||
} else if (ngx_rtmp_handshake_create_challenge(s,
|
||||
ngx_rtmp_server_version,
|
||||
&ngx_rtmp_server_partial_key) != NGX_OK)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_INFO, c->log, 0,
|
||||
"handshake: error creating challenge");
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return;
|
||||
}
|
||||
ngx_rtmp_handshake_send(c->write);
|
||||
break;
|
||||
|
||||
case NGX_RTMP_HANDSHAKE_SERVER_DONE:
|
||||
ngx_rtmp_handshake_done(s);
|
||||
break;
|
||||
|
||||
case NGX_RTMP_HANDSHAKE_CLIENT_RECV_RESPONSE:
|
||||
if (ngx_rtmp_handshake_parse_challenge(s,
|
||||
&ngx_rtmp_server_partial_key,
|
||||
&ngx_rtmp_client_full_key) != NGX_OK)
|
||||
{
|
||||
ngx_log_error(NGX_LOG_INFO, c->log, 0,
|
||||
"handshake: error parsing challenge");
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return;
|
||||
}
|
||||
s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start + 1;
|
||||
ngx_rtmp_handshake_recv(c->read);
|
||||
break;
|
||||
|
||||
case NGX_RTMP_HANDSHAKE_CLIENT_SEND_RESPONSE:
|
||||
if (ngx_rtmp_handshake_create_response(s) != NGX_OK) {
|
||||
ngx_log_error(NGX_LOG_INFO, c->log, 0,
|
||||
"handshake: response error");
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return;
|
||||
}
|
||||
ngx_rtmp_handshake_send(c->write);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_handshake_send(ngx_event_t *wev)
|
||||
{
|
||||
ngx_int_t n;
|
||||
ngx_connection_t *c;
|
||||
ngx_rtmp_session_t *s;
|
||||
ngx_buf_t *b;
|
||||
|
||||
c = wev->data;
|
||||
s = c->data;
|
||||
|
||||
if (c->destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (wev->timedout) {
|
||||
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
|
||||
"handshake: send: client timed out");
|
||||
c->timedout = 1;
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return;
|
||||
}
|
||||
|
||||
if (wev->timer_set) {
|
||||
ngx_del_timer(wev);
|
||||
}
|
||||
|
||||
b = s->hs_buf;
|
||||
|
||||
while(b->pos != b->last) {
|
||||
n = c->send(c, b->pos, b->last - b->pos);
|
||||
|
||||
if (n == NGX_ERROR) {
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return;
|
||||
}
|
||||
|
||||
if (n == NGX_AGAIN || n == 0) {
|
||||
ngx_add_timer(c->write, s->timeout);
|
||||
if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
|
||||
ngx_rtmp_finalize_session(s);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
b->pos += n;
|
||||
}
|
||||
|
||||
if (wev->active) {
|
||||
ngx_del_event(wev, NGX_WRITE_EVENT, 0);
|
||||
}
|
||||
|
||||
++s->hs_stage;
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"handshake: stage %ui", s->hs_stage);
|
||||
|
||||
switch (s->hs_stage) {
|
||||
case NGX_RTMP_HANDSHAKE_SERVER_SEND_RESPONSE:
|
||||
if (s->hs_old) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"handshake: old-style response");
|
||||
s->hs_buf->pos = s->hs_buf->start + 1;
|
||||
s->hs_buf->last = s->hs_buf->end;
|
||||
} else if (ngx_rtmp_handshake_create_response(s) != NGX_OK) {
|
||||
ngx_log_error(NGX_LOG_INFO, c->log, 0,
|
||||
"handshake: response error");
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return;
|
||||
}
|
||||
ngx_rtmp_handshake_send(wev);
|
||||
break;
|
||||
|
||||
case NGX_RTMP_HANDSHAKE_SERVER_RECV_RESPONSE:
|
||||
s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start + 1;
|
||||
ngx_rtmp_handshake_recv(c->read);
|
||||
break;
|
||||
|
||||
case NGX_RTMP_HANDSHAKE_CLIENT_RECV_CHALLENGE:
|
||||
s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start;
|
||||
ngx_rtmp_handshake_recv(c->read);
|
||||
break;
|
||||
|
||||
case NGX_RTMP_HANDSHAKE_CLIENT_DONE:
|
||||
ngx_rtmp_handshake_done(s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_rtmp_handshake(ngx_rtmp_session_t *s)
|
||||
{
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = s->connection;
|
||||
c->read->handler = ngx_rtmp_handshake_recv;
|
||||
c->write->handler = ngx_rtmp_handshake_send;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"handshake: start server handshake");
|
||||
|
||||
s->hs_buf = ngx_rtmp_alloc_handshake_buffer(s);
|
||||
s->hs_stage = NGX_RTMP_HANDSHAKE_SERVER_RECV_CHALLENGE;
|
||||
|
||||
ngx_rtmp_handshake_recv(c->read);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_rtmp_client_handshake(ngx_rtmp_session_t *s, unsigned async)
|
||||
{
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = s->connection;
|
||||
c->read->handler = ngx_rtmp_handshake_recv;
|
||||
c->write->handler = ngx_rtmp_handshake_send;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"handshake: start client handshake");
|
||||
|
||||
s->hs_buf = ngx_rtmp_alloc_handshake_buffer(s);
|
||||
s->hs_stage = NGX_RTMP_HANDSHAKE_CLIENT_SEND_CHALLENGE;
|
||||
|
||||
if (ngx_rtmp_handshake_create_challenge(s,
|
||||
ngx_rtmp_client_version,
|
||||
&ngx_rtmp_client_partial_key) != NGX_OK)
|
||||
{
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return;
|
||||
}
|
||||
|
||||
if (async) {
|
||||
ngx_add_timer(c->write, s->timeout);
|
||||
if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
|
||||
ngx_rtmp_finalize_session(s);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_rtmp_handshake_send(c->write);
|
||||
}
|
||||
|
330
ngx_rtmp_init.c
Normal file
330
ngx_rtmp_init.c
Normal file
|
@ -0,0 +1,330 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
#include "ngx_rtmp_proxy_protocol.h"
|
||||
|
||||
|
||||
static void ngx_rtmp_close_connection(ngx_connection_t *c);
|
||||
static u_char * ngx_rtmp_log_error(ngx_log_t *log, u_char *buf, size_t len);
|
||||
|
||||
|
||||
void
|
||||
ngx_rtmp_init_connection(ngx_connection_t *c)
|
||||
{
|
||||
ngx_uint_t i;
|
||||
ngx_rtmp_port_t *port;
|
||||
struct sockaddr *sa;
|
||||
struct sockaddr_in *sin;
|
||||
ngx_rtmp_in_addr_t *addr;
|
||||
ngx_rtmp_session_t *s;
|
||||
ngx_rtmp_addr_conf_t *addr_conf;
|
||||
ngx_int_t unix_socket;
|
||||
#if (NGX_HAVE_INET6)
|
||||
struct sockaddr_in6 *sin6;
|
||||
ngx_rtmp_in6_addr_t *addr6;
|
||||
#endif
|
||||
|
||||
++ngx_rtmp_naccepted;
|
||||
|
||||
/* find the server configuration for the address:port */
|
||||
|
||||
/* AF_INET only */
|
||||
|
||||
port = c->listening->servers;
|
||||
unix_socket = 0;
|
||||
|
||||
if (port->naddrs > 1) {
|
||||
|
||||
/*
|
||||
* There are several addresses on this port and one of them
|
||||
* is the "*:port" wildcard so getsockname() is needed to determine
|
||||
* the server address.
|
||||
*
|
||||
* AcceptEx() already gave this address.
|
||||
*/
|
||||
|
||||
if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {
|
||||
ngx_rtmp_close_connection(c);
|
||||
return;
|
||||
}
|
||||
|
||||
sa = c->local_sockaddr;
|
||||
|
||||
switch (sa->sa_family) {
|
||||
|
||||
#if (NGX_HAVE_INET6)
|
||||
case AF_INET6:
|
||||
sin6 = (struct sockaddr_in6 *) sa;
|
||||
|
||||
addr6 = port->addrs;
|
||||
|
||||
/* the last address is "*" */
|
||||
|
||||
for (i = 0; i < port->naddrs - 1; i++) {
|
||||
if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
addr_conf = &addr6[i].conf;
|
||||
|
||||
break;
|
||||
#endif
|
||||
case AF_UNIX:
|
||||
unix_socket = 1;
|
||||
/* fall through */
|
||||
|
||||
default: /* AF_INET */
|
||||
sin = (struct sockaddr_in *) sa;
|
||||
|
||||
addr = port->addrs;
|
||||
|
||||
/* the last address is "*" */
|
||||
|
||||
for (i = 0; i < port->naddrs - 1; i++) {
|
||||
if (addr[i].addr == sin->sin_addr.s_addr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
addr_conf = &addr[i].conf;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
switch (c->local_sockaddr->sa_family) {
|
||||
|
||||
#if (NGX_HAVE_INET6)
|
||||
case AF_INET6:
|
||||
addr6 = port->addrs;
|
||||
addr_conf = &addr6[0].conf;
|
||||
break;
|
||||
#endif
|
||||
|
||||
case AF_UNIX:
|
||||
unix_socket = 1;
|
||||
/* fall through */
|
||||
|
||||
default: /* AF_INET */
|
||||
addr = port->addrs;
|
||||
addr_conf = &addr[0].conf;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, c->log, 0, "*%ui client connected '%V'",
|
||||
c->number, &c->addr_text);
|
||||
|
||||
s = ngx_rtmp_init_session(c, addr_conf);
|
||||
if (s == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* only auto-pushed connections are
|
||||
* done through unix socket */
|
||||
|
||||
s->auto_pushed = unix_socket;
|
||||
|
||||
if (addr_conf->proxy_protocol) {
|
||||
ngx_rtmp_proxy_protocol(s);
|
||||
|
||||
} else {
|
||||
ngx_rtmp_handshake(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_rtmp_session_t *
|
||||
ngx_rtmp_init_session(ngx_connection_t *c, ngx_rtmp_addr_conf_t *addr_conf)
|
||||
{
|
||||
ngx_rtmp_session_t *s;
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
ngx_rtmp_error_log_ctx_t *ctx;
|
||||
|
||||
s = ngx_pcalloc(c->pool, sizeof(ngx_rtmp_session_t) +
|
||||
sizeof(ngx_chain_t *) * ((ngx_rtmp_core_srv_conf_t *)
|
||||
addr_conf->ctx-> srv_conf[ngx_rtmp_core_module
|
||||
.ctx_index])->out_queue);
|
||||
if (s == NULL) {
|
||||
ngx_rtmp_close_connection(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s->main_conf = addr_conf->ctx->main_conf;
|
||||
s->srv_conf = addr_conf->ctx->srv_conf;
|
||||
|
||||
s->addr_text = &addr_conf->addr_text;
|
||||
|
||||
c->data = s;
|
||||
s->connection = c;
|
||||
|
||||
ctx = ngx_palloc(c->pool, sizeof(ngx_rtmp_error_log_ctx_t));
|
||||
if (ctx == NULL) {
|
||||
ngx_rtmp_close_connection(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ctx->client = &c->addr_text;
|
||||
ctx->session = s;
|
||||
|
||||
c->log->connection = c->number;
|
||||
c->log->handler = ngx_rtmp_log_error;
|
||||
c->log->data = ctx;
|
||||
c->log->action = NULL;
|
||||
|
||||
c->log_error = NGX_ERROR_INFO;
|
||||
|
||||
s->ctx = ngx_pcalloc(c->pool, sizeof(void *) * ngx_rtmp_max_module);
|
||||
if (s->ctx == NULL) {
|
||||
ngx_rtmp_close_connection(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
|
||||
|
||||
s->out_queue = cscf->out_queue;
|
||||
s->out_cork = cscf->out_cork;
|
||||
s->in_streams = ngx_pcalloc(c->pool, sizeof(ngx_rtmp_stream_t)
|
||||
* cscf->max_streams);
|
||||
if (s->in_streams == NULL) {
|
||||
ngx_rtmp_close_connection(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if (nginx_version >= 1007005)
|
||||
ngx_queue_init(&s->posted_dry_events);
|
||||
#endif
|
||||
|
||||
s->epoch = ngx_current_msec;
|
||||
s->timeout = cscf->timeout;
|
||||
s->buflen = cscf->buflen;
|
||||
ngx_rtmp_set_chunk_size(s, NGX_RTMP_DEFAULT_CHUNK_SIZE);
|
||||
|
||||
|
||||
if (ngx_rtmp_fire_event(s, NGX_RTMP_CONNECT, NULL, NULL) != NGX_OK) {
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
static u_char *
|
||||
ngx_rtmp_log_error(ngx_log_t *log, u_char *buf, size_t len)
|
||||
{
|
||||
u_char *p;
|
||||
ngx_rtmp_session_t *s;
|
||||
ngx_rtmp_error_log_ctx_t *ctx;
|
||||
|
||||
if (log->action) {
|
||||
p = ngx_snprintf(buf, len, " while %s", log->action);
|
||||
len -= p - buf;
|
||||
buf = p;
|
||||
}
|
||||
|
||||
ctx = log->data;
|
||||
|
||||
p = ngx_snprintf(buf, len, ", client: %V", ctx->client);
|
||||
len -= p - buf;
|
||||
buf = p;
|
||||
|
||||
s = ctx->session;
|
||||
|
||||
if (s == NULL) {
|
||||
return p;
|
||||
}
|
||||
|
||||
p = ngx_snprintf(buf, len, ", server: %V", s->addr_text);
|
||||
len -= p - buf;
|
||||
buf = p;
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_close_connection(ngx_connection_t *c)
|
||||
{
|
||||
ngx_pool_t *pool;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, "close connection");
|
||||
|
||||
#if (NGX_STAT_STUB)
|
||||
(void) ngx_atomic_fetch_add(ngx_stat_active, -1);
|
||||
#endif
|
||||
|
||||
pool = c->pool;
|
||||
ngx_close_connection(c);
|
||||
ngx_destroy_pool(pool);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_close_session_handler(ngx_event_t *e)
|
||||
{
|
||||
ngx_rtmp_session_t *s;
|
||||
ngx_connection_t *c;
|
||||
ngx_rtmp_core_srv_conf_t *cscf;
|
||||
|
||||
s = e->data;
|
||||
c = s->connection;
|
||||
|
||||
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, "close session");
|
||||
|
||||
ngx_rtmp_fire_event(s, NGX_RTMP_DISCONNECT, NULL, NULL);
|
||||
|
||||
if (s->ping_evt.timer_set) {
|
||||
ngx_del_timer(&s->ping_evt);
|
||||
}
|
||||
|
||||
if (s->in_old_pool) {
|
||||
ngx_destroy_pool(s->in_old_pool);
|
||||
}
|
||||
|
||||
if (s->in_pool) {
|
||||
ngx_destroy_pool(s->in_pool);
|
||||
}
|
||||
|
||||
ngx_rtmp_free_handshake_buffers(s);
|
||||
|
||||
while (s->out_pos != s->out_last) {
|
||||
ngx_rtmp_free_shared_chain(cscf, s->out[s->out_pos++]);
|
||||
s->out_pos %= s->out_queue;
|
||||
}
|
||||
|
||||
ngx_rtmp_close_connection(c);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_rtmp_finalize_session(ngx_rtmp_session_t *s)
|
||||
{
|
||||
ngx_event_t *e;
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = s->connection;
|
||||
if (c->destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, "finalize session");
|
||||
|
||||
c->destroyed = 1;
|
||||
e = &s->close;
|
||||
e->data = s;
|
||||
e->handler = ngx_rtmp_close_session_handler;
|
||||
e->log = c->log;
|
||||
|
||||
ngx_post_event(e, &ngx_posted_events);
|
||||
}
|
||||
|
205
ngx_rtmp_limit_module.c
Normal file
205
ngx_rtmp_limit_module.c
Normal file
|
@ -0,0 +1,205 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_int_t max_conn;
|
||||
ngx_shm_zone_t *shm_zone;
|
||||
} ngx_rtmp_limit_main_conf_t;
|
||||
|
||||
|
||||
static ngx_str_t shm_name = ngx_string("rtmp_limit");
|
||||
|
||||
|
||||
static ngx_int_t ngx_rtmp_limit_postconfiguration(ngx_conf_t *cf);
|
||||
static void *ngx_rtmp_limit_create_main_conf(ngx_conf_t *cf);
|
||||
|
||||
|
||||
static ngx_command_t ngx_rtmp_limit_commands[] = {
|
||||
|
||||
{ ngx_string("max_connections"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_num_slot,
|
||||
NGX_RTMP_MAIN_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_limit_main_conf_t, max_conn),
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
||||
static ngx_rtmp_module_t ngx_rtmp_limit_module_ctx = {
|
||||
NULL, /* preconfiguration */
|
||||
ngx_rtmp_limit_postconfiguration, /* postconfiguration */
|
||||
ngx_rtmp_limit_create_main_conf, /* create main configuration */
|
||||
NULL, /* init main configuration */
|
||||
NULL, /* create server configuration */
|
||||
NULL, /* merge server configuration */
|
||||
NULL, /* create app configuration */
|
||||
NULL /* merge app configuration */
|
||||
};
|
||||
|
||||
|
||||
ngx_module_t ngx_rtmp_limit_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_rtmp_limit_module_ctx, /* module context */
|
||||
ngx_rtmp_limit_commands, /* module directives */
|
||||
NGX_RTMP_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
static void *
|
||||
ngx_rtmp_limit_create_main_conf(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_rtmp_limit_main_conf_t *lmcf;
|
||||
|
||||
lmcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_limit_main_conf_t));
|
||||
if (lmcf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
lmcf->max_conn = NGX_CONF_UNSET;
|
||||
|
||||
return lmcf;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_limit_connect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
ngx_rtmp_limit_main_conf_t *lmcf;
|
||||
ngx_slab_pool_t *shpool;
|
||||
ngx_shm_zone_t *shm_zone;
|
||||
uint32_t *nconn, n;
|
||||
ngx_int_t rc;
|
||||
|
||||
lmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_limit_module);
|
||||
if (lmcf->max_conn == NGX_CONF_UNSET) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
shm_zone = lmcf->shm_zone;
|
||||
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
|
||||
nconn = shm_zone->data;
|
||||
|
||||
ngx_shmtx_lock(&shpool->mutex);
|
||||
n = ++*nconn;
|
||||
ngx_shmtx_unlock(&shpool->mutex);
|
||||
|
||||
rc = n > (ngx_uint_t) lmcf->max_conn ? NGX_ERROR : NGX_OK;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"limit: inc conection counter: %uD", n);
|
||||
|
||||
if (rc != NGX_OK) {
|
||||
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
||||
"limit: too many connections: %uD > %i",
|
||||
n, lmcf->max_conn);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_limit_disconnect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
ngx_rtmp_limit_main_conf_t *lmcf;
|
||||
ngx_slab_pool_t *shpool;
|
||||
ngx_shm_zone_t *shm_zone;
|
||||
uint32_t *nconn, n;
|
||||
|
||||
lmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_limit_module);
|
||||
if (lmcf->max_conn == NGX_CONF_UNSET) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
shm_zone = lmcf->shm_zone;
|
||||
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
|
||||
nconn = shm_zone->data;
|
||||
|
||||
ngx_shmtx_lock(&shpool->mutex);
|
||||
n = --*nconn;
|
||||
ngx_shmtx_unlock(&shpool->mutex);
|
||||
|
||||
(void) n;
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"limit: dec conection counter: %uD", n);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_limit_shm_init(ngx_shm_zone_t *shm_zone, void *data)
|
||||
{
|
||||
ngx_slab_pool_t *shpool;
|
||||
uint32_t *nconn;
|
||||
|
||||
if (data) {
|
||||
shm_zone->data = data;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
|
||||
|
||||
nconn = ngx_slab_alloc(shpool, 4);
|
||||
if (nconn == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
*nconn = 0;
|
||||
|
||||
shm_zone->data = nconn;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_limit_postconfiguration(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_rtmp_core_main_conf_t *cmcf;
|
||||
ngx_rtmp_limit_main_conf_t *lmcf;
|
||||
ngx_rtmp_handler_pt *h;
|
||||
|
||||
cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);
|
||||
|
||||
h = ngx_array_push(&cmcf->events[NGX_RTMP_CONNECT]);
|
||||
*h = ngx_rtmp_limit_connect;
|
||||
|
||||
h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]);
|
||||
*h = ngx_rtmp_limit_disconnect;
|
||||
|
||||
lmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_limit_module);
|
||||
if (lmcf->max_conn == NGX_CONF_UNSET) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
lmcf->shm_zone = ngx_shared_memory_add(cf, &shm_name, ngx_pagesize * 2,
|
||||
&ngx_rtmp_limit_module);
|
||||
if (lmcf->shm_zone == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
lmcf->shm_zone->init = ngx_rtmp_limit_shm_init;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
1528
ngx_rtmp_live_module.c
Normal file
1528
ngx_rtmp_live_module.c
Normal file
File diff suppressed because it is too large
Load diff
84
ngx_rtmp_live_module.h
Normal file
84
ngx_rtmp_live_module.h
Normal file
|
@ -0,0 +1,84 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_LIVE_H_INCLUDED_
|
||||
#define _NGX_RTMP_LIVE_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
#include "ngx_rtmp_cmd_module.h"
|
||||
#include "ngx_rtmp_bandwidth.h"
|
||||
#include "ngx_rtmp_streams.h"
|
||||
|
||||
|
||||
typedef struct ngx_rtmp_live_ctx_s ngx_rtmp_live_ctx_t;
|
||||
typedef struct ngx_rtmp_live_stream_s ngx_rtmp_live_stream_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned active:1;
|
||||
uint32_t timestamp;
|
||||
uint32_t csid;
|
||||
uint32_t dropped;
|
||||
} ngx_rtmp_live_chunk_stream_t;
|
||||
|
||||
|
||||
struct ngx_rtmp_live_ctx_s {
|
||||
ngx_rtmp_session_t *session;
|
||||
ngx_rtmp_live_stream_t *stream;
|
||||
ngx_rtmp_live_ctx_t *next;
|
||||
ngx_uint_t ndropped;
|
||||
ngx_rtmp_live_chunk_stream_t cs[3];
|
||||
ngx_uint_t meta_version;
|
||||
ngx_event_t idle_evt;
|
||||
unsigned active:1;
|
||||
unsigned publishing:1;
|
||||
unsigned silent:1;
|
||||
unsigned paused:1;
|
||||
};
|
||||
|
||||
|
||||
struct ngx_rtmp_live_stream_s {
|
||||
u_char name[NGX_RTMP_MAX_NAME];
|
||||
ngx_rtmp_live_stream_t *next;
|
||||
ngx_rtmp_live_ctx_t *ctx;
|
||||
ngx_rtmp_bandwidth_t bw_in;
|
||||
ngx_rtmp_bandwidth_t bw_in_audio;
|
||||
ngx_rtmp_bandwidth_t bw_in_video;
|
||||
ngx_rtmp_bandwidth_t bw_in_data;
|
||||
ngx_rtmp_bandwidth_t bw_out;
|
||||
ngx_msec_t epoch;
|
||||
unsigned active:1;
|
||||
unsigned publishing:1;
|
||||
};
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_int_t nbuckets;
|
||||
ngx_rtmp_live_stream_t **streams;
|
||||
ngx_flag_t live;
|
||||
ngx_flag_t meta;
|
||||
ngx_msec_t sync;
|
||||
ngx_msec_t idle_timeout;
|
||||
ngx_flag_t atc;
|
||||
ngx_flag_t interleave;
|
||||
ngx_flag_t wait_key;
|
||||
ngx_flag_t wait_video;
|
||||
ngx_flag_t publish_notify;
|
||||
ngx_flag_t play_restart;
|
||||
ngx_flag_t idle_streams;
|
||||
ngx_flag_t buffer;
|
||||
ngx_pool_t *pool;
|
||||
ngx_rtmp_live_stream_t *free_streams;
|
||||
} ngx_rtmp_live_app_conf_t;
|
||||
|
||||
|
||||
extern ngx_module_t ngx_rtmp_live_module;
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_LIVE_H_INCLUDED_ */
|
1021
ngx_rtmp_log_module.c
Normal file
1021
ngx_rtmp_log_module.c
Normal file
File diff suppressed because it is too large
Load diff
2591
ngx_rtmp_mp4_module.c
Normal file
2591
ngx_rtmp_mp4_module.c
Normal file
File diff suppressed because it is too large
Load diff
750
ngx_rtmp_netcall_module.c
Normal file
750
ngx_rtmp_netcall_module.c
Normal file
|
@ -0,0 +1,750 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp_netcall_module.h"
|
||||
|
||||
|
||||
static ngx_int_t ngx_rtmp_netcall_postconfiguration(ngx_conf_t *cf);
|
||||
static void * ngx_rtmp_netcall_create_srv_conf(ngx_conf_t *cf);
|
||||
static char * ngx_rtmp_netcall_merge_srv_conf(ngx_conf_t *cf,
|
||||
void *parent, void *child);
|
||||
|
||||
static void ngx_rtmp_netcall_close(ngx_connection_t *cc);
|
||||
static void ngx_rtmp_netcall_detach(ngx_connection_t *cc);
|
||||
|
||||
static void ngx_rtmp_netcall_recv(ngx_event_t *rev);
|
||||
static void ngx_rtmp_netcall_send(ngx_event_t *wev);
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_msec_t timeout;
|
||||
size_t bufsize;
|
||||
ngx_log_t *log;
|
||||
} ngx_rtmp_netcall_srv_conf_t;
|
||||
|
||||
|
||||
typedef struct ngx_rtmp_netcall_session_s {
|
||||
ngx_rtmp_session_t *session;
|
||||
ngx_peer_connection_t *pc;
|
||||
ngx_url_t *url;
|
||||
struct ngx_rtmp_netcall_session_s *next;
|
||||
void *arg;
|
||||
ngx_rtmp_netcall_handle_pt handle;
|
||||
ngx_rtmp_netcall_filter_pt filter;
|
||||
ngx_rtmp_netcall_sink_pt sink;
|
||||
ngx_chain_t *in;
|
||||
ngx_chain_t *inlast;
|
||||
ngx_chain_t *out;
|
||||
ngx_msec_t timeout;
|
||||
unsigned detached:1;
|
||||
size_t bufsize;
|
||||
} ngx_rtmp_netcall_session_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_rtmp_netcall_session_t *cs;
|
||||
} ngx_rtmp_netcall_ctx_t;
|
||||
|
||||
|
||||
static ngx_command_t ngx_rtmp_netcall_commands[] = {
|
||||
|
||||
{ ngx_string("netcall_timeout"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_msec_slot,
|
||||
NGX_RTMP_SRV_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_netcall_srv_conf_t, timeout),
|
||||
NULL },
|
||||
|
||||
{ ngx_string("netcall_buffer"),
|
||||
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_size_slot,
|
||||
NGX_RTMP_SRV_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_netcall_srv_conf_t, bufsize),
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
||||
static ngx_rtmp_module_t ngx_rtmp_netcall_module_ctx = {
|
||||
NULL, /* preconfiguration */
|
||||
ngx_rtmp_netcall_postconfiguration, /* postconfiguration */
|
||||
NULL, /* create main configuration */
|
||||
NULL, /* init main configuration */
|
||||
ngx_rtmp_netcall_create_srv_conf, /* create server configuration */
|
||||
ngx_rtmp_netcall_merge_srv_conf, /* merge server configuration */
|
||||
NULL, /* create app configuration */
|
||||
NULL /* merge app configuration */
|
||||
};
|
||||
|
||||
|
||||
ngx_module_t ngx_rtmp_netcall_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_rtmp_netcall_module_ctx, /* module context */
|
||||
ngx_rtmp_netcall_commands, /* module directives */
|
||||
NGX_RTMP_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
static void *
|
||||
ngx_rtmp_netcall_create_srv_conf(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_rtmp_netcall_srv_conf_t *nscf;
|
||||
|
||||
nscf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_netcall_srv_conf_t));
|
||||
if (nscf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
nscf->timeout = NGX_CONF_UNSET_MSEC;
|
||||
nscf->bufsize = NGX_CONF_UNSET_SIZE;
|
||||
|
||||
nscf->log = &cf->cycle->new_log;
|
||||
|
||||
return nscf;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_rtmp_netcall_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
|
||||
{
|
||||
ngx_rtmp_netcall_srv_conf_t *prev = parent;
|
||||
ngx_rtmp_netcall_srv_conf_t *conf = child;
|
||||
|
||||
ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 10000);
|
||||
ngx_conf_merge_size_value(conf->bufsize, prev->bufsize, 1024);
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_netcall_disconnect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
ngx_rtmp_netcall_ctx_t *ctx;
|
||||
ngx_rtmp_netcall_session_t *cs;
|
||||
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_netcall_module);
|
||||
|
||||
if (ctx) {
|
||||
for (cs = ctx->cs; cs; cs = cs->next) {
|
||||
ngx_rtmp_netcall_detach(cs->pc->connection);
|
||||
}
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_netcall_get_peer(ngx_peer_connection_t *pc, void *data)
|
||||
{
|
||||
ngx_rtmp_netcall_session_t *cs = data;
|
||||
|
||||
pc->sockaddr =(struct sockaddr *)&cs->url->sockaddr;
|
||||
pc->socklen = cs->url->socklen;
|
||||
pc->name = &cs->url->host;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_netcall_free_peer(ngx_peer_connection_t *pc, void *data,
|
||||
ngx_uint_t state)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_netcall_create(ngx_rtmp_session_t *s, ngx_rtmp_netcall_init_t *ci)
|
||||
{
|
||||
ngx_rtmp_netcall_ctx_t *ctx;
|
||||
ngx_peer_connection_t *pc;
|
||||
ngx_rtmp_netcall_session_t *cs;
|
||||
ngx_rtmp_netcall_srv_conf_t *nscf;
|
||||
ngx_connection_t *c, *cc;
|
||||
ngx_pool_t *pool;
|
||||
ngx_int_t rc;
|
||||
|
||||
pool = NULL;
|
||||
c = s->connection;
|
||||
|
||||
nscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_netcall_module);
|
||||
if (nscf == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* get module context */
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_netcall_module);
|
||||
if (ctx == NULL) {
|
||||
ctx = ngx_pcalloc(c->pool,
|
||||
sizeof(ngx_rtmp_netcall_ctx_t));
|
||||
if (ctx == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_netcall_module);
|
||||
}
|
||||
|
||||
/* Create netcall pool, connection, session.
|
||||
* Note we use shared (app-wide) log because
|
||||
* s->connection->log might be unavailable
|
||||
* in detached netcall when it's being closed */
|
||||
pool = ngx_create_pool(4096, nscf->log);
|
||||
if (pool == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
pc = ngx_pcalloc(pool, sizeof(ngx_peer_connection_t));
|
||||
if (pc == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
cs = ngx_pcalloc(pool, sizeof(ngx_rtmp_netcall_session_t));
|
||||
if (cs == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* copy arg to connection pool */
|
||||
if (ci->argsize) {
|
||||
cs->arg = ngx_pcalloc(pool, ci->argsize);
|
||||
if (cs->arg == NULL) {
|
||||
goto error;
|
||||
}
|
||||
ngx_memcpy(cs->arg, ci->arg, ci->argsize);
|
||||
}
|
||||
|
||||
cs->timeout = nscf->timeout;
|
||||
cs->bufsize = nscf->bufsize;
|
||||
cs->url = ci->url;
|
||||
cs->session = s;
|
||||
cs->filter = ci->filter;
|
||||
cs->sink = ci->sink;
|
||||
cs->handle = ci->handle;
|
||||
if (cs->handle == NULL) {
|
||||
cs->detached = 1;
|
||||
}
|
||||
|
||||
pc->log = nscf->log;
|
||||
pc->get = ngx_rtmp_netcall_get_peer;
|
||||
pc->free = ngx_rtmp_netcall_free_peer;
|
||||
pc->data = cs;
|
||||
|
||||
/* connect */
|
||||
rc = ngx_event_connect_peer(pc);
|
||||
if (rc != NGX_OK && rc != NGX_AGAIN ) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"netcall: connection failed");
|
||||
goto error;
|
||||
}
|
||||
|
||||
cc = pc->connection;
|
||||
cc->data = cs;
|
||||
cc->pool = pool;
|
||||
cs->pc = pc;
|
||||
|
||||
cs->out = ci->create(s, ci->arg, pool);
|
||||
if (cs->out == NULL) {
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"netcall: creation failed");
|
||||
ngx_close_connection(pc->connection);
|
||||
goto error;
|
||||
}
|
||||
|
||||
cc->write->handler = ngx_rtmp_netcall_send;
|
||||
cc->read->handler = ngx_rtmp_netcall_recv;
|
||||
|
||||
if (!cs->detached) {
|
||||
cs->next = ctx->cs;
|
||||
ctx->cs = cs;
|
||||
}
|
||||
|
||||
ngx_rtmp_netcall_send(cc->write);
|
||||
|
||||
return c->destroyed ? NGX_ERROR : NGX_OK;
|
||||
|
||||
error:
|
||||
if (pool) {
|
||||
ngx_destroy_pool(pool);
|
||||
}
|
||||
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_netcall_close(ngx_connection_t *cc)
|
||||
{
|
||||
ngx_rtmp_netcall_session_t *cs, **css;
|
||||
ngx_pool_t *pool;
|
||||
ngx_rtmp_session_t *s;
|
||||
ngx_rtmp_netcall_ctx_t *ctx;
|
||||
ngx_buf_t *b;
|
||||
|
||||
cs = cc->data;
|
||||
|
||||
if (cc->destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
cc->destroyed = 1;
|
||||
|
||||
if (!cs->detached) {
|
||||
s = cs->session;
|
||||
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_netcall_module);
|
||||
|
||||
if (cs->in && cs->sink) {
|
||||
cs->sink(cs->session, cs->in);
|
||||
|
||||
b = cs->in->buf;
|
||||
b->pos = b->last = b->start;
|
||||
|
||||
}
|
||||
|
||||
for(css = &ctx->cs; *css; css = &((*css)->next)) {
|
||||
if (*css == cs) {
|
||||
*css = cs->next;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cs->handle && cs->handle(s, cs->arg, cs->in) != NGX_OK) {
|
||||
ngx_rtmp_finalize_session(s);
|
||||
}
|
||||
}
|
||||
|
||||
pool = cc->pool;
|
||||
ngx_close_connection(cc);
|
||||
ngx_destroy_pool(pool);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_netcall_detach(ngx_connection_t *cc)
|
||||
{
|
||||
ngx_rtmp_netcall_session_t *cs;
|
||||
|
||||
cs = cc->data;
|
||||
cs->detached = 1;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_netcall_recv(ngx_event_t *rev)
|
||||
{
|
||||
ngx_rtmp_netcall_session_t *cs;
|
||||
ngx_connection_t *cc;
|
||||
ngx_chain_t *cl;
|
||||
ngx_int_t n;
|
||||
ngx_buf_t *b;
|
||||
|
||||
cc = rev->data;
|
||||
cs = cc->data;
|
||||
|
||||
if (cc->destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (rev->timedout) {
|
||||
cc->timedout = 1;
|
||||
ngx_rtmp_netcall_close(cc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rev->timer_set) {
|
||||
ngx_del_timer(rev);
|
||||
}
|
||||
|
||||
for ( ;; ) {
|
||||
|
||||
if (cs->inlast == NULL ||
|
||||
cs->inlast->buf->last == cs->inlast->buf->end)
|
||||
{
|
||||
if (cs->in && cs->sink) {
|
||||
if (!cs->detached) {
|
||||
if (cs->sink(cs->session, cs->in) != NGX_OK) {
|
||||
ngx_rtmp_netcall_close(cc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
b = cs->in->buf;
|
||||
b->pos = b->last = b->start;
|
||||
|
||||
} else {
|
||||
cl = ngx_alloc_chain_link(cc->pool);
|
||||
if (cl == NULL) {
|
||||
ngx_rtmp_netcall_close(cc);
|
||||
return;
|
||||
}
|
||||
|
||||
cl->next = NULL;
|
||||
|
||||
cl->buf = ngx_create_temp_buf(cc->pool, cs->bufsize);
|
||||
if (cl->buf == NULL) {
|
||||
ngx_rtmp_netcall_close(cc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cs->in == NULL) {
|
||||
cs->in = cl;
|
||||
} else {
|
||||
cs->inlast->next = cl;
|
||||
}
|
||||
|
||||
cs->inlast = cl;
|
||||
}
|
||||
}
|
||||
|
||||
b = cs->inlast->buf;
|
||||
|
||||
n = cc->recv(cc, b->last, b->end - b->last);
|
||||
|
||||
if (n == NGX_ERROR || n == 0) {
|
||||
ngx_rtmp_netcall_close(cc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (n == NGX_AGAIN) {
|
||||
if (cs->filter && cs->in
|
||||
&& cs->filter(cs->in) != NGX_AGAIN)
|
||||
{
|
||||
ngx_rtmp_netcall_close(cc);
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_add_timer(rev, cs->timeout);
|
||||
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
|
||||
ngx_rtmp_netcall_close(cc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
b->last += n;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_netcall_send(ngx_event_t *wev)
|
||||
{
|
||||
ngx_rtmp_netcall_session_t *cs;
|
||||
ngx_connection_t *cc;
|
||||
ngx_chain_t *cl;
|
||||
|
||||
cc = wev->data;
|
||||
cs = cc->data;
|
||||
|
||||
if (cc->destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (wev->timedout) {
|
||||
ngx_log_error(NGX_LOG_INFO, cc->log, NGX_ETIMEDOUT,
|
||||
"netcall: client send timed out");
|
||||
cc->timedout = 1;
|
||||
ngx_rtmp_netcall_close(cc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (wev->timer_set) {
|
||||
ngx_del_timer(wev);
|
||||
}
|
||||
|
||||
cl = cc->send_chain(cc, cs->out, 0);
|
||||
|
||||
if (cl == NGX_CHAIN_ERROR) {
|
||||
ngx_rtmp_netcall_close(cc);
|
||||
return;
|
||||
}
|
||||
|
||||
cs->out = cl;
|
||||
|
||||
/* more data to send? */
|
||||
if (cl) {
|
||||
ngx_add_timer(wev, cs->timeout);
|
||||
if (ngx_handle_write_event(wev, 0) != NGX_OK) {
|
||||
ngx_rtmp_netcall_close(cc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* we've sent everything we had.
|
||||
* now receive reply */
|
||||
ngx_del_event(wev, NGX_WRITE_EVENT, 0);
|
||||
|
||||
ngx_rtmp_netcall_recv(cc->read);
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_netcall_http_format_request(ngx_int_t method, ngx_str_t *host,
|
||||
ngx_str_t *uri, ngx_chain_t *args,
|
||||
ngx_chain_t *body, ngx_pool_t *pool,
|
||||
ngx_str_t *content_type)
|
||||
{
|
||||
ngx_chain_t *al, *bl, *ret;
|
||||
ngx_buf_t *b;
|
||||
size_t content_length;
|
||||
static const char *methods[2] = { "GET", "POST" };
|
||||
static const char rq_tmpl[] = " HTTP/1.0\r\n"
|
||||
"Host: %V\r\n"
|
||||
"Content-Type: %V\r\n"
|
||||
"Connection: Close\r\n"
|
||||
"Content-Length: %uz\r\n"
|
||||
"\r\n";
|
||||
|
||||
content_length = 0;
|
||||
for (al = body; al; al = al->next) {
|
||||
b = al->buf;
|
||||
content_length += (b->last - b->pos);
|
||||
}
|
||||
|
||||
/* create first buffer */
|
||||
|
||||
al = ngx_alloc_chain_link(pool);
|
||||
if (al == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
b = ngx_create_temp_buf(pool, sizeof("POST") + /* longest method + 1 */
|
||||
uri->len);
|
||||
if (b == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
b->last = ngx_snprintf(b->last, b->end - b->last, "%s %V",
|
||||
methods[method], uri);
|
||||
|
||||
al->buf = b;
|
||||
|
||||
ret = al;
|
||||
|
||||
if (args) {
|
||||
*b->last++ = '?';
|
||||
al->next = args;
|
||||
for (al = args; al->next; al = al->next);
|
||||
}
|
||||
|
||||
/* create second buffer */
|
||||
|
||||
bl = ngx_alloc_chain_link(pool);
|
||||
if (bl == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
b = ngx_create_temp_buf(pool, sizeof(rq_tmpl) + host->len +
|
||||
content_type->len + NGX_SIZE_T_LEN);
|
||||
if (b == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bl->buf = b;
|
||||
|
||||
b->last = ngx_snprintf(b->last, b->end - b->last, rq_tmpl,
|
||||
host, content_type, content_length);
|
||||
|
||||
al->next = bl;
|
||||
bl->next = body;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_netcall_http_format_session(ngx_rtmp_session_t *s, ngx_pool_t *pool)
|
||||
{
|
||||
ngx_chain_t *cl;
|
||||
ngx_buf_t *b;
|
||||
ngx_str_t *addr_text;
|
||||
size_t bsize;
|
||||
|
||||
addr_text = &s->connection->addr_text;
|
||||
|
||||
cl = ngx_alloc_chain_link(pool);
|
||||
if (cl == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @2016-04-20 sergey-dryabzhinsky
|
||||
* Not all params may be filled in session
|
||||
* So not override them with empty values
|
||||
*/
|
||||
|
||||
bsize = sizeof("addr=") - 1 + addr_text->len * 3 +
|
||||
sizeof("&clientid=") - 1 + NGX_INT_T_LEN;
|
||||
|
||||
// Indicator of additional vars from session
|
||||
// Event `connect` don't have them, for example
|
||||
if (s->app.len) {
|
||||
bsize += sizeof("&app=") - 1 + s->app.len * 3;
|
||||
}
|
||||
if (s->flashver.len) {
|
||||
bsize += sizeof("&flashver=") - 1 + s->flashver.len * 3;
|
||||
}
|
||||
if (s->swf_url.len) {
|
||||
bsize += sizeof("&swfurl=") - 1 + s->swf_url.len * 3;
|
||||
}
|
||||
if (s->tc_url.len) {
|
||||
bsize += sizeof("&tcurl=") - 1 + s->tc_url.len * 3;
|
||||
}
|
||||
if (s->page_url.len) {
|
||||
bsize += sizeof("&pageurl=") - 1 + s->page_url.len * 3;
|
||||
}
|
||||
|
||||
b = ngx_create_temp_buf(pool, bsize);
|
||||
if (b == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cl->buf = b;
|
||||
cl->next = NULL;
|
||||
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "addr=", sizeof("addr=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, addr_text->data,
|
||||
addr_text->len, NGX_ESCAPE_ARGS);
|
||||
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "&clientid=",
|
||||
sizeof("&clientid=") - 1);
|
||||
b->last = ngx_sprintf(b->last, "%ui", (ngx_uint_t) s->connection->number);
|
||||
|
||||
if (s->app.len) {
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "&app=", sizeof("&app=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, s->app.data, s->app.len,
|
||||
NGX_ESCAPE_ARGS);
|
||||
}
|
||||
if (s->flashver.len) {
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "&flashver=",
|
||||
sizeof("&flashver=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, s->flashver.data,
|
||||
s->flashver.len, NGX_ESCAPE_ARGS);
|
||||
}
|
||||
if (s->swf_url.len) {
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "&swfurl=",
|
||||
sizeof("&swfurl=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, s->swf_url.data,
|
||||
s->swf_url.len, NGX_ESCAPE_ARGS);
|
||||
}
|
||||
if (s->tc_url.len) {
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "&tcurl=",
|
||||
sizeof("&tcurl=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, s->tc_url.data,
|
||||
s->tc_url.len, NGX_ESCAPE_ARGS);
|
||||
}
|
||||
if (s->page_url.len) {
|
||||
b->last = ngx_cpymem(b->last, (u_char*) "&pageurl=",
|
||||
sizeof("&pageurl=") - 1);
|
||||
b->last = (u_char*) ngx_escape_uri(b->last, s->page_url.data,
|
||||
s->page_url.len, NGX_ESCAPE_ARGS);
|
||||
}
|
||||
|
||||
return cl;
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_netcall_http_skip_header(ngx_chain_t *in)
|
||||
{
|
||||
ngx_buf_t *b;
|
||||
|
||||
/* find \n[\r]\n */
|
||||
enum {
|
||||
normal,
|
||||
lf,
|
||||
lfcr
|
||||
} state = normal;
|
||||
|
||||
if (in == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
b = in->buf;
|
||||
|
||||
for ( ;; ) {
|
||||
|
||||
while (b->pos == b->last) {
|
||||
in = in->next;
|
||||
if (in == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
b = in->buf;
|
||||
}
|
||||
|
||||
switch (*b->pos++) {
|
||||
case '\r':
|
||||
state = (state == lf) ? lfcr : normal;
|
||||
break;
|
||||
|
||||
case '\n':
|
||||
if (state != normal) {
|
||||
return in;
|
||||
}
|
||||
state = lf;
|
||||
break;
|
||||
|
||||
default:
|
||||
state = normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_netcall_memcache_set(ngx_rtmp_session_t *s, ngx_pool_t *pool,
|
||||
ngx_str_t *key, ngx_str_t *value, ngx_uint_t flags, ngx_uint_t sec)
|
||||
{
|
||||
ngx_chain_t *cl;
|
||||
ngx_buf_t *b;
|
||||
|
||||
cl = ngx_alloc_chain_link(pool);
|
||||
if (cl == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
b = ngx_create_temp_buf(pool, sizeof("set ") - 1 + key->len +
|
||||
(1 + NGX_INT_T_LEN) * 3 +
|
||||
(sizeof("\r\n") - 1) * 2 + value->len);
|
||||
|
||||
if (b == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cl->next = NULL;
|
||||
cl->buf = b;
|
||||
|
||||
b->last = ngx_sprintf(b->pos, "set %V %ui %ui %ui\r\n%V\r\n",
|
||||
key, flags, sec, (ngx_uint_t) value->len, value);
|
||||
|
||||
return cl;
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_netcall_postconfiguration(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_rtmp_core_main_conf_t *cmcf;
|
||||
ngx_rtmp_handler_pt *h;
|
||||
|
||||
cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);
|
||||
|
||||
h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]);
|
||||
*h = ngx_rtmp_netcall_disconnect;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
67
ngx_rtmp_netcall_module.h
Normal file
67
ngx_rtmp_netcall_module.h
Normal file
|
@ -0,0 +1,67 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_NETCALL_H_INCLUDED_
|
||||
#define _NGX_RTMP_NETCALL_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
||||
|
||||
typedef ngx_chain_t * (*ngx_rtmp_netcall_create_pt)(ngx_rtmp_session_t *s,
|
||||
void *arg, ngx_pool_t *pool);
|
||||
typedef ngx_int_t (*ngx_rtmp_netcall_filter_pt)(ngx_chain_t *in);
|
||||
typedef ngx_int_t (*ngx_rtmp_netcall_sink_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_chain_t *in);
|
||||
typedef ngx_int_t (*ngx_rtmp_netcall_handle_pt)(ngx_rtmp_session_t *s,
|
||||
void *arg, ngx_chain_t *in);
|
||||
|
||||
#define NGX_RTMP_NETCALL_HTTP_GET 0
|
||||
#define NGX_RTMP_NETCALL_HTTP_POST 1
|
||||
|
||||
|
||||
/* If handle is NULL then netcall is created detached
|
||||
* which means it's completely independent of RTMP
|
||||
* session and its result is never visible to anyone.
|
||||
*
|
||||
* WARNING: It's not recommended to create non-detached
|
||||
* netcalls from disconect handlers. Netcall disconnect
|
||||
* handler which detaches active netcalls is executed
|
||||
* BEFORE your handler. It leads to a crash
|
||||
* after netcall connection is closed */
|
||||
typedef struct {
|
||||
ngx_url_t *url;
|
||||
ngx_rtmp_netcall_create_pt create;
|
||||
ngx_rtmp_netcall_filter_pt filter;
|
||||
ngx_rtmp_netcall_sink_pt sink;
|
||||
ngx_rtmp_netcall_handle_pt handle;
|
||||
void *arg;
|
||||
size_t argsize;
|
||||
} ngx_rtmp_netcall_init_t;
|
||||
|
||||
|
||||
ngx_int_t ngx_rtmp_netcall_create(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_netcall_init_t *ci);
|
||||
|
||||
|
||||
/* HTTP handling */
|
||||
ngx_chain_t * ngx_rtmp_netcall_http_format_session(ngx_rtmp_session_t *s,
|
||||
ngx_pool_t *pool);
|
||||
ngx_chain_t * ngx_rtmp_netcall_http_format_request(ngx_int_t method,
|
||||
ngx_str_t *host, ngx_str_t *uri, ngx_chain_t *args, ngx_chain_t *body,
|
||||
ngx_pool_t *pool, ngx_str_t *content_type);
|
||||
ngx_chain_t * ngx_rtmp_netcall_http_skip_header(ngx_chain_t *in);
|
||||
|
||||
|
||||
/* Memcache handling */
|
||||
ngx_chain_t * ngx_rtmp_netcall_memcache_set(ngx_rtmp_session_t *s,
|
||||
ngx_pool_t *pool, ngx_str_t *key, ngx_str_t *value,
|
||||
ngx_uint_t flags, ngx_uint_t sec);
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_NETCALL_H_INCLUDED_ */
|
2242
ngx_rtmp_notify_module.c
Normal file
2242
ngx_rtmp_notify_module.c
Normal file
File diff suppressed because it is too large
Load diff
1353
ngx_rtmp_play_module.c
Normal file
1353
ngx_rtmp_play_module.c
Normal file
File diff suppressed because it is too large
Load diff
93
ngx_rtmp_play_module.h
Normal file
93
ngx_rtmp_play_module.h
Normal file
|
@ -0,0 +1,93 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_PLAY_H_INCLUDED_
|
||||
#define _NGX_RTMP_PLAY_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
#include "ngx_rtmp_cmd_module.h"
|
||||
|
||||
|
||||
typedef ngx_int_t (*ngx_rtmp_play_init_pt) (ngx_rtmp_session_t *s,
|
||||
ngx_file_t *f, ngx_int_t aindex, ngx_int_t vindex);
|
||||
typedef ngx_int_t (*ngx_rtmp_play_done_pt) (ngx_rtmp_session_t *s,
|
||||
ngx_file_t *f);
|
||||
typedef ngx_int_t (*ngx_rtmp_play_start_pt) (ngx_rtmp_session_t *s,
|
||||
ngx_file_t *f);
|
||||
typedef ngx_int_t (*ngx_rtmp_play_seek_pt) (ngx_rtmp_session_t *s,
|
||||
ngx_file_t *f, ngx_uint_t offs);
|
||||
typedef ngx_int_t (*ngx_rtmp_play_stop_pt) (ngx_rtmp_session_t *s,
|
||||
ngx_file_t *f);
|
||||
typedef ngx_int_t (*ngx_rtmp_play_send_pt) (ngx_rtmp_session_t *s,
|
||||
ngx_file_t *f, ngx_uint_t *ts);
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t name;
|
||||
ngx_str_t pfx;
|
||||
ngx_str_t sfx;
|
||||
|
||||
ngx_rtmp_play_init_pt init;
|
||||
ngx_rtmp_play_done_pt done;
|
||||
ngx_rtmp_play_start_pt start;
|
||||
ngx_rtmp_play_seek_pt seek;
|
||||
ngx_rtmp_play_stop_pt stop;
|
||||
ngx_rtmp_play_send_pt send;
|
||||
} ngx_rtmp_play_fmt_t;
|
||||
|
||||
|
||||
typedef struct ngx_rtmp_play_ctx_s ngx_rtmp_play_ctx_t;
|
||||
|
||||
|
||||
struct ngx_rtmp_play_ctx_s {
|
||||
ngx_rtmp_session_t *session;
|
||||
ngx_file_t file;
|
||||
ngx_rtmp_play_fmt_t *fmt;
|
||||
ngx_event_t send_evt;
|
||||
unsigned playing:1;
|
||||
unsigned opened:1;
|
||||
unsigned joined:1;
|
||||
ngx_uint_t ncrs;
|
||||
ngx_uint_t nheader;
|
||||
ngx_uint_t nbody;
|
||||
size_t pfx_size;
|
||||
ngx_str_t sfx;
|
||||
ngx_uint_t file_id;
|
||||
ngx_int_t aindex, vindex;
|
||||
ngx_uint_t nentry;
|
||||
ngx_uint_t post_seek;
|
||||
u_char name[NGX_RTMP_MAX_NAME];
|
||||
ngx_rtmp_play_ctx_t *next;
|
||||
};
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t *root;
|
||||
ngx_url_t *url;
|
||||
} ngx_rtmp_play_entry_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t temp_path;
|
||||
ngx_str_t local_path;
|
||||
ngx_array_t entries; /* ngx_rtmp_play_entry_t * */
|
||||
ngx_uint_t nbuckets;
|
||||
ngx_rtmp_play_ctx_t **ctx;
|
||||
} ngx_rtmp_play_app_conf_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_array_t fmts; /* ngx_rtmp_play_fmt_t * */
|
||||
} ngx_rtmp_play_main_conf_t;
|
||||
|
||||
|
||||
extern ngx_module_t ngx_rtmp_play_module;
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_PLAY_H_INCLUDED_ */
|
197
ngx_rtmp_proxy_protocol.c
Normal file
197
ngx_rtmp_proxy_protocol.c
Normal file
|
@ -0,0 +1,197 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <nginx.h>
|
||||
#include "ngx_rtmp_proxy_protocol.h"
|
||||
|
||||
|
||||
static void ngx_rtmp_proxy_protocol_recv(ngx_event_t *rev);
|
||||
|
||||
|
||||
void
|
||||
ngx_rtmp_proxy_protocol(ngx_rtmp_session_t *s)
|
||||
{
|
||||
ngx_event_t *rev;
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = s->connection;
|
||||
rev = c->read;
|
||||
rev->handler = ngx_rtmp_proxy_protocol_recv;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"proxy_protocol: start");
|
||||
|
||||
if (rev->ready) {
|
||||
/* the deferred accept(), rtsig, aio, iocp */
|
||||
|
||||
if (ngx_use_accept_mutex) {
|
||||
ngx_post_event(rev, &ngx_posted_events);
|
||||
return;
|
||||
}
|
||||
|
||||
rev->handler(rev);
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_add_timer(rev, s->timeout);
|
||||
|
||||
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_proxy_protocol_recv(ngx_event_t *rev)
|
||||
{
|
||||
u_char buf[107], *p, *pp, *text;
|
||||
size_t len;
|
||||
ssize_t n;
|
||||
ngx_err_t err;
|
||||
ngx_int_t i;
|
||||
ngx_addr_t addr;
|
||||
ngx_connection_t *c;
|
||||
ngx_rtmp_session_t *s;
|
||||
|
||||
c = rev->data;
|
||||
s = c->data;
|
||||
|
||||
if (c->destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (rev->timedout) {
|
||||
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
|
||||
"proxy_protocol: recv: client timed out");
|
||||
c->timedout = 1;
|
||||
ngx_rtmp_finalize_session(s);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rev->timer_set) {
|
||||
ngx_del_timer(rev);
|
||||
}
|
||||
|
||||
n = recv(c->fd, (char *) buf, sizeof(buf), MSG_PEEK);
|
||||
|
||||
err = ngx_socket_errno;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0, "recv(): %d", n);
|
||||
|
||||
if (n == -1) {
|
||||
|
||||
if (err == NGX_EAGAIN) {
|
||||
ngx_add_timer(rev, s->timeout);
|
||||
|
||||
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
|
||||
ngx_rtmp_finalize_session(s);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ngx_rtmp_finalize_session(s);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
p = buf;
|
||||
|
||||
if (n <= 8 && ngx_strncmp(p, "PROXY ", 6) != 0) {
|
||||
goto bad_header;
|
||||
}
|
||||
|
||||
n -= 6;
|
||||
p += 6;
|
||||
|
||||
ngx_memzero(&addr, sizeof(ngx_addr_t));
|
||||
|
||||
if (n >= 7 && ngx_strncmp(p, "UNKNOWN", 7) == 0) {
|
||||
n -= 7;
|
||||
p += 7;
|
||||
goto skip;
|
||||
}
|
||||
|
||||
if (n < 5 || ngx_strncmp(p, "TCP", 3) != 0
|
||||
|| (p[3] != '4' && p[3] != '6') || p[4] != ' ')
|
||||
{
|
||||
goto bad_header;
|
||||
}
|
||||
|
||||
n -= 5;
|
||||
p += 5;
|
||||
|
||||
pp = ngx_strlchr(p, p + n, ' ');
|
||||
|
||||
if (pp == NULL) {
|
||||
goto bad_header;
|
||||
}
|
||||
|
||||
if (ngx_parse_addr(s->connection->pool, &addr, p, pp - p) != NGX_OK) {
|
||||
goto bad_header;
|
||||
}
|
||||
|
||||
n -= pp - p;
|
||||
p = pp;
|
||||
|
||||
skip:
|
||||
|
||||
for (i = 0; i + 1 < n; i++) {
|
||||
if (p[i] == CR && p[i + 1] == LF) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i + 1 >= n) {
|
||||
goto bad_header;
|
||||
}
|
||||
|
||||
n = p - buf + i + 2;
|
||||
|
||||
if (c->recv(c, buf, n) != n) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
if (addr.socklen) {
|
||||
text = ngx_palloc(s->connection->pool, NGX_SOCKADDR_STRLEN);
|
||||
|
||||
if (text == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
len = ngx_sock_ntop(addr.sockaddr,
|
||||
#if (nginx_version >= 1005003)
|
||||
addr.socklen,
|
||||
#endif
|
||||
text, NGX_SOCKADDR_STRLEN, 0);
|
||||
if (len == 0) {
|
||||
goto failed;
|
||||
}
|
||||
|
||||
c->sockaddr = addr.sockaddr;
|
||||
c->socklen = addr.socklen;
|
||||
c->addr_text.data = text;
|
||||
c->addr_text.len = len;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0,
|
||||
"proxy_protocol: remote_addr:'%V'", &c->addr_text);
|
||||
}
|
||||
|
||||
ngx_rtmp_handshake(s);
|
||||
|
||||
return;
|
||||
|
||||
bad_header:
|
||||
|
||||
ngx_log_error(NGX_LOG_INFO, c->log, 0, "proxy_protocol: bad header");
|
||||
|
||||
failed:
|
||||
|
||||
ngx_rtmp_finalize_session(s);
|
||||
}
|
19
ngx_rtmp_proxy_protocol.h
Normal file
19
ngx_rtmp_proxy_protocol.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_PROXY_PROTOCOL_H_INCLUDED_
|
||||
#define _NGX_RTMP_PROXY_PROTOCOL_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
||||
|
||||
void ngx_rtmp_proxy_protocol(ngx_rtmp_session_t *c);
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_PROXY_PROTOCOL_H_INCLUDED_ */
|
|
@ -1,28 +1,30 @@
|
|||
|
||||
/*
|
||||
* Copyright (c) 2012 Roman Arutyunyan
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
#include "ngx_rtmp_amf0.h"
|
||||
#include "ngx_rtmp_amf.h"
|
||||
#include "ngx_rtmp_cmd_module.h"
|
||||
#include <string.h>
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_int_t
|
||||
ngx_rtmp_protocol_message_handler(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in)
|
||||
{
|
||||
ngx_buf_t *b;
|
||||
u_char *p;
|
||||
u_char *p;
|
||||
uint32_t val;
|
||||
uint8_t limit;
|
||||
ngx_connection_t *c;
|
||||
|
||||
c = s->connection;
|
||||
b = in->buf;
|
||||
|
||||
if (b->last - b->pos < 4) {
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, c->log, 0,
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"too small buffer for %d message: %d",
|
||||
(int)h->type, b->last - b->pos);
|
||||
return NGX_OK;
|
||||
|
@ -37,6 +39,7 @@ ngx_rtmp_protocol_message_handler(ngx_rtmp_session_t *s,
|
|||
switch(h->type) {
|
||||
case NGX_RTMP_MSG_CHUNK_SIZE:
|
||||
/* set chunk size =val */
|
||||
ngx_rtmp_set_chunk_size(s, val);
|
||||
break;
|
||||
|
||||
case NGX_RTMP_MSG_ABORT:
|
||||
|
@ -45,10 +48,15 @@ ngx_rtmp_protocol_message_handler(ngx_rtmp_session_t *s,
|
|||
|
||||
case NGX_RTMP_MSG_ACK:
|
||||
/* receive ack with sequence number =val */
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"receive ack seq=%uD", val);
|
||||
break;
|
||||
|
||||
case NGX_RTMP_MSG_ACK_SIZE:
|
||||
/* receive window size =val */
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"receive ack_size=%uD", val);
|
||||
s->ack_size = val;
|
||||
break;
|
||||
|
||||
case NGX_RTMP_MSG_BANDWIDTH:
|
||||
|
@ -58,6 +66,10 @@ ngx_rtmp_protocol_message_handler(ngx_rtmp_session_t *s,
|
|||
(void)val;
|
||||
(void)limit;
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"receive bandwidth=%uD limit=%d",
|
||||
val, (int)limit);
|
||||
|
||||
/* receive window size =val
|
||||
* && limit */
|
||||
}
|
||||
|
@ -71,35 +83,35 @@ ngx_rtmp_protocol_message_handler(ngx_rtmp_session_t *s,
|
|||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_user_message_handler(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in)
|
||||
ngx_int_t
|
||||
ngx_rtmp_user_message_handler(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
ngx_buf_t *b;
|
||||
u_char *p;
|
||||
u_char *p;
|
||||
uint16_t evt;
|
||||
uint32_t val, arg;
|
||||
ngx_connection_t *c;
|
||||
uint32_t val;
|
||||
|
||||
c = s->connection;
|
||||
b = in->buf;
|
||||
|
||||
if (b->last - b->pos < 6) {
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0,
|
||||
"too small buffer for user message: %d",
|
||||
b->last - b->pos);
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"too small buffer for user message: %d",
|
||||
b->last - b->pos);
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
p = (u_char*)&evt;
|
||||
|
||||
p[0] = b->pos[1];
|
||||
p[1] = b->pos[0];
|
||||
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, c->log, 0,
|
||||
"RTMP recv user evt %s (%d)",
|
||||
ngx_rtmp_user_message_type(evt), (int)evt);
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"RTMP recv user evt %s (%i)",
|
||||
ngx_rtmp_user_message_type(evt), (ngx_int_t) evt);
|
||||
|
||||
p = (u_char *) &val;
|
||||
|
||||
p = (u_char*)&val;
|
||||
p[0] = b->pos[5];
|
||||
p[1] = b->pos[4];
|
||||
p[2] = b->pos[3];
|
||||
|
@ -107,119 +119,346 @@ ngx_rtmp_user_message_handler(ngx_rtmp_session_t *s,
|
|||
|
||||
switch(evt) {
|
||||
case NGX_RTMP_USER_STREAM_BEGIN:
|
||||
/* use =val as stream id which started */
|
||||
break;
|
||||
{
|
||||
ngx_rtmp_stream_begin_t v;
|
||||
|
||||
v.msid = val;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"receive: stream_begin msid=%uD", v.msid);
|
||||
|
||||
return ngx_rtmp_stream_begin(s, &v);
|
||||
}
|
||||
|
||||
case NGX_RTMP_USER_STREAM_EOF:
|
||||
/* use =val as stream id which is over */
|
||||
break;
|
||||
{
|
||||
ngx_rtmp_stream_eof_t v;
|
||||
|
||||
v.msid = val;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"receive: stream_eof msid=%uD", v.msid);
|
||||
|
||||
return ngx_rtmp_stream_eof(s, &v);
|
||||
}
|
||||
|
||||
case NGX_RTMP_USER_STREAM_DRY:
|
||||
/* stream =val is dry */
|
||||
break;
|
||||
{
|
||||
ngx_rtmp_stream_dry_t v;
|
||||
|
||||
v.msid = val;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"receive: stream_dry msid=%uD", v.msid);
|
||||
|
||||
return ngx_rtmp_stream_dry(s, &v);
|
||||
}
|
||||
|
||||
case NGX_RTMP_USER_SET_BUFLEN:
|
||||
if (b->last - b->pos >= 10) {
|
||||
p = (u_char*)&arg;
|
||||
{
|
||||
ngx_rtmp_set_buflen_t v;
|
||||
|
||||
v.msid = val;
|
||||
|
||||
if (b->last - b->pos < 10) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
p = (u_char *) &v.buflen;
|
||||
|
||||
p[0] = b->pos[9];
|
||||
p[1] = b->pos[8];
|
||||
p[2] = b->pos[7];
|
||||
p[3] = b->pos[6];
|
||||
|
||||
(void)arg;
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"receive: set_buflen msid=%uD buflen=%uD",
|
||||
v.msid, v.buflen);
|
||||
|
||||
/* use =val as stream id && arg as buflen in msec*/
|
||||
/*TODO: move this to play module */
|
||||
s->buflen = v.buflen;
|
||||
|
||||
return ngx_rtmp_set_buflen(s, &v);
|
||||
}
|
||||
break;
|
||||
|
||||
case NGX_RTMP_USER_RECORDED:
|
||||
/* stream =val is recorded */
|
||||
break;
|
||||
{
|
||||
ngx_rtmp_recorded_t v;
|
||||
|
||||
v.msid = val;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"receive: recorded msid=%uD", v.msid);
|
||||
|
||||
return ngx_rtmp_recorded(s, &v);
|
||||
}
|
||||
|
||||
case NGX_RTMP_USER_PING_REQUEST:
|
||||
ngx_rtmp_send_user_ping_response(s, val);
|
||||
break;
|
||||
return ngx_rtmp_send_ping_response(s, val);
|
||||
|
||||
case NGX_RTMP_USER_PING_RESPONSE:
|
||||
/* use =val as incoming timestamp */
|
||||
break;
|
||||
|
||||
/* val = incoming timestamp */
|
||||
|
||||
ngx_rtmp_reset_ping(s);
|
||||
|
||||
return NGX_OK;
|
||||
|
||||
default:
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0,
|
||||
"unexpected user event: %d",
|
||||
(int)evt);
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"unexpected user event: %i", (ngx_int_t) evt);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_fetch(ngx_chain_t **in, u_char *ret)
|
||||
{
|
||||
while (*in && (*in)->buf->pos >= (*in)->buf->last) {
|
||||
*in = (*in)->next;
|
||||
}
|
||||
|
||||
if (*in == NULL) {
|
||||
return NGX_DONE;
|
||||
}
|
||||
|
||||
*ret = *(*in)->buf->pos++;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_amf0_message_handler(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in)
|
||||
static ngx_int_t
|
||||
ngx_rtmp_fetch_uint8(ngx_chain_t **in, uint8_t *ret)
|
||||
{
|
||||
ngx_rtmp_amf0_ctx_t act;
|
||||
ngx_connection_t *c;
|
||||
ngx_rtmp_core_main_conf_t *cmcf;
|
||||
ngx_rtmp_event_handler_pt ch;
|
||||
size_t len;
|
||||
return ngx_rtmp_fetch(in, (u_char *) ret);
|
||||
}
|
||||
|
||||
static u_char func[128];
|
||||
|
||||
static ngx_rtmp_amf0_elt_t elts[] = {
|
||||
{ NGX_RTMP_AMF0_STRING, 0, func, sizeof(func) },
|
||||
};
|
||||
static ngx_int_t
|
||||
ngx_rtmp_fetch_uint32(ngx_chain_t **in, uint32_t *ret, ngx_int_t n)
|
||||
{
|
||||
u_char *r = (u_char *) ret;
|
||||
ngx_int_t rc;
|
||||
|
||||
c = s->connection;
|
||||
cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module);
|
||||
*ret = 0;
|
||||
|
||||
/* read AMF0 func name & transaction id */
|
||||
act.link = in;
|
||||
act.log = s->connection->log;
|
||||
memset(func, 0, sizeof(func));
|
||||
|
||||
if (ngx_rtmp_amf0_read(&act, elts,
|
||||
sizeof(elts) / sizeof(elts[0])) != NGX_OK)
|
||||
{
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0,
|
||||
"AMF0 cmd failed");
|
||||
return NGX_ERROR;
|
||||
while (--n >= 0) {
|
||||
rc = ngx_rtmp_fetch(in, &r[n]);
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
len = ngx_strlen(func);
|
||||
|
||||
/* lookup function handler
|
||||
* only the first handler is called so far
|
||||
* because ngx_hash_find only returns one item;
|
||||
* no good to patch NGINX core ;) */
|
||||
ch = ngx_hash_find(&cmcf->amf0_hash,
|
||||
ngx_hash_strlow(func, func, len), func, len);
|
||||
|
||||
if (ch) {
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0,
|
||||
"AMF0 func '%s' passed to handler", func);
|
||||
|
||||
return ch(s, h, in);
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0,
|
||||
"AMF0 cmd '%s' no handler", func);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_receive_amf0(ngx_rtmp_session_t *s, ngx_chain_t *in,
|
||||
ngx_rtmp_amf0_elt_t *elts, size_t nelts)
|
||||
ngx_rtmp_aggregate_message_handler(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
||||
ngx_chain_t *in)
|
||||
{
|
||||
ngx_rtmp_amf0_ctx_t act;
|
||||
uint32_t base_time, timestamp, prev_size;
|
||||
size_t len;
|
||||
ngx_int_t first;
|
||||
u_char *last;
|
||||
ngx_int_t rc;
|
||||
ngx_buf_t *b;
|
||||
ngx_chain_t *cl, *next;
|
||||
ngx_rtmp_header_t ch;
|
||||
|
||||
ch = *h;
|
||||
|
||||
first = 1;
|
||||
base_time = 0;
|
||||
|
||||
while (in) {
|
||||
if (ngx_rtmp_fetch_uint8(&in, &ch.type) != NGX_OK) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_fetch_uint32(&in, &ch.mlen, 3) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_fetch_uint32(&in, ×tamp, 3) != NGX_OK) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_fetch_uint8(&in, (uint8_t *) ×tamp + 3) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_rtmp_fetch_uint32(&in, &ch.msid, 3) != NGX_OK)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
if (first) {
|
||||
base_time = timestamp;
|
||||
first = 0;
|
||||
}
|
||||
|
||||
ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"RTMP aggregate %s (%d) len=%uD time=%uD (+%D) msid=%uD",
|
||||
ngx_rtmp_message_type(ch.type),
|
||||
(ngx_int_t) ch.type, ch.mlen, ch.timestamp,
|
||||
timestamp - base_time, ch.msid);
|
||||
|
||||
/* limit chain */
|
||||
|
||||
len = 0;
|
||||
cl = in;
|
||||
while (cl) {
|
||||
b = cl->buf;
|
||||
len += (b->last - b->pos);
|
||||
if (len > ch.mlen) {
|
||||
break;
|
||||
}
|
||||
cl = cl->next;
|
||||
}
|
||||
|
||||
if (cl == NULL) {
|
||||
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
|
||||
"RTMP error parsing aggregate");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
next = cl->next;
|
||||
cl->next = NULL;
|
||||
b = cl->buf;
|
||||
last = b->last;
|
||||
b->last -= (len - ch.mlen);
|
||||
|
||||
/* handle aggregated message */
|
||||
|
||||
ch.timestamp = h->timestamp + timestamp - base_time;
|
||||
|
||||
rc = ngx_rtmp_receive_message(s, &ch, in);
|
||||
|
||||
/* restore chain before checking the result */
|
||||
|
||||
in = cl;
|
||||
in->next = next;
|
||||
b->pos = b->last;
|
||||
b->last = last;
|
||||
|
||||
if (rc != NGX_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* read 32-bit previous tag size */
|
||||
|
||||
if (ngx_rtmp_fetch_uint32(&in, &prev_size, 4) != NGX_OK) {
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"RTMP aggregate prev_size=%uD", prev_size);
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_amf_message_handler(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_header_t *h, ngx_chain_t *in)
|
||||
{
|
||||
ngx_rtmp_amf_ctx_t act;
|
||||
ngx_rtmp_core_main_conf_t *cmcf;
|
||||
ngx_array_t *ch;
|
||||
ngx_rtmp_handler_pt *ph;
|
||||
size_t len, n;
|
||||
|
||||
static u_char func[128];
|
||||
|
||||
static ngx_rtmp_amf_elt_t elts[] = {
|
||||
|
||||
{ NGX_RTMP_AMF_STRING,
|
||||
ngx_null_string,
|
||||
func, sizeof(func) },
|
||||
};
|
||||
|
||||
/* AMF command names come with string type, but shared object names
|
||||
* come without type */
|
||||
if (h->type == NGX_RTMP_MSG_AMF_SHARED ||
|
||||
h->type == NGX_RTMP_MSG_AMF3_SHARED)
|
||||
{
|
||||
elts[0].type |= NGX_RTMP_AMF_TYPELESS;
|
||||
} else {
|
||||
elts[0].type &= ~NGX_RTMP_AMF_TYPELESS;
|
||||
}
|
||||
|
||||
if ((h->type == NGX_RTMP_MSG_AMF3_SHARED ||
|
||||
h->type == NGX_RTMP_MSG_AMF3_META ||
|
||||
h->type == NGX_RTMP_MSG_AMF3_CMD)
|
||||
&& in->buf->last > in->buf->pos)
|
||||
{
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"AMF3 prefix: %ui", (ngx_int_t)*in->buf->pos);
|
||||
++in->buf->pos;
|
||||
}
|
||||
|
||||
cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module);
|
||||
|
||||
/* read AMF func name & transaction id */
|
||||
ngx_memzero(&act, sizeof(act));
|
||||
act.link = in;
|
||||
act.log = s->connection->log;
|
||||
memset(func, 0, sizeof(func));
|
||||
|
||||
if (ngx_rtmp_amf_read(&act, elts,
|
||||
sizeof(elts) / sizeof(elts[0])) != NGX_OK)
|
||||
{
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"AMF cmd failed");
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/* skip name */
|
||||
in = act.link;
|
||||
in->buf->pos += act.offset;
|
||||
|
||||
len = ngx_strlen(func);
|
||||
|
||||
ch = ngx_hash_find(&cmcf->amf_hash,
|
||||
ngx_hash_strlow(func, func, len), func, len);
|
||||
|
||||
if (ch && ch->nelts) {
|
||||
ph = ch->elts;
|
||||
for (n = 0; n < ch->nelts; ++n, ++ph) {
|
||||
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"AMF func '%s' passed to handler %d/%d",
|
||||
func, n, ch->nelts);
|
||||
switch ((*ph)(s, h, in)) {
|
||||
case NGX_ERROR:
|
||||
return NGX_ERROR;
|
||||
case NGX_DONE:
|
||||
return NGX_OK;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
|
||||
"AMF cmd '%s' no handler", func);
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
ngx_int_t
|
||||
ngx_rtmp_receive_amf(ngx_rtmp_session_t *s, ngx_chain_t *in,
|
||||
ngx_rtmp_amf_elt_t *elts, size_t nelts)
|
||||
{
|
||||
ngx_rtmp_amf_ctx_t act;
|
||||
|
||||
ngx_memzero(&act, sizeof(act));
|
||||
act.link = in;
|
||||
act.log = s->connection->log;
|
||||
|
||||
return ngx_rtmp_amf0_read(&act, elts, nelts);
|
||||
return ngx_rtmp_amf_read(&act, elts, nelts);
|
||||
}
|
||||
|
||||
|
|
1356
ngx_rtmp_record_module.c
Normal file
1356
ngx_rtmp_record_module.c
Normal file
File diff suppressed because it is too large
Load diff
111
ngx_rtmp_record_module.h
Normal file
111
ngx_rtmp_record_module.h
Normal file
|
@ -0,0 +1,111 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_RECORD_H_INCLUDED_
|
||||
#define _NGX_RTMP_RECORD_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
||||
|
||||
#define NGX_RTMP_RECORD_OFF 0x01
|
||||
#define NGX_RTMP_RECORD_AUDIO 0x02
|
||||
#define NGX_RTMP_RECORD_VIDEO 0x04
|
||||
#define NGX_RTMP_RECORD_DATA 0x08
|
||||
#define NGX_RTMP_RECORD_KEYFRAMES 0x10
|
||||
#define NGX_RTMP_RECORD_MANUAL 0x20
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t id;
|
||||
ngx_uint_t flags;
|
||||
ngx_str_t path;
|
||||
size_t max_size;
|
||||
size_t interval_size;
|
||||
size_t max_frames;
|
||||
ngx_msec_t interval;
|
||||
ngx_str_t suffix;
|
||||
ngx_flag_t unique;
|
||||
ngx_flag_t append;
|
||||
ngx_flag_t lock_file;
|
||||
ngx_flag_t notify;
|
||||
ngx_url_t *url;
|
||||
|
||||
void **rec_conf;
|
||||
ngx_array_t rec; /* ngx_rtmp_record_app_conf_t * */
|
||||
} ngx_rtmp_record_app_conf_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_rtmp_record_app_conf_t *conf;
|
||||
ngx_file_t file;
|
||||
ngx_uint_t nframes;
|
||||
uint32_t epoch, time_shift;
|
||||
ngx_time_t last;
|
||||
time_t timestamp;
|
||||
unsigned failed:1;
|
||||
unsigned initialized:1;
|
||||
unsigned aac_header_sent:1;
|
||||
unsigned avc_header_sent:1;
|
||||
unsigned video_key_sent:1;
|
||||
unsigned audio:1;
|
||||
unsigned video:1;
|
||||
unsigned record_started:1;
|
||||
} ngx_rtmp_record_rec_ctx_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_array_t rec; /* ngx_rtmp_record_rec_ctx_t */
|
||||
u_char name[NGX_RTMP_MAX_NAME];
|
||||
u_char args[NGX_RTMP_MAX_ARGS];
|
||||
} ngx_rtmp_record_ctx_t;
|
||||
|
||||
|
||||
ngx_uint_t ngx_rtmp_record_find(ngx_rtmp_record_app_conf_t *racf,
|
||||
ngx_str_t *id);
|
||||
|
||||
|
||||
/* Manual recording control,
|
||||
* 'n' is record node index in config array.
|
||||
* Note: these functions allocate path in static buffer */
|
||||
|
||||
ngx_int_t ngx_rtmp_record_open(ngx_rtmp_session_t *s, ngx_uint_t n,
|
||||
ngx_str_t *path);
|
||||
ngx_int_t ngx_rtmp_record_close(ngx_rtmp_session_t *s, ngx_uint_t n,
|
||||
ngx_str_t *path);
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t recorder;
|
||||
ngx_str_t path;
|
||||
} ngx_rtmp_record_done_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_str_t recorder;
|
||||
ngx_str_t path;
|
||||
} ngx_rtmp_record_started_t;
|
||||
|
||||
|
||||
typedef ngx_int_t (*ngx_rtmp_record_started_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_record_started_t *v);
|
||||
|
||||
|
||||
typedef ngx_int_t (*ngx_rtmp_record_done_pt)(ngx_rtmp_session_t *s,
|
||||
ngx_rtmp_record_done_t *v);
|
||||
|
||||
|
||||
extern ngx_rtmp_record_started_pt ngx_rtmp_record_started;
|
||||
|
||||
|
||||
extern ngx_rtmp_record_done_pt ngx_rtmp_record_done;
|
||||
|
||||
|
||||
extern ngx_module_t ngx_rtmp_record_module;
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_RECORD_H_INCLUDED_ */
|
1899
ngx_rtmp_relay_module.c
Normal file
1899
ngx_rtmp_relay_module.c
Normal file
File diff suppressed because it is too large
Load diff
72
ngx_rtmp_relay_module.h
Normal file
72
ngx_rtmp_relay_module.h
Normal file
|
@ -0,0 +1,72 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_RELAY_H_INCLUDED_
|
||||
#define _NGX_RTMP_RELAY_H_INCLUDED_
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_url_t url;
|
||||
ngx_str_t app;
|
||||
ngx_str_t name;
|
||||
ngx_str_t tc_url;
|
||||
ngx_str_t page_url;
|
||||
ngx_str_t swf_url;
|
||||
ngx_str_t flash_ver;
|
||||
ngx_str_t play_path;
|
||||
ngx_int_t live;
|
||||
ngx_int_t start;
|
||||
ngx_int_t stop;
|
||||
|
||||
void *tag; /* usually module reference */
|
||||
void *data; /* module-specific data */
|
||||
ngx_uint_t counter; /* mutable connection counter */
|
||||
} ngx_rtmp_relay_target_t;
|
||||
|
||||
|
||||
typedef struct ngx_rtmp_relay_ctx_s ngx_rtmp_relay_ctx_t;
|
||||
|
||||
struct ngx_rtmp_relay_ctx_s {
|
||||
ngx_str_t name;
|
||||
ngx_str_t url;
|
||||
ngx_log_t log;
|
||||
ngx_rtmp_session_t *session;
|
||||
ngx_rtmp_relay_ctx_t *publish;
|
||||
ngx_rtmp_relay_ctx_t *play;
|
||||
ngx_rtmp_relay_ctx_t *next;
|
||||
|
||||
ngx_str_t app;
|
||||
ngx_str_t tc_url;
|
||||
ngx_str_t page_url;
|
||||
ngx_str_t swf_url;
|
||||
ngx_str_t flash_ver;
|
||||
ngx_str_t play_path;
|
||||
ngx_int_t live;
|
||||
ngx_int_t start;
|
||||
ngx_int_t stop;
|
||||
|
||||
ngx_event_t push_evt;
|
||||
ngx_event_t *static_evt;
|
||||
void *tag;
|
||||
void *data;
|
||||
};
|
||||
|
||||
|
||||
extern ngx_module_t ngx_rtmp_relay_module;
|
||||
|
||||
|
||||
ngx_int_t ngx_rtmp_relay_pull(ngx_rtmp_session_t *s, ngx_str_t *name,
|
||||
ngx_rtmp_relay_target_t *target);
|
||||
ngx_int_t ngx_rtmp_relay_push(ngx_rtmp_session_t *s, ngx_str_t *name,
|
||||
ngx_rtmp_relay_target_t *target);
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_RELAY_H_INCLUDED_ */
|
979
ngx_rtmp_send.c
979
ngx_rtmp_send.c
File diff suppressed because it is too large
Load diff
|
@ -1,35 +1,21 @@
|
|||
|
||||
/*
|
||||
* Copyright (c) 2012 Roman Arutyunyan
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include "ngx_rtmp.h"
|
||||
|
||||
|
||||
/* Store refcount in negative bytes of shared buffer */
|
||||
|
||||
#define NGX_RTMP_REFCOUNT_TYPE uint32_t
|
||||
#define NGX_RTMP_REFCOUNT_BYTES sizeof(NGX_RTMP_REFCOUNT_TYPE)
|
||||
|
||||
#define ngx_rtmp_ref(b) \
|
||||
*((NGX_RTMP_REFCOUNT_TYPE*)(b) - 1)
|
||||
|
||||
#define ngx_rtmp_ref_set(b, v) \
|
||||
ngx_rtmp_ref(b) = v
|
||||
|
||||
#define ngx_rtmp_ref_get(b) \
|
||||
++ngx_rtmp_ref(b)
|
||||
|
||||
#define ngx_rtmp_ref_put(b) \
|
||||
--ngx_rtmp_ref(b)
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_alloc_shared_buf(ngx_rtmp_core_srv_conf_t *cscf)
|
||||
{
|
||||
ngx_chain_t *out;
|
||||
ngx_buf_t *b;
|
||||
size_t size;
|
||||
u_char *p;
|
||||
ngx_chain_t *out;
|
||||
ngx_buf_t *b;
|
||||
size_t size;
|
||||
|
||||
if (cscf->free) {
|
||||
out = cscf->free;
|
||||
|
@ -37,36 +23,25 @@ ngx_rtmp_alloc_shared_buf(ngx_rtmp_core_srv_conf_t *cscf)
|
|||
|
||||
} else {
|
||||
|
||||
if (cscf->free_chains) {
|
||||
out = cscf->free_chains;
|
||||
cscf->free_chains = out->next;
|
||||
size = cscf->chunk_size + NGX_RTMP_MAX_CHUNK_HEADER;
|
||||
|
||||
} else {
|
||||
out = ngx_alloc_chain_link(cscf->pool);
|
||||
if (out == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
out->buf = ngx_calloc_buf(cscf->pool);
|
||||
if (out->buf == NULL) {
|
||||
ngx_free_chain(cscf->pool, out);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
size = cscf->chunk_size + NGX_RTMP_MAX_CHUNK_HEADER
|
||||
+ NGX_RTMP_REFCOUNT_BYTES;
|
||||
|
||||
b = out->buf;
|
||||
b->start = ngx_palloc(cscf->pool, size);
|
||||
if (b->start == NULL) {
|
||||
out->next = cscf->free_chains;
|
||||
cscf->free_chains = out;
|
||||
p = ngx_pcalloc(cscf->pool, NGX_RTMP_REFCOUNT_BYTES
|
||||
+ sizeof(ngx_chain_t)
|
||||
+ sizeof(ngx_buf_t)
|
||||
+ size);
|
||||
if (p == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
b->start += NGX_RTMP_REFCOUNT_BYTES;
|
||||
b->end = b->start + size - NGX_RTMP_REFCOUNT_BYTES;
|
||||
p += NGX_RTMP_REFCOUNT_BYTES;
|
||||
out = (ngx_chain_t *)p;
|
||||
|
||||
p += sizeof(ngx_chain_t);
|
||||
out->buf = (ngx_buf_t *)p;
|
||||
|
||||
p += sizeof(ngx_buf_t);
|
||||
out->buf->start = p;
|
||||
out->buf->end = p + size;
|
||||
}
|
||||
|
||||
out->next = NULL;
|
||||
|
@ -75,80 +50,34 @@ ngx_rtmp_alloc_shared_buf(ngx_rtmp_core_srv_conf_t *cscf)
|
|||
b->memory = 1;
|
||||
|
||||
/* buffer has refcount =1 when created! */
|
||||
ngx_rtmp_ref_set(b->start, 1);
|
||||
ngx_rtmp_ref_set(out, 1);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_rtmp_free_shared_bufs(ngx_rtmp_core_srv_conf_t *cscf, ngx_chain_t *out)
|
||||
ngx_rtmp_free_shared_chain(ngx_rtmp_core_srv_conf_t *cscf, ngx_chain_t *in)
|
||||
{
|
||||
ngx_chain_t *cl;
|
||||
ngx_chain_t *cl;
|
||||
|
||||
while (out) {
|
||||
cl = out;
|
||||
out = out->next;
|
||||
|
||||
if (ngx_rtmp_ref_put(cl->buf->start) == 0) {
|
||||
/* both chain & buf are free;
|
||||
* put the whole chain in free list */
|
||||
cl->next = cscf->free;
|
||||
cscf->free = cl;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* only chain is free;
|
||||
* buf is still used by somebody & will
|
||||
* be freed in ngx_rtmp_free_shared_buf */
|
||||
cl->next = cscf->free_chains;
|
||||
cscf->free_chains = cl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_rtmp_acquire_shared_buf(ngx_buf_t *b)
|
||||
{
|
||||
ngx_rtmp_ref_get(b->start);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ngx_rtmp_free_shared_buf(ngx_rtmp_core_srv_conf_t *cscf, ngx_buf_t *b)
|
||||
{
|
||||
ngx_chain_t *cl;
|
||||
|
||||
if (ngx_rtmp_ref_put(b->start)) {
|
||||
if (ngx_rtmp_ref_put(in)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cscf->free_chains) {
|
||||
cl = cscf->free_chains;
|
||||
cscf->free_chains = cl->next;
|
||||
|
||||
} else {
|
||||
cl = ngx_alloc_chain_link(cscf->pool);
|
||||
if (cl == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
cl->buf = ngx_calloc_buf(cscf->pool);
|
||||
if (cl->buf == NULL) {
|
||||
ngx_free_chain(cscf->pool, cl);
|
||||
for (cl = in; ; cl = cl->next) {
|
||||
/* FIXME: Don't create circular chains in the first place */
|
||||
if (cl->next == NULL || cl->next == in) {
|
||||
cl->next = cscf->free;
|
||||
cscf->free = in;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cl->buf->start = b->start;
|
||||
cl->buf->end = b->end;
|
||||
cl->next = cscf->free;
|
||||
cscf->free = cl;
|
||||
}
|
||||
|
||||
|
||||
ngx_chain_t *
|
||||
ngx_rtmp_append_shared_bufs(ngx_rtmp_core_srv_conf_t *cscf,
|
||||
ngx_rtmp_append_shared_bufs(ngx_rtmp_core_srv_conf_t *cscf,
|
||||
ngx_chain_t *head, ngx_chain_t *in)
|
||||
{
|
||||
ngx_chain_t *l, **ll;
|
||||
|
@ -177,7 +106,7 @@ ngx_rtmp_append_shared_bufs(ngx_rtmp_core_srv_conf_t *cscf,
|
|||
}
|
||||
|
||||
while (l->buf->end - l->buf->last >= in->buf->last - p) {
|
||||
l->buf->last = ngx_cpymem(l->buf->last, p,
|
||||
l->buf->last = ngx_cpymem(l->buf->last, p,
|
||||
in->buf->last - p);
|
||||
in = in->next;
|
||||
if (in == NULL) {
|
||||
|
|
931
ngx_rtmp_stat_module.c
Normal file
931
ngx_rtmp_stat_module.c
Normal file
|
@ -0,0 +1,931 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_http.h>
|
||||
#include <nginx.h>
|
||||
#include "ngx_rtmp.h"
|
||||
#include "ngx_rtmp_version.h"
|
||||
#include "ngx_rtmp_live_module.h"
|
||||
#include "ngx_rtmp_play_module.h"
|
||||
#include "ngx_rtmp_codec_module.h"
|
||||
#include "ngx_rtmp_record_module.h"
|
||||
|
||||
|
||||
static ngx_int_t ngx_rtmp_stat_init_process(ngx_cycle_t *cycle);
|
||||
static char *ngx_rtmp_stat(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
|
||||
static ngx_int_t ngx_rtmp_stat_postconfiguration(ngx_conf_t *cf);
|
||||
static void * ngx_rtmp_stat_create_loc_conf(ngx_conf_t *cf);
|
||||
static char * ngx_rtmp_stat_merge_loc_conf(ngx_conf_t *cf,
|
||||
void *parent, void *child);
|
||||
|
||||
|
||||
static time_t start_time;
|
||||
|
||||
|
||||
#define NGX_RTMP_STAT_ALL 0xff
|
||||
#define NGX_RTMP_STAT_GLOBAL 0x01
|
||||
#define NGX_RTMP_STAT_LIVE 0x02
|
||||
#define NGX_RTMP_STAT_CLIENTS 0x04
|
||||
#define NGX_RTMP_STAT_PLAY 0x08
|
||||
|
||||
/*
|
||||
* global: stat-{bufs-{total,free,used}, total bytes in/out, bw in/out} - cscf
|
||||
*/
|
||||
|
||||
|
||||
typedef struct {
|
||||
ngx_uint_t stat;
|
||||
ngx_str_t stylesheet;
|
||||
} ngx_rtmp_stat_loc_conf_t;
|
||||
|
||||
|
||||
static ngx_conf_bitmask_t ngx_rtmp_stat_masks[] = {
|
||||
{ ngx_string("all"), NGX_RTMP_STAT_ALL },
|
||||
{ ngx_string("global"), NGX_RTMP_STAT_GLOBAL },
|
||||
{ ngx_string("live"), NGX_RTMP_STAT_LIVE },
|
||||
{ ngx_string("clients"), NGX_RTMP_STAT_CLIENTS },
|
||||
{ ngx_null_string, 0 }
|
||||
};
|
||||
|
||||
|
||||
static ngx_command_t ngx_rtmp_stat_commands[] = {
|
||||
|
||||
{ ngx_string("rtmp_stat"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
|
||||
ngx_rtmp_stat,
|
||||
NGX_HTTP_LOC_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_stat_loc_conf_t, stat),
|
||||
ngx_rtmp_stat_masks },
|
||||
|
||||
{ ngx_string("rtmp_stat_stylesheet"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_str_slot,
|
||||
NGX_HTTP_LOC_CONF_OFFSET,
|
||||
offsetof(ngx_rtmp_stat_loc_conf_t, stylesheet),
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
|
||||
static ngx_http_module_t ngx_rtmp_stat_module_ctx = {
|
||||
NULL, /* preconfiguration */
|
||||
ngx_rtmp_stat_postconfiguration, /* postconfiguration */
|
||||
|
||||
NULL, /* create main configuration */
|
||||
NULL, /* init main configuration */
|
||||
|
||||
NULL, /* create server configuration */
|
||||
NULL, /* merge server configuration */
|
||||
|
||||
ngx_rtmp_stat_create_loc_conf, /* create location configuration */
|
||||
ngx_rtmp_stat_merge_loc_conf, /* merge location configuration */
|
||||
};
|
||||
|
||||
|
||||
ngx_module_t ngx_rtmp_stat_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_rtmp_stat_module_ctx, /* module context */
|
||||
ngx_rtmp_stat_commands, /* module directives */
|
||||
NGX_HTTP_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
ngx_rtmp_stat_init_process, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
#define NGX_RTMP_STAT_BUFSIZE 256
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_stat_init_process(ngx_cycle_t *cycle)
|
||||
{
|
||||
/*
|
||||
* HTTP process initializer is called
|
||||
* after event module initializer
|
||||
* so we can run posted events here
|
||||
*/
|
||||
|
||||
ngx_event_process_posted(cycle, (ngx_queue_t*) &ngx_rtmp_init_queue);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
/* ngx_escape_html does not escape characters out of ASCII range
|
||||
* which are bad for xslt */
|
||||
|
||||
static void *
|
||||
ngx_rtmp_stat_escape(ngx_http_request_t *r, void *data, size_t len)
|
||||
{
|
||||
u_char *p, *np;
|
||||
void *new_data;
|
||||
size_t n;
|
||||
|
||||
p = data;
|
||||
|
||||
for (n = 0; n < len; ++n, ++p) {
|
||||
if (*p < 0x20 || *p >= 0x7f) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (n == len) {
|
||||
return data;
|
||||
}
|
||||
|
||||
new_data = ngx_palloc(r->pool, len);
|
||||
if (new_data == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
p = data;
|
||||
np = new_data;
|
||||
|
||||
for (n = 0; n < len; ++n, ++p, ++np) {
|
||||
*np = (*p < 0x20 || *p >= 0x7f) ? (u_char) ' ' : *p;
|
||||
}
|
||||
|
||||
return new_data;
|
||||
}
|
||||
|
||||
#if (NGX_WIN32)
|
||||
/*
|
||||
* Fix broken MSVC memcpy optimization for 4-byte data
|
||||
* when this function is inlined
|
||||
*/
|
||||
__declspec(noinline)
|
||||
#endif
|
||||
|
||||
static void
|
||||
ngx_rtmp_stat_output(ngx_http_request_t *r, ngx_chain_t ***lll,
|
||||
void *data, size_t len, ngx_uint_t escape)
|
||||
{
|
||||
ngx_chain_t *cl;
|
||||
ngx_buf_t *b;
|
||||
size_t real_len;
|
||||
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (escape) {
|
||||
data = ngx_rtmp_stat_escape(r, data, len);
|
||||
if (data == NULL) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
real_len = escape
|
||||
? len + ngx_escape_html(NULL, data, len)
|
||||
: len;
|
||||
|
||||
cl = **lll;
|
||||
if (cl && cl->buf->last + real_len > cl->buf->end) {
|
||||
*lll = &cl->next;
|
||||
}
|
||||
|
||||
if (**lll == NULL) {
|
||||
cl = ngx_alloc_chain_link(r->pool);
|
||||
if (cl == NULL) {
|
||||
return;
|
||||
}
|
||||
b = ngx_create_temp_buf(r->pool,
|
||||
ngx_max(NGX_RTMP_STAT_BUFSIZE, real_len));
|
||||
if (b == NULL || b->pos == NULL) {
|
||||
return;
|
||||
}
|
||||
cl->next = NULL;
|
||||
cl->buf = b;
|
||||
**lll = cl;
|
||||
}
|
||||
|
||||
b = (**lll)->buf;
|
||||
|
||||
if (escape) {
|
||||
b->last = (u_char *)ngx_escape_html(b->last, data, len);
|
||||
} else {
|
||||
b->last = ngx_cpymem(b->last, data, len);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* These shortcuts assume 2 variables exist in current context:
|
||||
* ngx_http_request_t *r
|
||||
* ngx_chain_t ***lll */
|
||||
|
||||
/* plain data */
|
||||
#define NGX_RTMP_STAT(data, len) ngx_rtmp_stat_output(r, lll, data, len, 0)
|
||||
|
||||
/* escaped data */
|
||||
#define NGX_RTMP_STAT_E(data, len) ngx_rtmp_stat_output(r, lll, data, len, 1)
|
||||
|
||||
/* literal */
|
||||
#define NGX_RTMP_STAT_L(s) NGX_RTMP_STAT((s), sizeof(s) - 1)
|
||||
|
||||
/* ngx_str_t */
|
||||
#define NGX_RTMP_STAT_S(s) NGX_RTMP_STAT((s)->data, (s)->len)
|
||||
|
||||
/* escaped ngx_str_t */
|
||||
#define NGX_RTMP_STAT_ES(s) NGX_RTMP_STAT_E((s)->data, (s)->len)
|
||||
|
||||
/* C string */
|
||||
#define NGX_RTMP_STAT_CS(s) NGX_RTMP_STAT((s), ngx_strlen(s))
|
||||
|
||||
/* escaped C string */
|
||||
#define NGX_RTMP_STAT_ECS(s) NGX_RTMP_STAT_E((s), ngx_strlen(s))
|
||||
|
||||
|
||||
#define NGX_RTMP_STAT_BW 0x01
|
||||
#define NGX_RTMP_STAT_BYTES 0x02
|
||||
#define NGX_RTMP_STAT_BW_BYTES 0x03
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_stat_bw(ngx_http_request_t *r, ngx_chain_t ***lll,
|
||||
ngx_rtmp_bandwidth_t *bw, char *name,
|
||||
ngx_uint_t flags)
|
||||
{
|
||||
u_char buf[NGX_INT64_LEN + 9];
|
||||
|
||||
ngx_rtmp_update_bandwidth(bw, 0);
|
||||
|
||||
if (flags & NGX_RTMP_STAT_BW) {
|
||||
NGX_RTMP_STAT_L("<bw_");
|
||||
NGX_RTMP_STAT_CS(name);
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), ">%uL</bw_",
|
||||
bw->bandwidth * 8)
|
||||
- buf);
|
||||
NGX_RTMP_STAT_CS(name);
|
||||
NGX_RTMP_STAT_L(">\r\n");
|
||||
}
|
||||
|
||||
if (flags & NGX_RTMP_STAT_BYTES) {
|
||||
NGX_RTMP_STAT_L("<bytes_");
|
||||
NGX_RTMP_STAT_CS(name);
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), ">%uL</bytes_",
|
||||
bw->bytes)
|
||||
- buf);
|
||||
NGX_RTMP_STAT_CS(name);
|
||||
NGX_RTMP_STAT_L(">\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef NGX_RTMP_POOL_DEBUG
|
||||
static void
|
||||
ngx_rtmp_stat_get_pool_size(ngx_pool_t *pool, ngx_uint_t *nlarge,
|
||||
ngx_uint_t *size)
|
||||
{
|
||||
ngx_pool_large_t *l;
|
||||
ngx_pool_t *p, *n;
|
||||
|
||||
*nlarge = 0;
|
||||
for (l = pool->large; l; l = l->next) {
|
||||
++*nlarge;
|
||||
}
|
||||
|
||||
*size = 0;
|
||||
for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
|
||||
*size += (p->d.last - (u_char *)p);
|
||||
if (n == NULL) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_stat_dump_pool(ngx_http_request_t *r, ngx_chain_t ***lll,
|
||||
ngx_pool_t *pool)
|
||||
{
|
||||
ngx_uint_t nlarge, size;
|
||||
u_char buf[NGX_INT_T_LEN];
|
||||
|
||||
size = 0;
|
||||
nlarge = 0;
|
||||
ngx_rtmp_stat_get_pool_size(pool, &nlarge, &size);
|
||||
NGX_RTMP_STAT_L("<pool><nlarge>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%ui", nlarge) - buf);
|
||||
NGX_RTMP_STAT_L("</nlarge><size>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%ui", size) - buf);
|
||||
NGX_RTMP_STAT_L("</size></pool>\r\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_stat_client(ngx_http_request_t *r, ngx_chain_t ***lll,
|
||||
ngx_rtmp_session_t *s)
|
||||
{
|
||||
u_char buf[NGX_INT_T_LEN];
|
||||
struct sockaddr_in *sa;
|
||||
|
||||
#ifdef NGX_RTMP_POOL_DEBUG
|
||||
ngx_rtmp_stat_dump_pool(r, lll, s->connection->pool);
|
||||
#endif
|
||||
NGX_RTMP_STAT_L("<id>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%ui",
|
||||
(ngx_uint_t) s->connection->number) - buf);
|
||||
NGX_RTMP_STAT_L("</id>");
|
||||
|
||||
NGX_RTMP_STAT_L("<address>");
|
||||
NGX_RTMP_STAT_ES(&s->connection->addr_text);
|
||||
NGX_RTMP_STAT_L("</address>");
|
||||
|
||||
/*
|
||||
** Displays socket port number
|
||||
*/
|
||||
NGX_RTMP_STAT_L("<port>");
|
||||
sa = (struct sockaddr_in *) s->connection->sockaddr;
|
||||
if (sa) {
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%ui",
|
||||
(ngx_uint_t) ntohs(sa->sin_port)) - buf);
|
||||
} else {
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%ui", 0) - buf);
|
||||
}
|
||||
NGX_RTMP_STAT_L("</port>");
|
||||
|
||||
|
||||
NGX_RTMP_STAT_L("<time>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%i",
|
||||
(ngx_int_t) (ngx_current_msec - s->epoch)) - buf);
|
||||
NGX_RTMP_STAT_L("</time>");
|
||||
|
||||
if (s->flashver.len) {
|
||||
NGX_RTMP_STAT_L("<flashver>");
|
||||
NGX_RTMP_STAT_ES(&s->flashver);
|
||||
NGX_RTMP_STAT_L("</flashver>");
|
||||
}
|
||||
|
||||
if (s->page_url.len) {
|
||||
NGX_RTMP_STAT_L("<pageurl>");
|
||||
NGX_RTMP_STAT_ES(&s->page_url);
|
||||
NGX_RTMP_STAT_L("</pageurl>");
|
||||
}
|
||||
|
||||
if (s->swf_url.len) {
|
||||
NGX_RTMP_STAT_L("<swfurl>");
|
||||
NGX_RTMP_STAT_ES(&s->swf_url);
|
||||
NGX_RTMP_STAT_L("</swfurl>");
|
||||
}
|
||||
|
||||
NGX_RTMP_STAT_L("<bytes_in>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%ui", (ngx_uint_t) s->in_bytes) - buf);
|
||||
NGX_RTMP_STAT_L("</bytes_in>");
|
||||
|
||||
NGX_RTMP_STAT_L("<bytes_out>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%ui", (ngx_uint_t) s->out_bytes) - buf);
|
||||
NGX_RTMP_STAT_L("</bytes_out>");
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_rtmp_stat_get_aac_profile(ngx_uint_t p, ngx_uint_t sbr, ngx_uint_t ps) {
|
||||
switch (p) {
|
||||
case 1:
|
||||
return "Main";
|
||||
case 2:
|
||||
if (ps) {
|
||||
return "HEv2";
|
||||
}
|
||||
if (sbr) {
|
||||
return "HE";
|
||||
}
|
||||
return "LC";
|
||||
case 3:
|
||||
return "SSR";
|
||||
case 4:
|
||||
return "LTP";
|
||||
case 5:
|
||||
return "SBR";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_rtmp_stat_get_avc_profile(ngx_uint_t p) {
|
||||
switch (p) {
|
||||
case 66:
|
||||
return "Baseline";
|
||||
case 77:
|
||||
return "Main";
|
||||
case 100:
|
||||
return "High";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_stat_live(ngx_http_request_t *r, ngx_chain_t ***lll,
|
||||
ngx_rtmp_live_app_conf_t *lacf)
|
||||
{
|
||||
ngx_rtmp_live_stream_t *stream;
|
||||
ngx_rtmp_codec_ctx_t *codec;
|
||||
ngx_rtmp_live_ctx_t *ctx;
|
||||
ngx_rtmp_record_ctx_t *rctx;
|
||||
ngx_rtmp_record_rec_ctx_t *recctx;
|
||||
ngx_rtmp_session_t *s;
|
||||
ngx_int_t n;
|
||||
ngx_uint_t nclients, total_nclients, rn;
|
||||
u_char buf[NGX_INT_T_LEN];
|
||||
u_char bbuf[NGX_INT32_LEN];
|
||||
ngx_rtmp_stat_loc_conf_t *slcf;
|
||||
u_char *cname;
|
||||
|
||||
// Is any of stream clients (publisher) recording now
|
||||
u_char is_recording = 0;
|
||||
|
||||
if (!lacf->live) {
|
||||
return;
|
||||
}
|
||||
|
||||
slcf = ngx_http_get_module_loc_conf(r, ngx_rtmp_stat_module);
|
||||
|
||||
NGX_RTMP_STAT_L("<live>\r\n");
|
||||
|
||||
total_nclients = 0;
|
||||
for (n = 0; n < lacf->nbuckets; ++n) {
|
||||
for (stream = lacf->streams[n]; stream; stream = stream->next) {
|
||||
NGX_RTMP_STAT_L("<stream>\r\n");
|
||||
|
||||
is_recording = 0;
|
||||
|
||||
NGX_RTMP_STAT_L("<name>");
|
||||
NGX_RTMP_STAT_ECS(stream->name);
|
||||
NGX_RTMP_STAT_L("</name>\r\n");
|
||||
|
||||
NGX_RTMP_STAT_L("<time>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), "%i",
|
||||
(ngx_int_t) (ngx_current_msec - stream->epoch))
|
||||
- buf);
|
||||
NGX_RTMP_STAT_L("</time>");
|
||||
|
||||
ngx_rtmp_stat_bw(r, lll, &stream->bw_in, "in",
|
||||
NGX_RTMP_STAT_BW_BYTES);
|
||||
ngx_rtmp_stat_bw(r, lll, &stream->bw_out, "out",
|
||||
NGX_RTMP_STAT_BW_BYTES);
|
||||
ngx_rtmp_stat_bw(r, lll, &stream->bw_in_audio, "audio",
|
||||
NGX_RTMP_STAT_BW);
|
||||
ngx_rtmp_stat_bw(r, lll, &stream->bw_in_video, "video",
|
||||
NGX_RTMP_STAT_BW);
|
||||
ngx_rtmp_stat_bw(r, lll, &stream->bw_in_data, "data",
|
||||
NGX_RTMP_STAT_BW);
|
||||
|
||||
nclients = 0;
|
||||
codec = NULL;
|
||||
for (ctx = stream->ctx; ctx; ctx = ctx->next, ++nclients) {
|
||||
s = ctx->session;
|
||||
if (slcf->stat & NGX_RTMP_STAT_CLIENTS) {
|
||||
NGX_RTMP_STAT_L("<client>");
|
||||
|
||||
ngx_rtmp_stat_client(r, lll, s);
|
||||
|
||||
NGX_RTMP_STAT_L("<dropped>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
|
||||
"%ui", ctx->ndropped) - buf);
|
||||
NGX_RTMP_STAT_L("</dropped>");
|
||||
|
||||
NGX_RTMP_STAT_L("<avsync>");
|
||||
if (!lacf->interleave) {
|
||||
NGX_RTMP_STAT(bbuf, ngx_snprintf(bbuf, sizeof(bbuf),
|
||||
"%D", ctx->cs[1].timestamp -
|
||||
ctx->cs[0].timestamp) - bbuf);
|
||||
}
|
||||
NGX_RTMP_STAT_L("</avsync>");
|
||||
|
||||
NGX_RTMP_STAT_L("<timestamp>");
|
||||
NGX_RTMP_STAT(bbuf, ngx_snprintf(bbuf, sizeof(bbuf),
|
||||
"%D", s->current_time) - bbuf);
|
||||
NGX_RTMP_STAT_L("</timestamp>");
|
||||
|
||||
if (ctx->publishing) {
|
||||
NGX_RTMP_STAT_L("<publishing/>");
|
||||
}
|
||||
|
||||
if (ctx->active) {
|
||||
NGX_RTMP_STAT_L("<active/>");
|
||||
}
|
||||
|
||||
rctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module);
|
||||
if (rctx) {
|
||||
recctx = rctx->rec.elts;
|
||||
for (rn = 0; rn < rctx->rec.nelts; ++rn, ++recctx) {
|
||||
if (recctx->initialized && recctx->file.fd != NGX_INVALID_FILE) {
|
||||
NGX_RTMP_STAT_L("<recording/>");
|
||||
is_recording = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NGX_RTMP_STAT_L("</client>\r\n");
|
||||
}
|
||||
if (ctx->publishing) {
|
||||
codec = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
|
||||
}
|
||||
}
|
||||
total_nclients += nclients;
|
||||
|
||||
if (codec) {
|
||||
NGX_RTMP_STAT_L("<meta>");
|
||||
|
||||
NGX_RTMP_STAT_L("<video>");
|
||||
NGX_RTMP_STAT_L("<width>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
|
||||
"%ui", codec->width) - buf);
|
||||
NGX_RTMP_STAT_L("</width><height>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
|
||||
"%ui", codec->height) - buf);
|
||||
NGX_RTMP_STAT_L("</height><frame_rate>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
|
||||
"%.3f", codec->frame_rate) - buf);
|
||||
NGX_RTMP_STAT_L("</frame_rate><data_rate>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
|
||||
"%.0f", codec->video_data_rate) - buf);
|
||||
NGX_RTMP_STAT_L("</data_rate>");
|
||||
|
||||
if(codec->video_keyframe_frequency) {
|
||||
NGX_RTMP_STAT_L("<keyframe_frequency>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
|
||||
"%.0f", codec->video_keyframe_frequency) - buf);
|
||||
NGX_RTMP_STAT_L("</keyframe_frequency>");
|
||||
}
|
||||
|
||||
cname = ngx_rtmp_get_video_codec_name(codec->video_codec_id);
|
||||
if (*cname) {
|
||||
NGX_RTMP_STAT_L("<codec>");
|
||||
NGX_RTMP_STAT_ECS(cname);
|
||||
NGX_RTMP_STAT_L("</codec>");
|
||||
}
|
||||
if (codec->avc_profile) {
|
||||
NGX_RTMP_STAT_L("<profile>");
|
||||
NGX_RTMP_STAT_CS(
|
||||
ngx_rtmp_stat_get_avc_profile(codec->avc_profile));
|
||||
NGX_RTMP_STAT_L("</profile>");
|
||||
}
|
||||
if (codec->avc_level) {
|
||||
NGX_RTMP_STAT_L("<compat>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
|
||||
"%ui", codec->avc_compat) - buf);
|
||||
NGX_RTMP_STAT_L("</compat>");
|
||||
}
|
||||
if (codec->avc_level) {
|
||||
NGX_RTMP_STAT_L("<level>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
|
||||
"%.1f", codec->avc_level / 10.) - buf);
|
||||
NGX_RTMP_STAT_L("</level>");
|
||||
}
|
||||
NGX_RTMP_STAT_L("</video>");
|
||||
|
||||
NGX_RTMP_STAT_L("<audio>");
|
||||
cname = ngx_rtmp_get_audio_codec_name(codec->audio_codec_id);
|
||||
if (*cname) {
|
||||
NGX_RTMP_STAT_L("<codec>");
|
||||
NGX_RTMP_STAT_ECS(cname);
|
||||
NGX_RTMP_STAT_L("</codec>");
|
||||
}
|
||||
if (codec->aac_profile) {
|
||||
NGX_RTMP_STAT_L("<profile>");
|
||||
NGX_RTMP_STAT_CS(
|
||||
ngx_rtmp_stat_get_aac_profile(codec->aac_profile,
|
||||
codec->aac_sbr,
|
||||
codec->aac_ps));
|
||||
NGX_RTMP_STAT_L("</profile>");
|
||||
}
|
||||
if (codec->aac_chan_conf) {
|
||||
NGX_RTMP_STAT_L("<channels>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
|
||||
"%ui", codec->aac_chan_conf) - buf);
|
||||
NGX_RTMP_STAT_L("</channels>");
|
||||
} else if (codec->audio_channels) {
|
||||
NGX_RTMP_STAT_L("<channels>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
|
||||
"%ui", codec->audio_channels) - buf);
|
||||
NGX_RTMP_STAT_L("</channels>");
|
||||
}
|
||||
if (codec->sample_rate) {
|
||||
NGX_RTMP_STAT_L("<sample_rate>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
|
||||
"%ui", codec->sample_rate) - buf);
|
||||
NGX_RTMP_STAT_L("</sample_rate>");
|
||||
}
|
||||
if (codec->audio_data_rate) {
|
||||
NGX_RTMP_STAT_L("<data_rate>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
|
||||
"%0.0f", codec->audio_data_rate) - buf);
|
||||
NGX_RTMP_STAT_L("</data_rate>");
|
||||
}
|
||||
NGX_RTMP_STAT_L("</audio>");
|
||||
|
||||
NGX_RTMP_STAT_L("</meta>\r\n");
|
||||
}
|
||||
|
||||
NGX_RTMP_STAT_L("<nclients>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
|
||||
"%ui", nclients) - buf);
|
||||
NGX_RTMP_STAT_L("</nclients>\r\n");
|
||||
|
||||
if (stream->publishing) {
|
||||
NGX_RTMP_STAT_L("<publishing/>\r\n");
|
||||
}
|
||||
|
||||
if (stream->active) {
|
||||
NGX_RTMP_STAT_L("<active/>\r\n");
|
||||
}
|
||||
|
||||
if (is_recording) {
|
||||
NGX_RTMP_STAT_L("<recording/>\r\n");
|
||||
}
|
||||
|
||||
NGX_RTMP_STAT_L("</stream>\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
NGX_RTMP_STAT_L("<nclients>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
|
||||
"%ui", total_nclients) - buf);
|
||||
NGX_RTMP_STAT_L("</nclients>\r\n");
|
||||
|
||||
NGX_RTMP_STAT_L("</live>\r\n");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_stat_play(ngx_http_request_t *r, ngx_chain_t ***lll,
|
||||
ngx_rtmp_play_app_conf_t *pacf)
|
||||
{
|
||||
ngx_rtmp_play_ctx_t *ctx, *sctx;
|
||||
ngx_rtmp_session_t *s;
|
||||
ngx_uint_t n, nclients, total_nclients;
|
||||
u_char buf[NGX_INT_T_LEN];
|
||||
u_char bbuf[NGX_INT32_LEN];
|
||||
ngx_rtmp_stat_loc_conf_t *slcf;
|
||||
|
||||
if (pacf->entries.nelts == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
slcf = ngx_http_get_module_loc_conf(r, ngx_rtmp_stat_module);
|
||||
|
||||
NGX_RTMP_STAT_L("<play>\r\n");
|
||||
|
||||
total_nclients = 0;
|
||||
for (n = 0; n < pacf->nbuckets; ++n) {
|
||||
for (ctx = pacf->ctx[n]; ctx; ) {
|
||||
NGX_RTMP_STAT_L("<stream>\r\n");
|
||||
|
||||
NGX_RTMP_STAT_L("<name>");
|
||||
NGX_RTMP_STAT_ECS(ctx->name);
|
||||
NGX_RTMP_STAT_L("</name>\r\n");
|
||||
|
||||
nclients = 0;
|
||||
sctx = ctx;
|
||||
for (; ctx; ctx = ctx->next) {
|
||||
if (ngx_strcmp(ctx->name, sctx->name)) {
|
||||
break;
|
||||
}
|
||||
|
||||
nclients++;
|
||||
|
||||
s = ctx->session;
|
||||
if (slcf->stat & NGX_RTMP_STAT_CLIENTS) {
|
||||
NGX_RTMP_STAT_L("<client>");
|
||||
|
||||
ngx_rtmp_stat_client(r, lll, s);
|
||||
|
||||
NGX_RTMP_STAT_L("<timestamp>");
|
||||
NGX_RTMP_STAT(bbuf, ngx_snprintf(bbuf, sizeof(bbuf),
|
||||
"%D", s->current_time) - bbuf);
|
||||
NGX_RTMP_STAT_L("</timestamp>");
|
||||
|
||||
NGX_RTMP_STAT_L("</client>\r\n");
|
||||
}
|
||||
}
|
||||
total_nclients += nclients;
|
||||
|
||||
NGX_RTMP_STAT_L("<active/>");
|
||||
NGX_RTMP_STAT_L("<nclients>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
|
||||
"%ui", nclients) - buf);
|
||||
NGX_RTMP_STAT_L("</nclients>\r\n");
|
||||
|
||||
NGX_RTMP_STAT_L("</stream>\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
NGX_RTMP_STAT_L("<nclients>");
|
||||
NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),
|
||||
"%ui", total_nclients) - buf);
|
||||
NGX_RTMP_STAT_L("</nclients>\r\n");
|
||||
|
||||
NGX_RTMP_STAT_L("</play>\r\n");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_stat_application(ngx_http_request_t *r, ngx_chain_t ***lll,
|
||||
ngx_rtmp_core_app_conf_t *cacf)
|
||||
{
|
||||
ngx_rtmp_stat_loc_conf_t *slcf;
|
||||
|
||||
NGX_RTMP_STAT_L("<application>\r\n");
|
||||
NGX_RTMP_STAT_L("<name>");
|
||||
NGX_RTMP_STAT_ES(&cacf->name);
|
||||
NGX_RTMP_STAT_L("</name>\r\n");
|
||||
|
||||
slcf = ngx_http_get_module_loc_conf(r, ngx_rtmp_stat_module);
|
||||
|
||||
if (slcf->stat & NGX_RTMP_STAT_LIVE) {
|
||||
ngx_rtmp_stat_live(r, lll,
|
||||
cacf->app_conf[ngx_rtmp_live_module.ctx_index]);
|
||||
}
|
||||
|
||||
if (slcf->stat & NGX_RTMP_STAT_PLAY) {
|
||||
ngx_rtmp_stat_play(r, lll,
|
||||
cacf->app_conf[ngx_rtmp_play_module.ctx_index]);
|
||||
}
|
||||
|
||||
NGX_RTMP_STAT_L("</application>\r\n");
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
ngx_rtmp_stat_server(ngx_http_request_t *r, ngx_chain_t ***lll,
|
||||
ngx_rtmp_core_srv_conf_t *cscf)
|
||||
{
|
||||
ngx_rtmp_core_app_conf_t **cacf;
|
||||
size_t n;
|
||||
|
||||
NGX_RTMP_STAT_L("<server>\r\n");
|
||||
|
||||
#ifdef NGX_RTMP_POOL_DEBUG
|
||||
ngx_rtmp_stat_dump_pool(r, lll, cscf->pool);
|
||||
#endif
|
||||
|
||||
cacf = cscf->applications.elts;
|
||||
for (n = 0; n < cscf->applications.nelts; ++n, ++cacf) {
|
||||
ngx_rtmp_stat_application(r, lll, *cacf);
|
||||
}
|
||||
|
||||
NGX_RTMP_STAT_L("</server>\r\n");
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_stat_handler(ngx_http_request_t *r)
|
||||
{
|
||||
ngx_rtmp_stat_loc_conf_t *slcf;
|
||||
ngx_rtmp_core_main_conf_t *cmcf;
|
||||
ngx_rtmp_core_srv_conf_t **cscf;
|
||||
ngx_chain_t *cl, *l, **ll, ***lll;
|
||||
size_t n;
|
||||
off_t len;
|
||||
static u_char tbuf[NGX_TIME_T_LEN];
|
||||
static u_char nbuf[NGX_INT_T_LEN];
|
||||
|
||||
slcf = ngx_http_get_module_loc_conf(r, ngx_rtmp_stat_module);
|
||||
if (slcf->stat == 0) {
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
cmcf = ngx_rtmp_core_main_conf;
|
||||
if (cmcf == NULL) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
cl = NULL;
|
||||
ll = &cl;
|
||||
lll = ≪
|
||||
|
||||
NGX_RTMP_STAT_L("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n");
|
||||
if (slcf->stylesheet.len) {
|
||||
NGX_RTMP_STAT_L("<?xml-stylesheet type=\"text/xsl\" href=\"");
|
||||
NGX_RTMP_STAT_ES(&slcf->stylesheet);
|
||||
NGX_RTMP_STAT_L("\" ?>\r\n");
|
||||
}
|
||||
|
||||
NGX_RTMP_STAT_L("<rtmp>\r\n");
|
||||
|
||||
#ifdef NGINX_VERSION
|
||||
NGX_RTMP_STAT_L("<nginx_version>" NGINX_VERSION "</nginx_version>\r\n");
|
||||
#endif
|
||||
|
||||
#ifdef NGINX_RTMP_VERSION
|
||||
NGX_RTMP_STAT_L("<nginx_rtmp_version>" NGINX_RTMP_VERSION "</nginx_rtmp_version>\r\n");
|
||||
#endif
|
||||
|
||||
#ifdef NGX_COMPILER
|
||||
NGX_RTMP_STAT_L("<compiler>" NGX_COMPILER "</compiler>\r\n");
|
||||
#endif
|
||||
/* This may prevent reproducible builds. If you need that info - pass `-DNGX_BUILD_DATEITIME=1` to CFLAGS */
|
||||
#ifdef NGX_BUILD_DATEITIME
|
||||
NGX_RTMP_STAT_L("<built>" __DATE__ " " __TIME__ "</built>\r\n");
|
||||
#endif
|
||||
|
||||
NGX_RTMP_STAT_L("<pid>");
|
||||
NGX_RTMP_STAT(nbuf, ngx_snprintf(nbuf, sizeof(nbuf),
|
||||
"%ui", (ngx_uint_t) ngx_getpid()) - nbuf);
|
||||
NGX_RTMP_STAT_L("</pid>\r\n");
|
||||
|
||||
NGX_RTMP_STAT_L("<uptime>");
|
||||
NGX_RTMP_STAT(tbuf, ngx_snprintf(tbuf, sizeof(tbuf),
|
||||
"%T", ngx_cached_time->sec - start_time) - tbuf);
|
||||
NGX_RTMP_STAT_L("</uptime>\r\n");
|
||||
|
||||
NGX_RTMP_STAT_L("<naccepted>");
|
||||
NGX_RTMP_STAT(nbuf, ngx_snprintf(nbuf, sizeof(nbuf),
|
||||
"%ui", ngx_rtmp_naccepted) - nbuf);
|
||||
NGX_RTMP_STAT_L("</naccepted>\r\n");
|
||||
|
||||
ngx_rtmp_stat_bw(r, lll, &ngx_rtmp_bw_in, "in", NGX_RTMP_STAT_BW_BYTES);
|
||||
ngx_rtmp_stat_bw(r, lll, &ngx_rtmp_bw_out, "out", NGX_RTMP_STAT_BW_BYTES);
|
||||
|
||||
cscf = cmcf->servers.elts;
|
||||
for (n = 0; n < cmcf->servers.nelts; ++n, ++cscf) {
|
||||
ngx_rtmp_stat_server(r, lll, *cscf);
|
||||
}
|
||||
|
||||
NGX_RTMP_STAT_L("</rtmp>\r\n");
|
||||
|
||||
len = 0;
|
||||
for (l = cl; l; l = l->next) {
|
||||
len += (l->buf->last - l->buf->pos);
|
||||
}
|
||||
ngx_str_set(&r->headers_out.content_type, "text/xml");
|
||||
r->headers_out.content_length_n = len;
|
||||
r->headers_out.status = NGX_HTTP_OK;
|
||||
ngx_http_send_header(r);
|
||||
(*ll)->buf->last_buf = 1;
|
||||
return ngx_http_output_filter(r, cl);
|
||||
|
||||
error:
|
||||
r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
r->headers_out.content_length_n = 0;
|
||||
return ngx_http_send_header(r);
|
||||
}
|
||||
|
||||
|
||||
static void *
|
||||
ngx_rtmp_stat_create_loc_conf(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_rtmp_stat_loc_conf_t *conf;
|
||||
|
||||
conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_stat_loc_conf_t));
|
||||
if (conf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
conf->stat = 0;
|
||||
|
||||
return conf;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_rtmp_stat_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
|
||||
{
|
||||
ngx_rtmp_stat_loc_conf_t *prev = parent;
|
||||
ngx_rtmp_stat_loc_conf_t *conf = child;
|
||||
|
||||
ngx_conf_merge_bitmask_value(conf->stat, prev->stat, 0);
|
||||
ngx_conf_merge_str_value(conf->stylesheet, prev->stylesheet, "");
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
static char *
|
||||
ngx_rtmp_stat(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
|
||||
{
|
||||
ngx_http_core_loc_conf_t *clcf;
|
||||
|
||||
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
|
||||
clcf->handler = ngx_rtmp_stat_handler;
|
||||
|
||||
return ngx_conf_set_bitmask_slot(cf, cmd, conf);
|
||||
}
|
||||
|
||||
|
||||
static ngx_int_t
|
||||
ngx_rtmp_stat_postconfiguration(ngx_conf_t *cf)
|
||||
{
|
||||
start_time = ngx_cached_time->sec;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
19
ngx_rtmp_streams.h
Normal file
19
ngx_rtmp_streams.h
Normal file
|
@ -0,0 +1,19 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_STREAMS_H_INCLUDED_
|
||||
#define _NGX_RTMP_STREAMS_H_INCLUDED_
|
||||
|
||||
|
||||
#define NGX_RTMP_MSID 1
|
||||
|
||||
#define NGX_RTMP_CSID_AMF_INI 3
|
||||
#define NGX_RTMP_CSID_AMF 5
|
||||
#define NGX_RTMP_CSID_AUDIO 6
|
||||
#define NGX_RTMP_CSID_VIDEO 7
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_STREAMS_H_INCLUDED_ */
|
15
ngx_rtmp_version.h
Normal file
15
ngx_rtmp_version.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
|
||||
/*
|
||||
* Copyright (C) Roman Arutyunyan
|
||||
*/
|
||||
|
||||
|
||||
#ifndef _NGX_RTMP_VERSION_H_INCLUDED_
|
||||
#define _NGX_RTMP_VERSION_H_INCLUDED_
|
||||
|
||||
|
||||
#define nginx_rtmp_version 1002002
|
||||
#define NGINX_RTMP_VERSION "1.2.2-r1"
|
||||
|
||||
|
||||
#endif /* _NGX_RTMP_VERSION_H_INCLUDED_ */
|
371
stat.xsl
Normal file
371
stat.xsl
Normal file
|
@ -0,0 +1,371 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
|
||||
|
||||
<!--
|
||||
Copyright (C) Roman Arutyunyan
|
||||
-->
|
||||
|
||||
|
||||
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
||||
|
||||
|
||||
<xsl:template match="/">
|
||||
<html>
|
||||
<head>
|
||||
<title>RTMP statistics</title>
|
||||
</head>
|
||||
<body>
|
||||
<xsl:apply-templates select="rtmp"/>
|
||||
<hr/>
|
||||
Generated by <a href='https://github.com/arut/nginx-rtmp-module'>
|
||||
nginx-rtmp-module</a> <xsl:value-of select="/rtmp/nginx_rtmp_version"/>,
|
||||
<a href="http://nginx.org">nginx</a> <xsl:value-of select="/rtmp/nginx_version"/>,
|
||||
pid <xsl:value-of select="/rtmp/pid"/>,
|
||||
built <xsl:value-of select="/rtmp/built"/> <xsl:value-of select="/rtmp/compiler"/>
|
||||
</body>
|
||||
</html>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="rtmp">
|
||||
<table cellspacing="1" cellpadding="5">
|
||||
<tr bgcolor="#999999">
|
||||
<th>RTMP</th>
|
||||
<th>#clients</th>
|
||||
<th colspan="4">Video</th>
|
||||
<th colspan="4">Audio</th>
|
||||
<th>In bytes</th>
|
||||
<th>Out bytes</th>
|
||||
<th>In bits/s</th>
|
||||
<th>Out bits/s</th>
|
||||
<th>State</th>
|
||||
<th>Record</th>
|
||||
<th>Time</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">Accepted: <xsl:value-of select="naccepted"/></td>
|
||||
<th bgcolor="#999999">codec</th>
|
||||
<th bgcolor="#999999">bits/s</th>
|
||||
<th bgcolor="#999999">size</th>
|
||||
<th bgcolor="#999999">fps</th>
|
||||
<th bgcolor="#999999">codec</th>
|
||||
<th bgcolor="#999999">bits/s</th>
|
||||
<th bgcolor="#999999">freq</th>
|
||||
<th bgcolor="#999999">chan</th>
|
||||
<td>
|
||||
<xsl:call-template name="showsize">
|
||||
<xsl:with-param name="size" select="bytes_in"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="showsize">
|
||||
<xsl:with-param name="size" select="bytes_out"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="showsize">
|
||||
<xsl:with-param name="size" select="bw_in"/>
|
||||
<xsl:with-param name="bits" select="1"/>
|
||||
<xsl:with-param name="persec" select="1"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="showsize">
|
||||
<xsl:with-param name="size" select="bw_out"/>
|
||||
<xsl:with-param name="bits" select="1"/>
|
||||
<xsl:with-param name="persec" select="1"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td/>
|
||||
<td/>
|
||||
<td>
|
||||
<xsl:call-template name="showtime">
|
||||
<xsl:with-param name="time" select="/rtmp/uptime * 1000"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
</tr>
|
||||
<xsl:apply-templates select="server"/>
|
||||
</table>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="server">
|
||||
<xsl:apply-templates select="application"/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="application">
|
||||
<tr bgcolor="#999999">
|
||||
<td>
|
||||
<b><xsl:value-of select="name"/></b>
|
||||
</td>
|
||||
</tr>
|
||||
<xsl:apply-templates select="live"/>
|
||||
<xsl:apply-templates select="play"/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="live">
|
||||
<tr bgcolor="#aaaaaa">
|
||||
<td>
|
||||
<i>live streams</i>
|
||||
</td>
|
||||
<td align="middle">
|
||||
<xsl:value-of select="nclients"/>
|
||||
</td>
|
||||
</tr>
|
||||
<xsl:apply-templates select="stream"/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="play">
|
||||
<tr bgcolor="#aaaaaa">
|
||||
<td>
|
||||
<i>vod streams</i>
|
||||
</td>
|
||||
<td align="middle">
|
||||
<xsl:value-of select="nclients"/>
|
||||
</td>
|
||||
</tr>
|
||||
<xsl:apply-templates select="stream"/>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="stream">
|
||||
<tr valign="top">
|
||||
<xsl:attribute name="bgcolor">
|
||||
<xsl:choose>
|
||||
<xsl:when test="active">#cccccc</xsl:when>
|
||||
<xsl:otherwise>#dddddd</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:attribute>
|
||||
<td>
|
||||
<a href="">
|
||||
<xsl:attribute name="onclick">
|
||||
var d=document.getElementById('<xsl:value-of select="../../name"/>-<xsl:value-of select="name"/>');
|
||||
d.style.display=d.style.display=='none'?'':'none';
|
||||
return false
|
||||
</xsl:attribute>
|
||||
<xsl:value-of select="name"/>
|
||||
<xsl:if test="string-length(name) = 0">
|
||||
[EMPTY]
|
||||
</xsl:if>
|
||||
</a>
|
||||
</td>
|
||||
<td align="middle"> <xsl:value-of select="nclients"/> </td>
|
||||
<td>
|
||||
<xsl:value-of select="meta/video/codec"/> <xsl:value-of select="meta/video/profile"/> <xsl:value-of select="meta/video/level"/>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="showsize">
|
||||
<xsl:with-param name="size" select="bw_video"/>
|
||||
<xsl:with-param name="bits" select="1"/>
|
||||
<xsl:with-param name="persec" select="1"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:apply-templates select="meta/video/width"/>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:value-of select="meta/video/frame_rate"/>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:value-of select="meta/audio/codec"/> <xsl:value-of select="meta/audio/profile"/>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="showsize">
|
||||
<xsl:with-param name="size" select="bw_audio"/>
|
||||
<xsl:with-param name="bits" select="1"/>
|
||||
<xsl:with-param name="persec" select="1"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:apply-templates select="meta/audio/sample_rate"/>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:value-of select="meta/audio/channels"/>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="showsize">
|
||||
<xsl:with-param name="size" select="bytes_in"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="showsize">
|
||||
<xsl:with-param name="size" select="bytes_out"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="showsize">
|
||||
<xsl:with-param name="size" select="bw_in"/>
|
||||
<xsl:with-param name="bits" select="1"/>
|
||||
<xsl:with-param name="persec" select="1"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td>
|
||||
<xsl:call-template name="showsize">
|
||||
<xsl:with-param name="size" select="bw_out"/>
|
||||
<xsl:with-param name="bits" select="1"/>
|
||||
<xsl:with-param name="persec" select="1"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
<td><xsl:call-template name="streamstate"/></td>
|
||||
<td><xsl:call-template name="recordstate"/></td>
|
||||
<td>
|
||||
<xsl:call-template name="showtime">
|
||||
<xsl:with-param name="time" select="time"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="display:none">
|
||||
<xsl:attribute name="id">
|
||||
<xsl:value-of select="../../name"/>-<xsl:value-of select="name"/>
|
||||
</xsl:attribute>
|
||||
<td colspan="16" ngcolor="#eeeeee">
|
||||
<table cellspacing="1" cellpadding="5">
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>State</th>
|
||||
<th>Recording</th>
|
||||
<th>Address</th>
|
||||
<th>Flash version</th>
|
||||
<th>Page URL</th>
|
||||
<th>SWF URL</th>
|
||||
<th>Dropped</th>
|
||||
<th>Timestamp</th>
|
||||
<th>A-V</th>
|
||||
<th>Time</th>
|
||||
</tr>
|
||||
<xsl:apply-templates select="client"/>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="showtime">
|
||||
<xsl:param name="time"/>
|
||||
|
||||
<xsl:if test="$time > 0">
|
||||
<xsl:variable name="sec">
|
||||
<xsl:value-of select="floor($time div 1000)"/>
|
||||
</xsl:variable>
|
||||
|
||||
<xsl:if test="$sec >= 86400">
|
||||
<xsl:value-of select="floor($sec div 86400)"/>d
|
||||
</xsl:if>
|
||||
|
||||
<xsl:if test="$sec >= 3600">
|
||||
<xsl:value-of select="(floor($sec div 3600)) mod 24"/>h
|
||||
</xsl:if>
|
||||
|
||||
<xsl:if test="$sec >= 60">
|
||||
<xsl:value-of select="(floor($sec div 60)) mod 60"/>m
|
||||
</xsl:if>
|
||||
|
||||
<xsl:value-of select="$sec mod 60"/>s
|
||||
</xsl:if>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="showsize">
|
||||
<xsl:param name="size"/>
|
||||
<xsl:param name="bits" select="0" />
|
||||
<xsl:param name="persec" select="0" />
|
||||
<xsl:variable name="sizen">
|
||||
<xsl:value-of select="floor($size div 1024)"/>
|
||||
</xsl:variable>
|
||||
<xsl:choose>
|
||||
<xsl:when test="$sizen >= 1073741824">
|
||||
<xsl:value-of select="format-number($sizen div 1073741824,'#.###')"/> T</xsl:when>
|
||||
|
||||
<xsl:when test="$sizen >= 1048576">
|
||||
<xsl:value-of select="format-number($sizen div 1048576,'#.###')"/> G</xsl:when>
|
||||
|
||||
<xsl:when test="$sizen >= 1024">
|
||||
<xsl:value-of select="format-number($sizen div 1024,'#.##')"/> M</xsl:when>
|
||||
<xsl:when test="$sizen >= 0">
|
||||
<xsl:value-of select="$sizen"/> K</xsl:when>
|
||||
</xsl:choose>
|
||||
<xsl:if test="string-length($size) > 0">
|
||||
<xsl:choose>
|
||||
<xsl:when test="$bits = 1">b</xsl:when>
|
||||
<xsl:otherwise>B</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
<xsl:if test="$persec = 1">/s</xsl:if>
|
||||
</xsl:if>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="streamstate">
|
||||
<xsl:choose>
|
||||
<xsl:when test="active">active</xsl:when>
|
||||
<xsl:otherwise>idle</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:template>
|
||||
|
||||
|
||||
<xsl:template name="clientstate">
|
||||
<xsl:choose>
|
||||
<xsl:when test="publishing">publishing</xsl:when>
|
||||
<xsl:otherwise>playing</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="recordstate">
|
||||
<xsl:choose>
|
||||
<xsl:when test="recording">yes</xsl:when>
|
||||
<xsl:otherwise>no</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:template>
|
||||
|
||||
|
||||
<xsl:template match="client">
|
||||
<tr>
|
||||
<xsl:attribute name="bgcolor">
|
||||
<xsl:choose>
|
||||
<xsl:when test="publishing">#cccccc</xsl:when>
|
||||
<xsl:otherwise>#eeeeee</xsl:otherwise>
|
||||
</xsl:choose>
|
||||
</xsl:attribute>
|
||||
<td><xsl:value-of select="id"/></td>
|
||||
<td><xsl:call-template name="clientstate"/></td>
|
||||
<td><xsl:call-template name="recordstate"/></td>
|
||||
<td>
|
||||
<a target="_blank">
|
||||
<xsl:attribute name="href">
|
||||
http://apps.db.ripe.net/search/query.html?searchtext=<xsl:value-of select="address"/>
|
||||
</xsl:attribute>
|
||||
<xsl:attribute name="title">whois</xsl:attribute>
|
||||
<xsl:value-of select="address"/>:<xsl:value-of select="port"/>
|
||||
</a>
|
||||
</td>
|
||||
<td><xsl:value-of select="flashver"/></td>
|
||||
<td>
|
||||
<a target="_blank">
|
||||
<xsl:attribute name="href">
|
||||
<xsl:value-of select="pageurl"/>
|
||||
</xsl:attribute>
|
||||
<xsl:value-of select="pageurl"/>
|
||||
</a>
|
||||
</td>
|
||||
<td><xsl:value-of select="swfurl"/></td>
|
||||
<td><xsl:value-of select="dropped"/></td>
|
||||
<td><xsl:value-of select="timestamp"/></td>
|
||||
<td><xsl:value-of select="avsync"/></td>
|
||||
<td>
|
||||
<xsl:call-template name="showtime">
|
||||
<xsl:with-param name="time" select="time"/>
|
||||
</xsl:call-template>
|
||||
</td>
|
||||
</tr>
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="publishing">
|
||||
publishing
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="active">
|
||||
active
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="recording">
|
||||
recording
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template match="width">
|
||||
<xsl:value-of select="."/>x<xsl:value-of select="../height"/>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
11
test/README.md
Normal file
11
test/README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# RTMP tests
|
||||
|
||||
nginx.conf is sample config for testing nginx-rtmp.
|
||||
Please update paths in it before using.
|
||||
|
||||
RTMP port: 1935, HTTP port: 8080
|
||||
|
||||
* http://localhost:8080/ - play myapp/mystream with JWPlayer
|
||||
* http://localhost:8080/record.html - capture myapp/mystream from webcam with old JWPlayer
|
||||
* http://localhost:8080/rtmp-publisher/player.html - play myapp/mystream with the test flash applet
|
||||
* http://localhost:8080/rtmp-publisher/publisher.html - capture myapp/mystream with the test flash applet
|
|
@ -1 +1 @@
|
|||
rtmpdump -v -r "rtmp://localhost/helo/pd"
|
||||
rtmpdump -v -r "rtmp://localhost/myapp/mystream"
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
ffmpeg -loglevel verbose -re -i /mnt/home/rarutyunyan/Videos/anything_else-solaris.giga.su.avi -f flv rtmp://localhost/helo
|
||||
#ffmpeg -loglevel verbose -re -i /mnt/home/rarutyunyan/Videos/the_changeup-solaris.giga.su.avi -an -f flv rtmp://localhost/helo
|
||||
ffmpeg -loglevel verbose -re -i ~/movie.avi -f flv rtmp://localhost/myapp/mystream
|
||||
|
|
|
@ -1,42 +1,66 @@
|
|||
|
||||
#user nobody;
|
||||
worker_processes 1;
|
||||
|
||||
error_log logs/error.log debug;
|
||||
#error_log logs/error.log notice;
|
||||
#error_log logs/error.log info;
|
||||
|
||||
#pid logs/nginx.pid;
|
||||
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
|
||||
rtmp {
|
||||
|
||||
server {
|
||||
listen 1935;
|
||||
|
||||
listen 1935;
|
||||
application myapp {
|
||||
live on;
|
||||
|
||||
wait_key_frame off;
|
||||
#record keyframes;
|
||||
#record_path /tmp;
|
||||
#record_max_size 128K;
|
||||
#record_interval 30s;
|
||||
#record_suffix .this.is.flv;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
http {
|
||||
|
||||
server {
|
||||
|
||||
# testing framework
|
||||
# based on flowplayer (http://flowplayer.org)
|
||||
|
||||
listen 8080;
|
||||
|
||||
location / {
|
||||
root /home/rarutyunyan/nginx-rtmp-module/test/www;
|
||||
#on_publish http://localhost:8080/publish;
|
||||
#on_play http://localhost:8080/play;
|
||||
#on_record_done http://localhost:8080/record_done;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
http {
|
||||
server {
|
||||
listen 8080;
|
||||
|
||||
location /stat {
|
||||
rtmp_stat all;
|
||||
rtmp_stat_stylesheet stat.xsl;
|
||||
}
|
||||
|
||||
location /stat.xsl {
|
||||
root /path/to/nginx-rtmp-module/;
|
||||
}
|
||||
|
||||
location /control {
|
||||
rtmp_control all;
|
||||
}
|
||||
|
||||
#location /publish {
|
||||
# return 201;
|
||||
#}
|
||||
|
||||
#location /play {
|
||||
# return 202;
|
||||
#}
|
||||
|
||||
#location /record_done {
|
||||
# return 203;
|
||||
#}
|
||||
|
||||
location /rtmp-publisher {
|
||||
root /path/to/nginx-rtmp-module/test;
|
||||
}
|
||||
|
||||
location / {
|
||||
root /path/to/nginx-rtmp-module/test/www;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
ffplay -loglevel verbose "rtmp://192.168.0.100/helo/pd"
|
||||
ffplay -loglevel verbose "rtmp://localhost/myapp/mystream"
|
||||
|
|
15
test/rtmp-publisher/README.md
Normal file
15
test/rtmp-publisher/README.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
# RTMP Publisher
|
||||
|
||||
Simple RTMP publisher.
|
||||
|
||||
Edit the following flashvars in publisher.html & player.html to suite your needs.
|
||||
|
||||
streamer: RTMP endpoint
|
||||
file: live stream name
|
||||
|
||||
## Compile
|
||||
|
||||
Install flex sdk http://www.adobe.com/devnet/flex/flex-sdk-download.html
|
||||
|
||||
mxmlc RtmpPublisher.mxml
|
||||
mxmlc RtmpPlayer.mxml
|
69
test/rtmp-publisher/RtmpPlayer.mxml
Normal file
69
test/rtmp-publisher/RtmpPlayer.mxml
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
|
||||
xmlns:s="library://ns.adobe.com/flex/spark"
|
||||
xmlns:mx="library://ns.adobe.com/flex/mx"
|
||||
minWidth="500" minHeight="350" creationComplete="init()">
|
||||
|
||||
<fx:Script>
|
||||
<![CDATA[
|
||||
import mx.controls.Alert;
|
||||
import mx.core.FlexGlobals;
|
||||
import flash.display.StageDisplayState;
|
||||
import mx.managers.SystemManager;
|
||||
|
||||
private var streamer:String;
|
||||
private var file:String;
|
||||
|
||||
private function playButtonListener(event:MouseEvent):void {
|
||||
if(playButton.label == 'Play') {
|
||||
playButton.label = 'Stop';
|
||||
videoDisplay.source = streamer + "/" + file;
|
||||
videoDisplay.play();
|
||||
} else {
|
||||
playButton.label = 'Play';
|
||||
videoDisplay.source = "";
|
||||
//videoDisplay.close();
|
||||
}
|
||||
}
|
||||
|
||||
private function fullscreenButtonListener(event:MouseEvent):void {
|
||||
try {
|
||||
switch (systemManager.stage.displayState) {
|
||||
case StageDisplayState.FULL_SCREEN:
|
||||
stage.displayState = StageDisplayState.NORMAL;
|
||||
break;
|
||||
default:
|
||||
stage.displayState = StageDisplayState.FULL_SCREEN;
|
||||
break;
|
||||
}
|
||||
} catch (err:SecurityError) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
private function init():void {
|
||||
videoDisplay.mx_internal::videoPlayer.bufferTime = 1;
|
||||
|
||||
streamer = FlexGlobals.topLevelApplication.parameters.streamer;
|
||||
if(streamer == null) {
|
||||
Alert.show('Missing flashvars: streamer');
|
||||
return;
|
||||
}
|
||||
|
||||
file = FlexGlobals.topLevelApplication.parameters.file;
|
||||
if(file == null) {
|
||||
Alert.show('Missing flashvars: file');
|
||||
return;
|
||||
}
|
||||
|
||||
playButton.addEventListener(MouseEvent.CLICK, playButtonListener);
|
||||
fullscreenButton.addEventListener(MouseEvent.CLICK, fullscreenButtonListener);
|
||||
}
|
||||
]]>
|
||||
</fx:Script>
|
||||
<s:BorderContainer x="0" y="0" width="100%" height="100%">
|
||||
<s:VideoDisplay width="100%" height="100%" id="videoDisplay"></s:VideoDisplay>
|
||||
<s:Button label="Play" id="playButton" horizontalCenter="0" bottom="10"></s:Button>
|
||||
<s:Button label="[ ]" id="fullscreenButton" right="10" bottom="10"></s:Button>
|
||||
</s:BorderContainer>
|
||||
</s:Application>
|
BIN
test/rtmp-publisher/RtmpPlayer.swf
Normal file
BIN
test/rtmp-publisher/RtmpPlayer.swf
Normal file
Binary file not shown.
101
test/rtmp-publisher/RtmpPlayerLight.mxml
Normal file
101
test/rtmp-publisher/RtmpPlayerLight.mxml
Normal file
|
@ -0,0 +1,101 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
|
||||
xmlns:s="library://ns.adobe.com/flex/spark"
|
||||
xmlns:mx="library://ns.adobe.com/flex/mx"
|
||||
minWidth="500" minHeight="350" creationComplete="init()">
|
||||
|
||||
<fx:Script>
|
||||
<![CDATA[
|
||||
import mx.controls.Alert;
|
||||
import mx.core.FlexGlobals;
|
||||
import flash.display.StageDisplayState;
|
||||
import mx.managers.SystemManager;
|
||||
import org.osmf.events.MediaPlayerStateChangeEvent;
|
||||
import org.osmf.events.TimeEvent;
|
||||
import org.osmf.media.MediaPlayerState;
|
||||
|
||||
private var streamer:String;
|
||||
private var file:String;
|
||||
private var videoEventsDisabled:Boolean;
|
||||
private var previousVideoTime:Number;
|
||||
|
||||
private function fullscreenListener(event:MouseEvent):void {
|
||||
try {
|
||||
switch (stage.displayState) {
|
||||
case StageDisplayState.FULL_SCREEN:
|
||||
stage.displayState = StageDisplayState.NORMAL;
|
||||
break;
|
||||
default:
|
||||
stage.displayState = StageDisplayState.FULL_SCREEN;
|
||||
break;
|
||||
}
|
||||
} catch (err:SecurityError) {
|
||||
Alert.show('Fullsceen error: ' + err);
|
||||
}
|
||||
}
|
||||
|
||||
private function resetVideo():void {
|
||||
videoEventsDisabled = true;
|
||||
|
||||
try {
|
||||
videoDisplay.source = "";
|
||||
} catch (any:*) {}
|
||||
|
||||
setTimeout(resetVideoSource, 5000);
|
||||
}
|
||||
|
||||
private function resetVideoSource():void {
|
||||
videoEventsDisabled = false;
|
||||
previousVideoTime = NaN;
|
||||
videoDisplay.source = streamer + "/" + file;
|
||||
}
|
||||
|
||||
protected function stateChangeListener(event:MediaPlayerStateChangeEvent):void {
|
||||
if (videoEventsDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.state == MediaPlayerState.PLAYBACK_ERROR) {
|
||||
resetVideo();
|
||||
}
|
||||
}
|
||||
|
||||
protected function timeChangeListener(event:TimeEvent):void {
|
||||
if (videoEventsDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNaN(event.time) && !isNaN(previousVideoTime)) {
|
||||
resetVideo();
|
||||
} else {
|
||||
previousVideoTime = event.time;
|
||||
}
|
||||
}
|
||||
|
||||
private function init():void {
|
||||
videoDisplay.mx_internal::videoPlayer.bufferTime = 1;
|
||||
|
||||
streamer = FlexGlobals.topLevelApplication.parameters.streamer;
|
||||
if(streamer == null) {
|
||||
Alert.show('Missing flashvars: streamer');
|
||||
return;
|
||||
}
|
||||
|
||||
file = FlexGlobals.topLevelApplication.parameters.file;
|
||||
if(file == null) {
|
||||
Alert.show('Missing flashvars: file');
|
||||
return;
|
||||
}
|
||||
|
||||
videoDisplay.addEventListener(MouseEvent.DOUBLE_CLICK, fullscreenListener);
|
||||
videoDisplay.addEventListener("MediaPlayerStateChange", stateChangeListener);
|
||||
videoDisplay.addEventListener("currentTimeChange", timeChangeListener);
|
||||
|
||||
resetVideoSource();
|
||||
}
|
||||
]]>
|
||||
</fx:Script>
|
||||
<s:BorderContainer x="0" y="0" width="100%" height="100%">
|
||||
<s:VideoDisplay doubleClickEnabled="true" width="100%" height="100%" id="videoDisplay"></s:VideoDisplay>
|
||||
</s:BorderContainer>
|
||||
</s:Application>
|
BIN
test/rtmp-publisher/RtmpPlayerLight.swf
Normal file
BIN
test/rtmp-publisher/RtmpPlayerLight.swf
Normal file
Binary file not shown.
86
test/rtmp-publisher/RtmpPublisher.mxml
Normal file
86
test/rtmp-publisher/RtmpPublisher.mxml
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
|
||||
xmlns:s="library://ns.adobe.com/flex/spark"
|
||||
xmlns:mx="library://ns.adobe.com/flex/mx"
|
||||
minWidth="500" minHeight="350" creationComplete="init()">
|
||||
|
||||
<fx:Script>
|
||||
<![CDATA[
|
||||
import mx.controls.Alert;
|
||||
import mx.core.FlexGlobals;
|
||||
import mx.events.FlexEvent;
|
||||
import spark.skins.spark.PanelSkin;
|
||||
|
||||
private var streamer:String;
|
||||
private var file:String;
|
||||
private var camera:Camera;
|
||||
private var microphone:Microphone;
|
||||
private var connection:NetConnection;
|
||||
private var stream:NetStream;
|
||||
private var h264Settings:H264VideoStreamSettings;
|
||||
|
||||
private function publishButtonListener(event:MouseEvent):void {
|
||||
if(publishButton.label == 'Publish') {
|
||||
publishButton.label = 'Stop';
|
||||
connection = new NetConnection();
|
||||
connection.connect(streamer);
|
||||
connection.addEventListener(NetStatusEvent.NET_STATUS, netStatusHander);
|
||||
} else {
|
||||
publishButton.label = 'Publish';
|
||||
stream.close();
|
||||
connection.close();
|
||||
stream = null;
|
||||
connection = null;
|
||||
}
|
||||
}
|
||||
|
||||
private function netStatusHander(event:NetStatusEvent):void {
|
||||
switch(event.info.code) {
|
||||
case 'NetConnection.Connect.Success':
|
||||
stream = new NetStream(connection);
|
||||
stream.attachCamera(camera);
|
||||
stream.attachAudio(microphone);
|
||||
h264Settings = new H264VideoStreamSettings();
|
||||
h264Settings.setProfileLevel(H264Profile.BASELINE, H264Level.LEVEL_3_1);
|
||||
stream.videoStreamSettings = h264Settings;
|
||||
stream.publish(file, 'live');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function init():void {
|
||||
streamer = FlexGlobals.topLevelApplication.parameters.streamer;
|
||||
if(streamer == null) {
|
||||
Alert.show('Missing flashvars: streamer');
|
||||
return;
|
||||
}
|
||||
|
||||
file = FlexGlobals.topLevelApplication.parameters.file;
|
||||
if(file == null) {
|
||||
Alert.show('Missing flashvars: file');
|
||||
return;
|
||||
}
|
||||
|
||||
publishButton.addEventListener(MouseEvent.CLICK, publishButtonListener);
|
||||
|
||||
camera = Camera.getCamera();
|
||||
camera.setMode(640, 480, 30);
|
||||
camera.setQuality(131072, 70);
|
||||
|
||||
//videoDisplay.attachCamera(camera);
|
||||
var localCam:Video = new Video(640,480);
|
||||
localCam.attachCamera(camera);
|
||||
videoDisplay.addChild(localCam);
|
||||
|
||||
microphone = Microphone.getMicrophone();
|
||||
microphone.setSilenceLevel(0);
|
||||
microphone.codec = "Speex";
|
||||
microphone.encodeQuality = 6;
|
||||
}
|
||||
]]>
|
||||
</fx:Script>
|
||||
<s:BorderContainer x="0" y="0" width="100%" height="100%">
|
||||
<s:VideoDisplay width="100%" height="100%" id="videoDisplay"></s:VideoDisplay>
|
||||
<s:Button label="Publish" id="publishButton" horizontalCenter="0" bottom="10"></s:Button>
|
||||
</s:BorderContainer>
|
||||
</s:Application>
|
BIN
test/rtmp-publisher/RtmpPublisher.swf
Normal file
BIN
test/rtmp-publisher/RtmpPublisher.swf
Normal file
Binary file not shown.
22
test/rtmp-publisher/player.html
Normal file
22
test/rtmp-publisher/player.html
Normal file
|
@ -0,0 +1,22 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>RTMP Player</title>
|
||||
<script type="text/javascript" src="swfobject.js"></script>
|
||||
<script type="text/javascript">
|
||||
var flashVars = {
|
||||
streamer: 'rtmp://localhost/myapp',
|
||||
file:'mystream'
|
||||
};
|
||||
var params = {};
|
||||
params.allowfullscreen = "true";
|
||||
var attributes = {};
|
||||
swfobject.embedSWF("RtmpPlayer.swf", "rtmp-publisher", "640", "480", "9.0.0", null, flashVars, params, attributes);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="rtmp-publisher">
|
||||
<p>Flash not installed</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
19
test/rtmp-publisher/publisher.html
Normal file
19
test/rtmp-publisher/publisher.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>RTMP Publisher</title>
|
||||
<script type="text/javascript" src="swfobject.js"></script>
|
||||
<script type="text/javascript">
|
||||
var flashVars = {
|
||||
streamer: 'rtmp://localhost/myapp',
|
||||
file:'mystream'
|
||||
};
|
||||
swfobject.embedSWF("RtmpPublisher.swf", "rtmp-publisher", "640", "480", "9.0.0", null, flashVars);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="rtmp-publisher">
|
||||
<p>Flash not installed</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
4
test/rtmp-publisher/swfobject.js
Normal file
4
test/rtmp-publisher/swfobject.js
Normal file
File diff suppressed because one or more lines are too long
BIN
test/www/bg.jpg
Normal file
BIN
test/www/bg.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -5,16 +5,15 @@
|
|||
<div id="container">Loading the player ...</div>
|
||||
<script type="text/javascript">
|
||||
jwplayer("container").setup({
|
||||
image: "http://192.168.0.100/showme.jpg",
|
||||
modes: [
|
||||
{ type: "flash",
|
||||
src: "/jwplayer/player.swf",
|
||||
config: {
|
||||
file: "video.mp4",
|
||||
streamer: "rtmp://192.168.0.100/helo/p",
|
||||
provider: "rtmp"
|
||||
}
|
||||
sources: [
|
||||
{
|
||||
file: "rtmp://localhost/myapp?carg=1/mystream?sarg=2"
|
||||
}
|
||||
]
|
||||
],
|
||||
image: "bg.jpg",
|
||||
autostart: false,
|
||||
width: 640,
|
||||
height: 480,
|
||||
primary: "flash"
|
||||
});
|
||||
</script>
|
||||
|
|
BIN
test/www/jwplayer/jwplayer.flash.swf
Normal file
BIN
test/www/jwplayer/jwplayer.flash.swf
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue