| @@ -14,7 +14,10 @@ enum { · 13 unmodified lines |
| 14 | 14 | Port = 1935, |
| 15 | 15 | CSsz = 1536, |
| 16 | 16 | ChunkDefault = 128, |
| 17 | | − ChunkDesired = 4096, |
| 17 | + ChunkDesired = 65536, |
| 18 | + |
| 19 | + FlKey = 1<<0, |
| 20 | + FlHdr = 1<<1, |
| 18 | 21 | |
| 19 | 22 | Type0 = 0, |
| 20 | 23 | Type1, |
| @@ -23,6 +26,7 @@ enum { · 2 unmodified lines |
| 23 | 26 | |
| 24 | 27 | CSUserCtl = 2, |
| 25 | 28 | CSCtl = 3, |
| 29 | + CSData = 4, |
| 26 | 30 | |
| 27 | 31 | CbCommand = 0, |
| 28 | 32 | CbTransID, |
| @@ -76,6 +80,7 @@ struct Command { · 47 unmodified lines |
| 76 | 80 | void (*cb)(RTMP *r, int ok, A₀ *a[NumCb], void *aux); |
| 77 | 81 | void *aux; |
| 78 | 82 | int tid; |
| 83 | + int cs; |
| 79 | 84 | |
| 80 | 85 | Command *prev, *next; |
| 81 | 86 | }; |
| @@ -117,6 +122,13 @@ struct RTMP { · 35 unmodified lines |
| 117 | 122 | int tid; |
| 118 | 123 | Command *w; |
| 119 | 124 | }cmds; |
| 125 | + struct { |
| 126 | + int hsent; |
| 127 | + int spssz; |
| 128 | + int ppssz; |
| 129 | + u8int sps[128]; |
| 130 | + u8int pps[128]; |
| 131 | + }v; |
| 120 | 132 | u8int biobuf[Biobufsz]; |
| 121 | 133 | }; |
| 122 | 134 | |
| @@ -142,6 +154,11 @@ struct RTMP { · 19 unmodified lines |
| 142 | 154 | r->o.msg.cmd.cb = cb_; \ |
| 143 | 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 | 162 | static int szs[] = { |
| 146 | 163 | [Type3] = 0, |
| 147 | 164 | [Type2] = 3, |
| @@ -204,6 +221,7 @@ newmsg(RTMP *r, int type, int fmt, int cs) · 56 unmodified lines |
| 204 | 221 | r->o.msg.cmd.tid = ++r->cmds.tid; |
| 205 | 222 | else |
| 206 | 223 | r->o.msg.cmd.tid = 0; |
| 224 | + r->o.msg.cmd.cs = cs; |
| 207 | 225 | } |
| 208 | 226 | |
| 209 | 227 | static void |
| @@ -328,12 +346,12 @@ rtmpsend(RTMP *r) · 118 unmodified lines |
| 328 | 346 | hsz = szs[m->fmt]; |
| 329 | 347 | e = h + hsz; |
| 330 | 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 | 350 | if(hsz >= szs[Type1]){ |
| 333 | 351 | h = a₀i24(h, e, m->sz); |
| 334 | 352 | h = a₀byte(h, e, m->type); |
| 335 | 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 | 357 | assert(h != nil); |
| @@ -487,7 +505,14 @@ loop(void *aux) · 147 unmodified lines |
| 487 | 505 | werrstr("transaction ID is not a number"); |
| 488 | 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 | 516 | if(c == nil) |
| 492 | 517 | fprint(2, "response to non-existent transaction %d", (int)a[n]->num); |
| 493 | 518 | break; |
| @@ -648,8 +673,7 @@ rtmpstream(RTMP *r, ulong *sid) · 154 unmodified lines |
| 648 | 673 | qlock(r); |
| 649 | 674 | |
| 650 | 675 | newmsg(r, AMF0Command, Type0, CSCtl); |
| 651 | | − putstr("createStream"); |
| 652 | | − putnum(r->o.msg.cmd.tid); |
| 676 | + putcall("createStream"); |
| 653 | 677 | putnull(); |
| 654 | 678 | |
| 655 | 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 | 723 | qlock(r); |
| 700 | 724 | |
| 701 | | − newmsg(r, AMF0Command, Type0, CSCtl); |
| 702 | | − notransaction(r); |
| 703 | | − putstr("publish"); |
| 704 | | − putnum(0); |
| 725 | + newmsg(r, AMF0Command, Type0, CSData); |
| 726 | + putcall("publish"); |
| 705 | 727 | putnull(); |
| 706 | 728 | putstr(name); |
| 707 | 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 | 758 | qlock(r); |
| 737 | 759 | |
| 738 | | − newmsg(r, AMF0Metadata, Type0, CSCtl); |
| 739 | | − r->o.msg.sid = sid; |
| 760 | + newmsg(r, AMF0Metadata, Type0, CSData); |
| 761 | + |
| 762 | + putstr("@setDataFrame"); |
| 740 | 763 | |
| 741 | 764 | putstr("onMetaData"); |
| 742 | | − putnum(0); |
| 743 | | − putobj(); |
| 765 | + putarr(); |
| 766 | + puti32(1 + (vcodec < 0 ? 0 : 3) + (acodec < 0 ? 0 : 1)); |
| 744 | 767 | putkvnum("duration", 0.0); |
| 745 | 768 | if(vcodec >= 0){ |
| 746 | 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 | 773 | if(acodec >= 0) |
| 751 | 774 | putkvnum("audiocodecid", acodec); |
| 752 | 775 | putend(); |
| 776 | + r->o.msg.sid = sid; |
| 753 | 777 | |
| 754 | 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 | 782 | return res; |
| 759 | 783 | } |
| 760 | 784 | |
| 761 | | −int |
| 762 | | −rtmpdata(RTMP *r, ulong sid, u32int dt, int type, int fl, void *data, int sz) |
| 785 | +static int |
| 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 | 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 | 838 | r->o.msg.sid = sid; |
| 774 | 839 | |
| 775 | 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 | 844 | if(type == Tvideo){ |
| 780 | 845 | putbyte(((fl & FlKey) ? 0x10 : 0x20) | VcodecH264); |
| 781 | 846 | putbyte((fl & FlHdr) ? 0 : 1); |
| 782 | | − puti24(dt); |
| 847 | + puti24(0); |
| 783 | 848 | if((fl & FlHdr) == 0) |
| 784 | 849 | puti32(sz); |
| 785 | 850 | } |
| 786 | 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 | 908 | qunlock(r); |
| 791 | 909 | |
| 792 | 910 | return res; |
| @@ -817,6 +935,7 @@ connect(RTMP *r) · 24 unmodified lines |
| 817 | 935 | putcommand("connect", connected); |
| 818 | 936 | putkvstr("app", r->app); |
| 819 | 937 | putkvstr("tcUrl", r->tcurl); |
| 938 | + putkvstr("type", "nonprivate"); |
| 820 | 939 | putkvbool("fpad", 0); /* no proxy */ |
| 821 | 940 | putkvnum("audioCodecs", 0x4 | 0x400); /* mp3 + aac */ |
| 822 | 941 | putkvnum("videoCodecs", 0x80); /* h.264 */ |
| @@ -907,7 +1026,6 @@ rtmpdial(char *url) · 84 unmodified lines |
| 907 | 1026 | *s = 0; |
| 908 | 1027 | path = s+1; |
| 909 | 1028 | }else{ |
| 910 | | − *s = 0; |
| 911 | 1029 | path = nil; |
| 912 | 1030 | } |
| 913 | 1031 | |