| @@ -14,7 +14,10 @@ enum { · 13 unmodified lines |
| 14 | Port = 1935, | 14 | Port = 1935, |
| 15 | CSsz = 1536, | 15 | CSsz = 1536, |
| 16 | ChunkDefault = 128, | 16 | ChunkDefault = 128, |
| 17 | ChunkDesired = 4096, | 17 | ChunkDesired = 65536, |
| | 18 | |
| | 19 | FlKey = 1<<0, |
| | 20 | FlHdr = 1<<1, |
| 18 | | 21 | |
| 19 | Type0 = 0, | 22 | Type0 = 0, |
| 20 | Type1, | 23 | Type1, |
| @@ -23,6 +26,7 @@ enum { · 2 unmodified lines |
| 23 | | 26 | |
| 24 | CSUserCtl = 2, | 27 | CSUserCtl = 2, |
| 25 | CSCtl = 3, | 28 | CSCtl = 3, |
| | 29 | CSData = 4, |
| 26 | | 30 | |
| 27 | CbCommand = 0, | 31 | CbCommand = 0, |
| 28 | CbTransID, | 32 | CbTransID, |
| @@ -76,6 +80,7 @@ struct Command { · 47 unmodified lines |
| 76 | void (*cb)(RTMP *r, int ok, A₀ *a[NumCb], void *aux); | 80 | void (*cb)(RTMP *r, int ok, A₀ *a[NumCb], void *aux); |
| 77 | void *aux; | 81 | void *aux; |
| 78 | int tid; | 82 | int tid; |
| | 83 | int cs; |
| 79 | | 84 | |
| 80 | Command *prev, *next; | 85 | Command *prev, *next; |
| 81 | }; | 86 | }; |
| @@ -117,6 +122,13 @@ struct RTMP { · 35 unmodified lines |
| 117 | int tid; | 122 | int tid; |
| 118 | Command *w; | 123 | Command *w; |
| 119 | }cmds; | 124 | }cmds; |
| | 125 | struct { |
| | 126 | int hsent; |
| | 127 | int spssz; |
| | 128 | int ppssz; |
| | 129 | u8int sps[128]; |
| | 130 | u8int pps[128]; |
| | 131 | }v; |
| 120 | u8int biobuf[Biobufsz]; | 132 | u8int biobuf[Biobufsz]; |
| 121 | }; | 133 | }; |
| 122 | | 134 | |
| @@ -142,6 +154,11 @@ struct RTMP { · 19 unmodified lines |
| 142 | r->o.msg.cmd.cb = cb_; \ | 154 | r->o.msg.cmd.cb = cb_; \ |
| 143 | }while(0) | 155 | }while(0) |
| 144 | | 156 | |
| | 157 | #define putcall(name) do { \ |
| | 158 | putstr(name); \ |
| | 159 | putnum(r->o.msg.cmd.tid); \ |
| | 160 | }while(0) |
| | 161 | |
| 145 | static int szs[] = { | 162 | static int szs[] = { |
| 146 | [Type3] = 0, | 163 | [Type3] = 0, |
| 147 | [Type2] = 3, | 164 | [Type2] = 3, |
| @@ -204,6 +221,7 @@ newmsg(RTMP *r, int type, int fmt, int cs) · 56 unmodified lines |
| 204 | r->o.msg.cmd.tid = ++r->cmds.tid; | 221 | r->o.msg.cmd.tid = ++r->cmds.tid; |
| 205 | else | 222 | else |
| 206 | r->o.msg.cmd.tid = 0; | 223 | r->o.msg.cmd.tid = 0; |
| | 224 | r->o.msg.cmd.cs = cs; |
| 207 | } | 225 | } |
| 208 | | 226 | |
| 209 | static void | 227 | static void |
| @@ -328,12 +346,12 @@ rtmpsend(RTMP *r) · 118 unmodified lines |
| 328 | hsz = szs[m->fmt]; | 346 | hsz = szs[m->fmt]; |
| 329 | e = h + hsz; | 347 | e = h + hsz; |
| 330 | if(hsz >= szs[Type2]){ | 348 | if(hsz >= szs[Type2]){ |
| 331 | h = a₀i24(h, e, 0); /* FIXME put actual timestamps? */ | 349 | h = a₀i24(h, e, m->ts); |
| 332 | if(hsz >= szs[Type1]){ | 350 | if(hsz >= szs[Type1]){ |
| 333 | h = a₀i24(h, e, m->sz); | 351 | h = a₀i24(h, e, m->sz); |
| 334 | h = a₀byte(h, e, m->type); | 352 | h = a₀byte(h, e, m->type); |
| 335 | if(hsz >= szs[Type0]) | 353 | if(hsz >= szs[Type0]) |
| 336 | h = a₀i32(h, e, m->sid); | 354 | h = a₀i32le(h, e, m->sid); |
| 337 | } | 355 | } |
| 338 | } | 356 | } |
| 339 | assert(h != nil); | 357 | assert(h != nil); |
| @@ -487,7 +505,14 @@ loop(void *aux) · 147 unmodified lines |
| 487 | werrstr("transaction ID is not a number"); | 505 | werrstr("transaction ID is not a number"); |
| 488 | goto err; | 506 | goto err; |
| 489 | } | 507 | } |
| 490 | for(c = r->cmds.w; c != nil && c->tid != a[n]->num; c = c->next); | 508 | for(c = r->cmds.w; c != nil; c = c->next){ |
| | 509 | /* transaction id match */ |
| | 510 | if(c->tid == a[n]->num) |
| | 511 | break; |
| | 512 | /* no transaction id, but the chunk stream matches */ |
| | 513 | if(a[n]->num == 0 && c->cs == m->cs) |
| | 514 | break; |
| | 515 | } |
| 491 | if(c == nil) | 516 | if(c == nil) |
| 492 | fprint(2, "response to non-existent transaction %d", (int)a[n]->num); | 517 | fprint(2, "response to non-existent transaction %d", (int)a[n]->num); |
| 493 | break; | 518 | break; |
| @@ -648,8 +673,7 @@ rtmpstream(RTMP *r, ulong *sid) · 154 unmodified lines |
| 648 | qlock(r); | 673 | qlock(r); |
| 649 | | 674 | |
| 650 | newmsg(r, AMF0Command, Type0, CSCtl); | 675 | newmsg(r, AMF0Command, Type0, CSCtl); |
| 651 | putstr("createStream"); | 676 | putcall("createStream"); |
| 652 | putnum(r->o.msg.cmd.tid); | | |
| 653 | putnull(); | 677 | putnull(); |
| 654 | | 678 | |
| 655 | r->o.msg.cmd.cb = streamcreated; | 679 | r->o.msg.cmd.cb = streamcreated; |
| @@ -698,10 +722,8 @@ rtmppublish(RTMP *r, ulong sid, int type, char *name) · 42 unmodified lines |
| 698 | | 722 | |
| 699 | qlock(r); | 723 | qlock(r); |
| 700 | | 724 | |
| 701 | newmsg(r, AMF0Command, Type0, CSCtl); | 725 | newmsg(r, AMF0Command, Type0, CSData); |
| 702 | notransaction(r); | 726 | putcall("publish"); |
| 703 | putstr("publish"); | | |
| 704 | putnum(0); | | |
| 705 | putnull(); | 727 | putnull(); |
| 706 | putstr(name); | 728 | putstr(name); |
| 707 | putstr(pubtype2s[type]); | 729 | putstr(pubtype2s[type]); |
| @@ -735,12 +757,13 @@ rtmpmeta(RTMP *r, ulong sid, int vcodec, int w, int h, int acodec) · 27 unmodified lines |
| 735 | | 757 | |
| 736 | qlock(r); | 758 | qlock(r); |
| 737 | | 759 | |
| 738 | newmsg(r, AMF0Metadata, Type0, CSCtl); | 760 | newmsg(r, AMF0Metadata, Type0, CSData); |
| 739 | r->o.msg.sid = sid; | 761 | |
| | 762 | putstr("@setDataFrame"); |
| 740 | | 763 | |
| 741 | putstr("onMetaData"); | 764 | putstr("onMetaData"); |
| 742 | putnum(0); | 765 | putarr(); |
| 743 | putobj(); | 766 | puti32(1 + (vcodec < 0 ? 0 : 3) + (acodec < 0 ? 0 : 1)); |
| 744 | putkvnum("duration", 0.0); | 767 | putkvnum("duration", 0.0); |
| 745 | if(vcodec >= 0){ | 768 | if(vcodec >= 0){ |
| 746 | putkvnum("videocodecid", vcodec); | 769 | putkvnum("videocodecid", vcodec); |
| @@ -750,6 +773,7 @@ rtmpmeta(RTMP *r, ulong sid, int vcodec, int w, int h, int acodec) · 3 unmodified lines |
| 750 | if(acodec >= 0) | 773 | if(acodec >= 0) |
| 751 | putkvnum("audiocodecid", acodec); | 774 | putkvnum("audiocodecid", acodec); |
| 752 | putend(); | 775 | putend(); |
| | 776 | r->o.msg.sid = sid; |
| 753 | | 777 | |
| 754 | res = rtmpsend(r); | 778 | res = rtmpsend(r); |
| 755 | | 779 | |
| @@ -758,18 +782,59 @@ rtmpmeta(RTMP *r, ulong sid, int vcodec, int w, int h, int acodec) · 2 unmodified lines |
| 758 | return res; | 782 | return res; |
| 759 | } | 783 | } |
| 760 | | 784 | |
| 761 | int | 785 | static int |
| 762 | rtmpdata(RTMP *r, ulong sid, u32int dt, int type, int fl, void *data, int sz) | 786 | nalsz(u8int *p, int sz, int *csz) |
| 763 | { | 787 | { |
| 764 | int res; | 788 | int n; |
| 765 | | 789 | |
| 766 | assert(type == Taudio || type == Tvideo); | 790 | for(n = 3; sz-n > 3; n++){ |
| | 791 | if(p[n] == 0 && p[n+1] == 0 && p[n+2] == 1){ |
| | 792 | *csz = 3; |
| | 793 | return n; |
| | 794 | } |
| | 795 | if(p[n] == 0 && p[n+1] == 0 && p[n+2] == 0 && p[n+3] == 1){ |
| | 796 | *csz = 4; |
| | 797 | return n; |
| | 798 | } |
| | 799 | } |
| 767 | | 800 | |
| 768 | qlock(r); | 801 | *csz = 0; |
| | 802 | |
| | 803 | return sz; |
| | 804 | } |
| | 805 | |
| | 806 | static int |
| | 807 | mkps(RTMP *r, u8int *p) |
| | 808 | { |
| | 809 | u8int *p₀; |
| | 810 | |
| | 811 | p₀ = p; |
| | 812 | |
| | 813 | *p++ = 1; /* version */ |
| | 814 | *p++ = r->v.sps[1]; /* profile */ |
| | 815 | *p++ = r->v.sps[2]; /* compatibility */ |
| | 816 | *p++ = r->v.sps[3]; /* level */ |
| | 817 | *p++ = 0xfc | 3; /* reserved (6 bits), NULA length size - 1 (2 bits) */ |
| | 818 | *p++ = 0xe0 | 1; /* reserved (3 bits), num of SPS (5 bits) */ |
| | 819 | *p++ = r->v.spssz >> 8; |
| | 820 | *p++ = r->v.spssz; |
| | 821 | p = (u8int*)memmove(p, r->v.sps, r->v.spssz) + r->v.spssz; |
| | 822 | |
| | 823 | *p++ = 1; |
| | 824 | *p++ = r->v.ppssz >> 8; |
| | 825 | *p++ = r->v.ppssz; |
| | 826 | p = (u8int*)memmove(p, r->v.pps, r->v.ppssz) + r->v.ppssz; |
| | 827 | |
| | 828 | return p - p₀; |
| | 829 | } |
| 769 | | 830 | |
| | 831 | static int |
| | 832 | data(RTMP *r, ulong sid, u32int dt, int type, int fl, void *data, int sz) |
| | 833 | { |
| 770 | bextend(&r->o, 64 + sz); | 834 | bextend(&r->o, 64 + sz); |
| 771 | | 835 | |
| 772 | newmsg(r, type == Taudio ? Audio : Video, Type0, CSCtl); | 836 | newmsg(r, type == Taudio ? Audio : Video, Type0, CSData); |
| | 837 | r->o.msg.ts = dt; |
| 773 | r->o.msg.sid = sid; | 838 | r->o.msg.sid = sid; |
| 774 | | 839 | |
| 775 | if(type == Taudio){ | 840 | if(type == Taudio){ |
| @@ -779,14 +844,67 @@ rtmpdata(RTMP *r, ulong sid, u32int dt, int type, int fl, void *data, int sz) · 3 unmodified lines |
| 779 | if(type == Tvideo){ | 844 | if(type == Tvideo){ |
| 780 | putbyte(((fl & FlKey) ? 0x10 : 0x20) | VcodecH264); | 845 | putbyte(((fl & FlKey) ? 0x10 : 0x20) | VcodecH264); |
| 781 | putbyte((fl & FlHdr) ? 0 : 1); | 846 | putbyte((fl & FlHdr) ? 0 : 1); |
| 782 | puti24(dt); | 847 | puti24(0); |
| 783 | if((fl & FlHdr) == 0) | 848 | if((fl & FlHdr) == 0) |
| 784 | puti32(sz); | 849 | puti32(sz); |
| 785 | } | 850 | } |
| 786 | putdata(data, sz); | 851 | putdata(data, sz); |
| 787 | | 852 | |
| 788 | res = rtmpsend(r); | 853 | return rtmpsend(r); |
| | 854 | } |
| | 855 | |
| | 856 | static int |
| | 857 | h264data(RTMP *r, ulong sid, u32int dt, u8int *p, int sz) |
| | 858 | { |
| | 859 | int csz, nsz, ntype; |
| | 860 | u8int ps[32+256]; |
| | 861 | |
| | 862 | for(; sz > 0; p += nsz, sz -= nsz){ |
| | 863 | nsz = nalsz(p, sz, &csz); |
| | 864 | p += csz; |
| | 865 | sz -= csz; |
| | 866 | nsz -= csz; |
| | 867 | |
| | 868 | ntype = *p & 0x1f; |
| | 869 | if(ntype == 7){ |
| | 870 | memmove(r->v.sps, p, nsz); |
| | 871 | r->v.spssz = nsz; |
| | 872 | continue; |
| | 873 | } |
| | 874 | if(ntype == 8){ |
| | 875 | memmove(r->v.pps, p, nsz); |
| | 876 | r->v.ppssz = nsz; |
| | 877 | continue; |
| | 878 | } |
| | 879 | |
| | 880 | if(r->v.spssz < 1 || r->v.ppssz < 1) |
| | 881 | continue; |
| 789 | | 882 | |
| | 883 | if(!r->v.hsent || ntype == 8){ |
| | 884 | r->v.hsent = 1; |
| | 885 | if(data(r, sid, 0, Tvideo, FlKey|FlHdr, ps, mkps(r, ps)) < 0) |
| | 886 | return -1; |
| | 887 | } |
| | 888 | if(ntype != 7 && ntype != 8){ |
| | 889 | if(data(r, sid, 100, Tvideo, ntype == 5 ? FlKey : 0, p-csz, nsz+csz) < 0) |
| | 890 | return -1; |
| | 891 | } |
| | 892 | } |
| | 893 | |
| | 894 | return 0; |
| | 895 | } |
| | 896 | |
| | 897 | int |
| | 898 | rtmpdata(RTMP *r, ulong sid, u32int dt, int type, void *p, int sz) |
| | 899 | { |
| | 900 | int res; |
| | 901 | |
| | 902 | assert(type == Taudio || type == Tvideo); |
| | 903 | |
| | 904 | qlock(r); |
| | 905 | res = 0; |
| | 906 | if(type == Tvideo) |
| | 907 | res = h264data(r, sid, dt, p, sz); |
| 790 | qunlock(r); | 908 | qunlock(r); |
| 791 | | 909 | |
| 792 | return res; | 910 | return res; |
| @@ -817,6 +935,7 @@ connect(RTMP *r) · 24 unmodified lines |
| 817 | putcommand("connect", connected); | 935 | putcommand("connect", connected); |
| 818 | putkvstr("app", r->app); | 936 | putkvstr("app", r->app); |
| 819 | putkvstr("tcUrl", r->tcurl); | 937 | putkvstr("tcUrl", r->tcurl); |
| | 938 | putkvstr("type", "nonprivate"); |
| 820 | putkvbool("fpad", 0); /* no proxy */ | 939 | putkvbool("fpad", 0); /* no proxy */ |
| 821 | putkvnum("audioCodecs", 0x4 | 0x400); /* mp3 + aac */ | 940 | putkvnum("audioCodecs", 0x4 | 0x400); /* mp3 + aac */ |
| 822 | putkvnum("videoCodecs", 0x80); /* h.264 */ | 941 | putkvnum("videoCodecs", 0x80); /* h.264 */ |
| @@ -907,7 +1026,6 @@ rtmpdial(char *url) · 84 unmodified lines |
| 907 | *s = 0; | 1026 | *s = 0; |
| 908 | path = s+1; | 1027 | path = s+1; |
| 909 | }else{ | 1028 | }else{ |
| 910 | *s = 0; | | |
| 911 | path = nil; | 1029 | path = nil; |
| 912 | } | 1030 | } |
| 913 | | 1031 | |