├── .idea ├── .gitignore ├── compiler.xml ├── encodings.xml ├── jarRepositories.xml ├── libraries │ └── io_netty_netty_all_5_0_0_Alpha1.xml ├── misc.xml ├── rtmpServer.iml ├── sbt.xml ├── uiDesigner.xml ├── vcs.xml └── workspace.xml ├── README.en.md ├── README.md ├── pom.xml ├── rtmpServer.iml ├── src └── main │ └── java │ ├── AMF │ ├── AMF.java │ ├── AMFClass.java │ └── AMFUtil.java │ ├── Decoder │ ├── AudioStreamDecoder.java │ ├── RtmpDecoder.java │ ├── RtmpDecoderbak.java │ └── VideoStreamDecoder.java │ ├── EnCoder │ └── RtmpEncoder.java │ ├── Handler │ └── RtmpHandler.java │ ├── META-INF │ └── MANIFEST.MF │ ├── Rtmp.java │ ├── Rtmp │ ├── Amf.java │ ├── RtmpAudio.java │ ├── RtmpBytesRead.java │ ├── RtmpChunk.java │ ├── RtmpChunkSize.java │ ├── RtmpControl.java │ ├── RtmpHandshake.java │ ├── RtmpMessage.java │ ├── RtmpNot.java │ ├── RtmpNotify.java │ ├── RtmpPacket.java │ ├── RtmpPing.java │ ├── RtmpResponse.java │ ├── RtmpServerWindow.java │ └── RtmpVideo.java │ ├── User │ ├── Publish.java │ ├── PublishGroup.java │ ├── Receive.java │ └── ReceiveGroup.java │ ├── Util │ ├── AACDecoderSpecific.java │ ├── AudioSpecificConfig.java │ ├── Common.java │ └── MsgType.java │ └── test.java └── target └── classes ├── AMF ├── AMF.class ├── AMFClass.class └── AMFUtil.class ├── Decoder ├── AudioStreamDecoder.class ├── RtmpDecoder$1.class ├── RtmpDecoder.class ├── RtmpDecoderbak.class ├── SendData.class ├── SendDecoder.class └── VideoStreamDecoder.class ├── EnCoder └── RtmpEncoder.class ├── Handler └── RtmpHandler.class ├── META-INF └── rtmpServer.kotlin_module ├── Rtmp$1.class ├── Rtmp.class ├── Rtmp ├── Amf.class ├── RtmpAudio.class ├── RtmpBytesRead.class ├── RtmpChunk.class ├── RtmpChunkSize.class ├── RtmpControl.class ├── RtmpHandshake.class ├── RtmpMessage.class ├── RtmpNot.class ├── RtmpNotify.class ├── RtmpPacket.class ├── RtmpPing.class ├── RtmpResponse.class ├── RtmpServerWindow.class └── RtmpVideo.class ├── User ├── Publish.class ├── PublishGroup.class ├── Receive.class └── ReceiveGroup.class ├── Util ├── AACDecoderSpecific.class ├── AudioSpecificConfig.class ├── Common.class └── MsgType.class └── test.class /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/libraries/io_netty_netty_all_5_0_0_Alpha1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/rtmpServer.iml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.idea/sbt.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 13 | 14 | 16 | 17 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 1570624912476 95 | 136 | 137 | 1571039624065 138 | 143 | 144 | 1571132649103 145 | 150 | 151 | 1571217773128 152 | 157 | 158 | 1571384083045 159 | 164 | 165 | 1571648930857 166 | 171 | 172 | 1572255160271 173 | 178 | 181 | 182 | 184 | 185 | 186 | 187 | 188 | 189 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 264 | 265 | 266 | 267 | 268 | 269 | No facets are configured 270 | 271 | 276 | 277 | 278 | 279 | 280 | 281 | scala-sdk-2.12.8 282 | 283 | 288 | 289 | 290 | 291 | 292 | 293 | 1.8 294 | 295 | 300 | 301 | 302 | 303 | 304 | 305 | rtmpServer 306 | 307 | 312 | 313 | 314 | 315 | 316 | 317 | 1.8 318 | 319 | 324 | 325 | 326 | 327 | 328 | 329 | io.netty:netty-all:5.0.0.Alpha1 330 | 331 | 336 | 337 | 338 | 339 | 340 | 341 | -------------------------------------------------------------------------------- /README.en.md: -------------------------------------------------------------------------------- 1 | # rtmpServer 2 | 3 | #### Description 4 | {**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**} 5 | 6 | #### Software Architecture 7 | Software architecture description 8 | 9 | #### Installation 10 | 11 | 1. xxxx 12 | 2. xxxx 13 | 3. xxxx 14 | 15 | #### Instructions 16 | 17 | 1. xxxx 18 | 2. xxxx 19 | 3. xxxx 20 | 21 | #### Contribution 22 | 23 | 1. Fork the repository 24 | 2. Create Feat_xxx branch 25 | 3. Commit your code 26 | 4. Create Pull Request 27 | 28 | 29 | #### Gitee Feature 30 | 31 | 1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md 32 | 2. Gitee blog [blog.gitee.com](https://blog.gitee.com) 33 | 3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) 34 | 4. The most valuable open source project [GVP](https://gitee.com/gvp) 35 | 5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) 36 | 6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 基于netty4.0 实现的rtmp 协议demo 推拉流已经实现 -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | rtmpServer 8 | rtmpServer 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | org.apache.maven.plugins 14 | maven-compiler-plugin 15 | 16 | 6 17 | 6 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | io.netty 26 | netty-all 27 | 4.1.42.Final 28 | 29 | 30 | 31 | 32 | org.jboss.netty 33 | netty 34 | 3.2.10.Final 35 | 36 | 37 | 38 | org.projectlombok 39 | lombok 40 | 1.16.18 41 | provided 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /rtmpServer.iml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/main/java/AMF/AMF.java: -------------------------------------------------------------------------------- 1 | package AMF; 2 | 3 | public class AMF { 4 | public final static byte 5 | Number = 0x00, // 0 6 | Boolean = 0x01, // 1 7 | String = 0x02, // 2 8 | UntypedObject = 0x03, // 3 9 | MovieClip = 0x04, // 4 10 | Null = 0x05, // 5 11 | Undefined = 0x06, // 6 12 | ReferencedObject = 0x07, // 7 13 | MixedArray = 0x08, // 8 14 | End = 0x09, // 9 15 | Array = 0x10, // 10 16 | Date = 0x11, // 11 17 | LongString = 0x12, // 12 18 | TypeAsObject = 0x13, // 13 19 | Recordset = 0x14, // 14 20 | Xml = 0x15, // 15 21 | TypedObject = 0x16, // 16 22 | AMF3data = 0x17; // 17 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/AMF/AMFClass.java: -------------------------------------------------------------------------------- 1 | package AMF; 2 | 3 | public class AMFClass { 4 | public byte[] message; 5 | public int pos; 6 | public int version = 0; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/AMF/AMFUtil.java: -------------------------------------------------------------------------------- 1 | package AMF; 2 | 3 | 4 | import Util.Common; 5 | 6 | import java.lang.reflect.Field; 7 | import java.util.*; 8 | 9 | 10 | public class AMFUtil { 11 | /** 12 | * 加载 amf string 13 | * @param amfClass 14 | * @return 15 | */ 16 | public static String load_amf_string(AMFClass amfClass) { 17 | String msg = ""; 18 | if(amfClass.message[amfClass.pos] != AMF.String) { 19 | return msg; 20 | } 21 | amfClass.pos += 1; 22 | if(amfClass.pos + 2 > amfClass.message.length){ 23 | System.out.println("string len 解析数据长度不足,数据错误"); 24 | return msg; 25 | } 26 | byte[] strLenByte = {amfClass.message[amfClass.pos],amfClass.message[amfClass.pos+1]}; 27 | int strLen = Common.byteToInt16(strLenByte); 28 | amfClass.pos += 2; 29 | if(amfClass.pos + strLen > amfClass.message.length){ 30 | System.out.println("string message 解析数据长度不足,数据错误"); 31 | return msg; 32 | } 33 | byte[] str = new byte[strLen]; 34 | int index = 0; 35 | for(int i = amfClass.pos;i < amfClass.pos + strLen;i++){ //链接字符串 36 | str[index] = amfClass.message[i]; 37 | index++; 38 | } 39 | amfClass.pos += strLen; 40 | msg = new String(str); 41 | return msg; 42 | } 43 | 44 | /** 45 | * amf string 写入 46 | * @param str 47 | * @return 48 | */ 49 | public static byte[] writeString(String str) { 50 | List data = new ArrayList(); 51 | data.add(AMF.String); 52 | byte[] strByte = str.getBytes(); 53 | int len = strByte.length; 54 | byte[] lenByte = Common.intToByte(len); //转换 之后为小端模式,直接拿前两位 就可以了 55 | data.add(lenByte[1]); // rtmp 数据都为 大端模式 56 | data.add(lenByte[0]); 57 | for(byte val: strByte) { 58 | data.add(val); 59 | } 60 | return Common.conversionByteArray(data); 61 | } 62 | 63 | /** 64 | * 65 | * @param key 66 | * @return 67 | */ 68 | public static byte[] writeKey(String key) { 69 | List data = new ArrayList(); 70 | byte[] strByte = key.getBytes(); 71 | int len = strByte.length; 72 | byte[] lenByte = Common.intToByte(len); //转换 之后为小端模式,直接拿前两位 就可以了 73 | data.add(lenByte[1]); // rtmp 数据都为 大端模式 74 | data.add(lenByte[0]); 75 | for(byte val: strByte) { 76 | data.add(val); 77 | } 78 | return Common.conversionByteArray(data); 79 | } 80 | 81 | /** 82 | * 编写boolean 83 | * @param val 84 | * @return 85 | */ 86 | public static byte[] writeBoolean(boolean val) { 87 | byte[] data = new byte[2]; 88 | data[0] = AMF.Boolean; 89 | data[1] = (byte) (val ? 0x01: 0x00); 90 | return data; 91 | } 92 | 93 | public static byte[] writeObject(Map objectData) { 94 | List data = new ArrayList(); 95 | data.add(AMF.UntypedObject); 96 | for (Map.Entry entry : objectData.entrySet()) { 97 | try { 98 | byte[] valueByte = writeValue(entry.getValue()); //如果解析不出 数据,直接跳过去 99 | String key = entry.getKey(); 100 | byte[] keyByte = writeKey(key); 101 | for(byte i : keyByte){ 102 | data.add(i); 103 | } 104 | for(byte i: valueByte){ 105 | data.add(i); 106 | } 107 | } catch (Exception e) { 108 | e.printStackTrace(); 109 | } 110 | 111 | } 112 | data.add((byte) 0x00); 113 | data.add((byte) 0x00); 114 | data.add((byte) 0x09); 115 | return Common.conversionByteArray(data); 116 | } 117 | 118 | public static byte[] writeMixedArray(Map objectData) { 119 | List data = new ArrayList(); 120 | data.add(AMF.MixedArray); 121 | for(byte i : Common.intToByte(0)){ 122 | data.add(i); 123 | } 124 | for (Map.Entry entry : objectData.entrySet()) { 125 | try { 126 | byte[] valueByte = writeValue(entry.getValue()); //如果解析不出 数据,直接跳过去 127 | String key = entry.getKey(); 128 | byte[] keyByte = writeKey(key); 129 | for(byte i : keyByte){ 130 | data.add(i); 131 | } 132 | for(byte i: valueByte){ 133 | data.add(i); 134 | } 135 | } catch (Exception e) { 136 | e.printStackTrace(); 137 | } 138 | 139 | } 140 | data.add((byte) 0x00); 141 | data.add((byte) 0x00); 142 | data.add((byte) 0x09); 143 | return Common.conversionByteArray(data); 144 | } 145 | 146 | 147 | /** 148 | * 获取 object value 149 | * @param param 150 | * @return 151 | */ 152 | public static byte[] writeValue(Object param) throws Exception { 153 | byte[] data; 154 | if (param instanceof Double) { 155 | double d = ((Double) param).doubleValue(); 156 | data = writeNumber(d); 157 | } else if (param instanceof String) { 158 | String s = (String) param; 159 | data = writeString(s); 160 | } else if (param instanceof Boolean) { 161 | boolean b = ((Boolean) param).booleanValue(); 162 | data = writeBoolean(b); 163 | } else if(param instanceof Map){ 164 | Map map = objectToMap(param); 165 | data = writeObject(map); 166 | } else { 167 | throw new Exception("数据异常,没有找到解析value"); 168 | } 169 | return data; 170 | } 171 | 172 | public static Map objectToMap(Object obj) { 173 | Map map = new HashMap(); 174 | map = objectToMap(obj, false); 175 | return map; 176 | } 177 | 178 | public static Map objectToMap(Object obj, boolean keepNullVal) { 179 | if (obj == null) { 180 | return null; 181 | } 182 | 183 | Map map = new HashMap(); 184 | try { 185 | Field[] declaredFields = obj.getClass().getDeclaredFields(); 186 | for (Field field : declaredFields) { 187 | field.setAccessible(true); 188 | if (keepNullVal == true) { 189 | map.put(field.getName(), field.get(obj)); 190 | } else { 191 | if (field.get(obj) != null && !"".equals(field.get(obj).toString())) { 192 | map.put(field.getName(), field.get(obj)); 193 | } 194 | } 195 | } 196 | } catch (Exception e) { 197 | e.printStackTrace(); 198 | } 199 | return map; 200 | } 201 | 202 | /** 203 | * amf number 写法 204 | * @param val 205 | * @return 206 | */ 207 | public static byte[] writeNumber(double val){ 208 | List data = new ArrayList(); 209 | data.add(AMF.Number); 210 | byte[] doubleData = Common.reverseArray(Common.double2Bytes(val)); 211 | for(byte i : doubleData){ 212 | data.add(i); 213 | } 214 | return Common.conversionByteArray(data); 215 | } 216 | 217 | /** 218 | * amf null 写入方法 219 | * @return 220 | */ 221 | public static byte writeNull() { 222 | return AMF.Null; 223 | } 224 | 225 | /** 226 | * 提取 amf 里面的 boolean 227 | * @param amfClass 228 | * @return 229 | */ 230 | public static boolean load_amf_boolean(AMFClass amfClass) { 231 | if(amfClass.message[amfClass.pos] != AMF.Boolean) { 232 | System.out.println("boolean获取失败"); 233 | return false; 234 | } 235 | amfClass.pos += 1; 236 | boolean flag = amfClass.message[amfClass.pos] != 0; 237 | amfClass.pos += 1; 238 | return flag; 239 | } 240 | 241 | /** 242 | * 加载 key 243 | * @param amfClass 244 | * @return 245 | */ 246 | public static String load_amf_key(AMFClass amfClass) { 247 | String msg = ""; 248 | if(amfClass.pos + 2 > amfClass.message.length){ 249 | System.out.println("key len 解析数据长度不足,数据错误"); 250 | return msg; 251 | } 252 | byte[] strLenByte = {amfClass.message[amfClass.pos],amfClass.message[amfClass.pos+1]}; 253 | int strLen = Common.byteToInt16(strLenByte); 254 | amfClass.pos += 2; 255 | if(amfClass.pos + strLen > amfClass.message.length){ 256 | System.out.println("key message 解析数据长度不足,数据错误"); 257 | return msg; 258 | } 259 | byte[] str = new byte[strLen]; 260 | int index = 0; 261 | for(int i = amfClass.pos;i < amfClass.pos + strLen;i++){ //链接字符串 262 | str[index] = amfClass.message[i]; 263 | index++; 264 | } 265 | amfClass.pos += strLen; 266 | msg = new String(str); 267 | return msg; 268 | } 269 | 270 | /** 271 | * 解析 amf number 272 | * @param amfClass 273 | * @return 274 | */ 275 | public static double load_amf_number(AMFClass amfClass){ 276 | byte type = amfClass.message[amfClass.pos]; 277 | if(type != AMF.Number){ 278 | return -1; 279 | } 280 | amfClass.pos += 1; 281 | if(amfClass.pos + Common.AFM_NUMBER_LENGTH > amfClass.message.length) { 282 | return -1; 283 | } 284 | byte[] number = new byte[Common.AFM_NUMBER_LENGTH]; 285 | int index = 0; 286 | for(int i = amfClass.pos; i < amfClass.pos + Common.AFM_NUMBER_LENGTH;i++) { 287 | number[index] = amfClass.message[i]; 288 | index++; 289 | } 290 | // System.out.println (Common.bytes2hex(Common.reverseArray(number))); 291 | amfClass.pos += Common.AFM_NUMBER_LENGTH; 292 | return Common.bytesToDouble(Common.reverseArray(number)); 293 | } 294 | 295 | 296 | // public static Object load_amf_value(AMFClass amfClass){ 297 | // byte type = amfClass.message[amfClass.pos]; 298 | // switch (type) { 299 | // case AMF.Number: 300 | // return load_amf_number(amfClass); 301 | // case AMF.String: 302 | // return load_amf_string(amfClass); 303 | // case AMF.UntypedObject: 304 | // return load_amf_object(amfClass); 305 | // case AMF.Boolean: 306 | // return load_amf_boolean(amfClass); 307 | // default: 308 | // System.out.println("其他消息" + type); 309 | // return null; 310 | // } 311 | // } 312 | /** 313 | * 解析 object value 314 | * @param amfClass 315 | * @return 316 | */ 317 | public static Object load_amf(AMFClass amfClass) { 318 | byte type = amfClass.message[amfClass.pos]; 319 | switch (type) { 320 | case AMF.Number: 321 | return load_amf_number(amfClass); 322 | case AMF.String: 323 | return load_amf_string(amfClass); 324 | case AMF.UntypedObject: 325 | return load_amf_object(amfClass); 326 | case AMF.Boolean: 327 | return load_amf_boolean(amfClass); 328 | case AMF.Null: 329 | amfClass.pos++; 330 | return null; 331 | case AMF.MixedArray: 332 | return load_amf_mixedArray(amfClass); 333 | default: 334 | System.out.println("其他消息" + type); 335 | return null; 336 | } 337 | } 338 | 339 | /** 340 | * 解析 amf object 数据 341 | * @param amfClass 342 | * @return 343 | */ 344 | public static Map load_amf_object(AMFClass amfClass) { 345 | Map amfData = new HashMap(); 346 | byte type = amfClass.message[amfClass.pos]; 347 | if(type != AMF.UntypedObject){ 348 | return null; 349 | } 350 | amfClass.pos += 1; 351 | while (true){ 352 | String key = load_amf_key(amfClass); 353 | if(key.length() == 0){ 354 | break; 355 | } 356 | Object value = load_amf(amfClass); 357 | amfData.put(key,value); 358 | // System.out.println(key +"==="+value); 359 | } 360 | //System.out.println(amfClass.pos); 361 | amfClass.pos += 1;// object 最后 结束为 00 00 09 取到最后是00 所以 +1 跳过 09 这个 字节 362 | return amfData; 363 | } 364 | 365 | 366 | /** 367 | * 解析 amf object 数据 368 | * @param amfClass 369 | * @return 370 | */ 371 | public static Map load_amf_mixedArray(AMFClass amfClass) { 372 | Map amfData = new HashMap(); 373 | byte type = amfClass.message[amfClass.pos]; 374 | if(type != AMF.MixedArray){ 375 | return null; 376 | } 377 | amfClass.pos += 1; 378 | if(amfClass.pos + 4 > amfClass.message.length) { 379 | return null; 380 | } 381 | amfClass.pos += 4; 382 | while (true){ 383 | String key = load_amf_key(amfClass); 384 | if(key.length() == 0){ 385 | break; 386 | } 387 | Object value = load_amf(amfClass); 388 | amfData.put(key,value); 389 | // System.out.println(key +"==="+value); 390 | } 391 | System.out.println(amfClass.pos); 392 | amfClass.pos += 1;// object 最后 结束为 00 00 09 取到最后是00 所以 +1 跳过 09 这个 字节 393 | return amfData; 394 | } 395 | 396 | 397 | } 398 | -------------------------------------------------------------------------------- /src/main/java/Decoder/AudioStreamDecoder.java: -------------------------------------------------------------------------------- 1 | package Decoder; 2 | 3 | import Util.AACDecoderSpecific; 4 | import Util.AudioSpecificConfig; 5 | import Util.Common; 6 | 7 | public class AudioStreamDecoder { 8 | private int index = 0; 9 | AudioSpecificConfig ascAudioSpecificConfig = new AudioSpecificConfig(); 10 | AACDecoderSpecific aacDecoderSpecific = new AACDecoderSpecific(); 11 | 12 | 13 | public void decode(byte[] data) { 14 | byte type = data[index++]; 15 | byte AACPacketType = data[index++]; 16 | // System.out.println(Common.bytes2hex(data)); 17 | if (AACPacketType == 0x00) { 18 | System.out.println(Common.bytes2hex(data)); 19 | aacDecoderSpecific.nAudioFortmatType = (byte) ((type & 0xff & 0xff) >> 4); 20 | aacDecoderSpecific.nAudioSampleType = (byte) ((type & 0x0c & 0xff & 0xff) >> 2); 21 | aacDecoderSpecific.nAudioSizeType = (byte) ((type & 0x02 & 0xff & 0xff) >> 1); 22 | aacDecoderSpecific.nAudioStereo = (byte) (type & 0x01 & 0xff & 0xff); 23 | } 24 | // System.out.println(aacDecoderSpecific.nAudioFortmatType); 25 | 26 | byte[] packet = new byte[data.length - index]; 27 | // System.out.println("packet length" + packet.length); 28 | // System.out.println("data length" + data.length); 29 | if (aacDecoderSpecific.nAudioFortmatType == 0x0a) { 30 | for (int i = 0; i < data.length - 2; i++) { 31 | packet[i] = data[index++]; 32 | } 33 | if (AACPacketType == 0x00) { 34 | // Common.appendMethodA("D:\\test2.aac",packet); 35 | // unsigned short audioSpecificConfig = 0; 36 | short audioSpecificConfig = (short) ((data[2] & 0xff) << 8); 37 | audioSpecificConfig += 0x00ff & data[3]; 38 | ascAudioSpecificConfig.nAudioObjectType = (byte) ((audioSpecificConfig & 0xF800) >> 11); 39 | ascAudioSpecificConfig.nSampleFrequencyIndex = (byte) ((audioSpecificConfig & 0x0780) >> 7); 40 | ascAudioSpecificConfig.nChannels = (byte) ((audioSpecificConfig & 0x78) >> 3); 41 | ascAudioSpecificConfig.nFrameLengthFlag = (byte) ((audioSpecificConfig & 0x04) >> 2); 42 | ascAudioSpecificConfig.nDependOnCoreCoder = (byte) ((audioSpecificConfig & 0x02) >> 1); 43 | ascAudioSpecificConfig.nExtensionFlag = (byte) (audioSpecificConfig & 0x01); 44 | System.out.println("nAudioObjectType" + ascAudioSpecificConfig.nAudioObjectType); 45 | System.out.println("nSampleFrequencyIndex" + ascAudioSpecificConfig.nSampleFrequencyIndex); 46 | System.out.println("nChannels" + ascAudioSpecificConfig.nChannels); 47 | // Common.appendMethodA("D:\\fuweicong.aac",Common.CreateADTS(ascAudioSpecificConfig,packet.length)); 48 | // Common.appendMethodA("D:\\fuweicong.aac",packet); // 错误 49 | } else { 50 | Common.appendMethodA("D:\\fuweicong.aac",Common.CreateADTS(ascAudioSpecificConfig,packet.length + 7)); 51 | Common.appendMethodA("D:\\fuweicong.aac",packet); 52 | } 53 | } 54 | index = 0; 55 | } 56 | 57 | 58 | 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/Decoder/RtmpDecoder.java: -------------------------------------------------------------------------------- 1 | package Decoder; 2 | 3 | import Rtmp.*; 4 | import User.Receive; 5 | import User.ReceiveGroup; 6 | import Util.Common; 7 | import io.netty.buffer.ByteBuf; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.handler.codec.ByteToMessageDecoder; 10 | 11 | import java.util.*; 12 | 13 | public class RtmpDecoder extends ByteToMessageDecoder { 14 | 15 | // chunk message 数据 16 | private ByteBuf byteBuf = null; 17 | 18 | //握手数据 19 | private RtmpHandshake rtmpHandshake = new RtmpHandshake(); 20 | 21 | private String path = null; 22 | 23 | AudioStreamDecoder audioStreamDecoder = new AudioStreamDecoder(); 24 | 25 | private RtmpPacket rtmpPacket = null; 26 | 27 | private int chunkLength = Common.DEFAULT_CHUNK_MESSAGE_LENGTH; 28 | 29 | @Override 30 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 31 | byteBuf = in; 32 | if (!rtmpHandshake.isHandshake) { //rtmp 握手认证 33 | rtmpHandshake.handShake(ctx,in); 34 | } else { 35 | handChunkMessage(ctx); 36 | } 37 | } 38 | 39 | // +-------+ +--------------+----------------+ 40 | // | Chunk | = | Chunk Header | Chunk Data | 41 | // +-------+ +--------------+----------------+ 42 | // 43 | // +--------------+ +-------------+----------------+-------------------+ 44 | // | Chunk Header | = | Basic header| Message Header |Extended Timestamp | 45 | // +--------------+ +-------------+----------------+-------------------+ 46 | // 47 | /** 48 | * 解析chunkMessage 数据 49 | * 50 | * @param ctx 51 | */ 52 | private void handChunkMessage(ChannelHandlerContext ctx) { 53 | if(rtmpPacket != null && rtmpPacket.isLackMessage()) { 54 | rtmpPacket.lackMessage(byteBuf); 55 | } 56 | else if(rtmpPacket != null){ 57 | rtmpPacket.reset(byteBuf); 58 | } 59 | else { 60 | rtmpPacket = new RtmpPacket(ctx,byteBuf); 61 | } 62 | if(rtmpPacket.isLackMessage()){ 63 | return; 64 | } 65 | 66 | if(!rtmpPacket.isSubpackage()) { 67 | switch (rtmpPacket.getMessageType()) { 68 | case ChunkSize: 69 | RtmpChunkSize rtmpChunkSize = new RtmpChunkSize(); 70 | rtmpChunkSize.setChunSize(rtmpPacket.getMessageData()); 71 | rtmpChunkSize.responseChunkSize(ctx,Common.DEFAULT_CHUNK_MESSAGE_LENGTH); 72 | rtmpPacket.setChunkLength(rtmpChunkSize.getChunkSize()); 73 | // RtmpResponse.chunkLength = rtmpChunkSize.getChunkSize(); 74 | chunkLength = rtmpChunkSize.getChunkSize(); 75 | break; 76 | case BYTES_READ: 77 | RtmpBytesRead rtmpBytesRead = new RtmpBytesRead(); 78 | rtmpBytesRead.setBytesRead(rtmpPacket.getMessageData()); 79 | break; 80 | case PING: 81 | RtmpPing rtmpPing = new RtmpPing(); 82 | rtmpPing.setPing(rtmpPacket.getMessageData()); 83 | break; 84 | case SERVER_WINDOW: 85 | RtmpServerWindow rtmpServerWindow = new RtmpServerWindow(); 86 | rtmpServerWindow.setRtmpServerWindow(rtmpPacket.getMessageData()); 87 | break; 88 | case AUDIO: 89 | RtmpAudio rtmpAudio = new RtmpAudio(); 90 | rtmpAudio.setAudioData(rtmpPacket.getMessageData(),this.path,rtmpPacket.getTimestamp(),audioStreamDecoder); 91 | break; 92 | case VIDEO: 93 | RtmpVideo rtmpVideo = new RtmpVideo(); 94 | rtmpVideo.setVideoData(rtmpPacket.getMessageData(),this.path,rtmpPacket.getTimestamp()); 95 | break; 96 | case NOTIFY: 97 | RtmpNotify rtmpNotify = new RtmpNotify(); 98 | rtmpNotify.setNotify(rtmpPacket.getMessageData(),this.path); 99 | break; 100 | case Control: 101 | RtmpControl rtmpControl = new RtmpControl(); 102 | rtmpControl.setControl(rtmpPacket.getMessageData(),ctx,chunkLength); 103 | System.out.println("strameId === " + rtmpPacket.getStreamId()); 104 | if(rtmpControl.getMsg().equals("FCPublish") || rtmpControl.getMsg().equals("play")) 105 | this.path = rtmpControl.getPath(); 106 | break; 107 | case NOT: 108 | System.out.println("消息类型还没编写"); 109 | RtmpNot rtmpNot = new RtmpNot(); 110 | rtmpNot.setNot(rtmpPacket.getMessageData()); 111 | } 112 | } 113 | 114 | 115 | if(rtmpPacket.getByteBuf().readableBytes() > 0){ 116 | handChunkMessage(ctx); 117 | } 118 | 119 | } 120 | 121 | @Override 122 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 123 | System.err.println("错误信息"); 124 | System.out.println(cause.getMessage()); 125 | List listVideo = ReceiveGroup.getChannel(this.path); 126 | List newListVideo = new ArrayList(); 127 | for (int i = 0; i < listVideo.size(); i++) { 128 | Receive receive = listVideo.get(i); 129 | if (receive.receive != ctx) { 130 | newListVideo.add(receive); 131 | } 132 | } 133 | ReceiveGroup.setChannel(this.path, newListVideo); 134 | super.exceptionCaught(ctx, cause); 135 | } 136 | 137 | } -------------------------------------------------------------------------------- /src/main/java/Decoder/RtmpDecoderbak.java: -------------------------------------------------------------------------------- 1 | package Decoder; 2 | 3 | import AMF.AMFClass; 4 | import AMF.AMFUtil; 5 | import Rtmp.RtmpHandshake; 6 | import User.Publish; 7 | import User.PublishGroup; 8 | import User.Receive; 9 | import User.ReceiveGroup; 10 | import Util.Common; 11 | import Util.MsgType; 12 | import io.netty.buffer.ByteBuf; 13 | import io.netty.buffer.ByteBufAllocator; 14 | import io.netty.buffer.Unpooled; 15 | import io.netty.channel.ChannelHandlerContext; 16 | import io.netty.handler.codec.ByteToMessageDecoder; 17 | 18 | import java.util.ArrayList; 19 | import java.util.HashMap; 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | import static Util.Common.FLV_KEY_FRAME; 24 | 25 | public class RtmpDecoderbak extends ByteToMessageDecoder { 26 | 27 | //chunk head 数据 28 | private List chunkData = new ArrayList(); // chunk所有数据, 包含 header 29 | private int chunkHeadIndex = 0; //byte head 提取的下标 30 | private int timestamp = 0; // 时间戳 31 | private int sendTimestamp = 0; 32 | private int msgLength = 0; //整个chunk数据长度,不包含 header 33 | private int allMsglength = 0; 34 | private byte msgType; //消息类型 35 | private int streamId = 0; 36 | private boolean isExtendedTimestamp = false; 37 | 38 | // chunk message 数据 39 | private ByteBuf byteBuf = null; 40 | //当数据不足msg length 时候的处理 41 | private byte[] allMessageData = null; 42 | private int readMessageIndex = 0; 43 | 44 | //网络传输数据包不足时候的处理 45 | private boolean lackMessage = false; 46 | 47 | 48 | private int head_len = 0; 49 | private boolean lackHeadMessage = false; 50 | private int csid = 0; 51 | private Map csIdMap = new HashMap(); 52 | private Map timeMap = new HashMap(); 53 | private Map msyTypeMap = new HashMap(); 54 | 55 | 56 | //握手数据 57 | private RtmpHandshake rtmpHandshake = new RtmpHandshake(); 58 | 59 | private String path = null; 60 | private int chunkLength = Common.DEFAULT_CHUNK_MESSAGE_LENGTH; 61 | private int sendChunkLength = Common.DEFAULT_CHUNK_MESSAGE_LENGTH; 62 | 63 | private boolean isPlay = false; 64 | 65 | private ByteBuf streamByteBuf = ByteBufAllocator.DEFAULT.buffer(1024); 66 | 67 | AudioStreamDecoder audioStreamDecoder = new AudioStreamDecoder(); 68 | 69 | 70 | @Override 71 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 72 | 73 | // System.err.println("可读字节 " + in.readableBytes()); 74 | byteBuf = in; 75 | if (!rtmpHandshake.isHandshake) { //rtmp 握手认证 76 | //handshake(ctx); 77 | rtmpHandshake.handShake(ctx,in); 78 | } else { 79 | handChunkMessage(ctx); 80 | } 81 | } 82 | 83 | // +-------+ +--------------+----------------+ 84 | // | Chunk | = | Chunk Header | Chunk Data | 85 | // +-------+ +--------------+----------------+ 86 | // 87 | // +--------------+ +-------------+----------------+-------------------+ 88 | // | Chunk Header | = | Basic header| Message Header |Extended Timestamp | 89 | // +--------------+ +-------------+----------------+-------------------+ 90 | // 91 | 92 | /** 93 | * 解析chunkMessage 数据 94 | * 95 | * @param ctx 96 | */ 97 | private void handChunkMessage(ChannelHandlerContext ctx) { 98 | List headData = new ArrayList(); 99 | try { 100 | if (!lackMessage) { 101 | if (!lackHeadMessage) { 102 | // System.out.println(Common.bytes2hex(Common.conversionByteArray(chunkData))); 103 | byte[] flags = new byte[1]; 104 | byteBuf.readBytes(flags); 105 | headData.add(flags[0]); 106 | int[] chunk_head_length = {12, 8, 4, 1}; //对应的 chunk head length 长度 107 | // byte fmtByte = (byte)((byte)flags >> 6); //向右移动 6 位 获取 fmt 108 | int fmt = (byte) ((flags[0] & 0xff & 0xff) >> 6); 109 | int csidTS = (byte) ((flags[0] & 0xff & 0xff) & 0x3f); // 按位与 11 为 1 ,有0 为 0 110 | this.csid = csidTS; 111 | this.head_len = chunk_head_length[fmt]; 112 | int basic_head_len = chunkHeadIndex = getBasicHeadLength(csidTS); 113 | byte[] chunkDataByte = Common.conversionByteArray(chunkData); 114 | } 115 | if (byteBuf.readableBytes() < this.head_len) { // 当byte长度不足时候,跳出函数等待数据 116 | lackHeadMessage = true; 117 | return; 118 | } 119 | lackHeadMessage = false; 120 | if ((this.head_len == 1 || this.head_len == 4) && this.allMessageData == null) { 121 | this.msgLength = this.csIdMap.get(this.csid); 122 | this.allMsglength = this.csIdMap.get(this.csid); 123 | this.allMessageData = new byte[this.allMsglength]; 124 | } 125 | // System.out.println("head" + head_len + " msg Length " + this.msgLength); 126 | if (head_len >= 4) { // 大于 1 先提取出 timestamp 127 | byte[] timestampByte = new byte[Common.TIMESTAMP_BYTE_LENGTH]; 128 | byteBuf.readBytes(timestampByte); 129 | for (byte i : timestampByte) { 130 | headData.add(i); 131 | } 132 | int ts = Common.byteToInt24(timestampByte); 133 | sendTimestamp = ts; 134 | if (timeMap.containsKey(csid)) { //如果存在那么 拿出之前的时间戳 135 | if (head_len < 12) { 136 | ts += timeMap.get(csid); 137 | } 138 | this.timestamp = ts; 139 | timeMap.put(csid, this.timestamp); 140 | } else { 141 | timestamp = ts; //如果不存在 csid 在列表中, 那么时间戳直接赋值就可以 142 | timeMap.put(csid, this.timestamp); 143 | } 144 | 145 | if (timestamp == Common.TIMESTAMP_MAX_NUM) { 146 | isExtendedTimestamp = true; // 前3个字节放不下,放在最后面的四个字节 147 | } 148 | chunkHeadIndex = chunkHeadIndex + Common.TIMESTAMP_BYTE_LENGTH; 149 | } 150 | if (head_len >= 8) { 151 | // 大于 4 先提取出 msgLength 152 | byte[] msg_len = new byte[Common.TIMESTAMP_BYTE_LENGTH]; 153 | byteBuf.readBytes(msg_len); 154 | this.msgLength = Common.byteToInt24(msg_len); 155 | this.allMsglength = Common.byteToInt24(msg_len); 156 | this.allMessageData = new byte[allMsglength]; 157 | this.csIdMap.put(this.csid, this.allMsglength); 158 | for (byte i : msg_len) { 159 | headData.add(i); 160 | } 161 | // 提取msgType 162 | byte[] msgTypeByte = new byte[1]; 163 | byteBuf.readBytes(msgTypeByte); 164 | this.msgType = msgTypeByte[0]; 165 | msyTypeMap.put(this.csid, this.msgType); 166 | for (byte i : msgTypeByte) { 167 | headData.add(i); 168 | } 169 | } 170 | 171 | if (head_len >= 12) { 172 | byte[] streamByte = new byte[Common.STREAM_ID_LENGTH]; 173 | byteBuf.readBytes(streamByte); 174 | System.out.println(Common.bytes2hex(streamByte)); 175 | this.streamId = Common.byteSmallToInt(streamByte); //只有 stream 是小端模式 176 | for (byte i : streamByte) { 177 | headData.add(i); 178 | } 179 | //System.out.println("streamId === " + streamId); 180 | } 181 | if (isExtendedTimestamp) { 182 | byte[] timestampByte = new byte[Common.EXTEND_TIMESTAMP_LENGTH]; 183 | byteBuf.readBytes(timestampByte); 184 | this.timestamp = Common.byteToInt24(timestampByte); 185 | for (byte i : timestampByte) { 186 | headData.add(i); 187 | } 188 | } 189 | } 190 | 191 | int msgIndex = msgLength > chunkLength ? chunkLength : msgLength; 192 | if (byteBuf.readableBytes() < msgIndex) { //如果不够,需要等到数据足够再读取 193 | // System.err.println("数据不足了"); 194 | lackMessage = true; 195 | return; 196 | } 197 | lackMessage = false; 198 | byte[] messageData = new byte[msgIndex]; //这里是其中一部分数据,分包需要重复提取到完整为止 199 | byteBuf.readBytes(messageData); 200 | int index = 0; 201 | for (int i = this.readMessageIndex; i < this.readMessageIndex + msgIndex; i++) { 202 | this.allMessageData[i] = messageData[index]; 203 | index++; 204 | } 205 | this.readMessageIndex += msgIndex; 206 | if (this.readMessageIndex < allMsglength) { //还没有提取完所有数据 207 | msgLength = allMsglength - this.readMessageIndex; 208 | } else { 209 | handMessage(this.allMessageData, ctx); 210 | this.allMessageData = null; 211 | this.msgLength = 0; 212 | this.readMessageIndex = 0; 213 | isExtendedTimestamp = false; 214 | } 215 | if (byteBuf.readableBytes() > 0) { 216 | handChunkMessage(ctx); 217 | } 218 | } catch (Exception e) { 219 | ctx.close(); 220 | e.printStackTrace(); 221 | System.err.println(Common.bytes2hex(Common.conversionByteArray(chunkData))); 222 | } 223 | } 224 | 225 | /** 226 | * 用户链接 227 | * 228 | * @param txid 229 | * @param ctx 230 | */ 231 | private void handConnect(double txid, ChannelHandlerContext ctx) { 232 | Map version = new HashMap(); 233 | double capabilities = 255.0; 234 | double mode = 1.0; 235 | version.put("fmsVer", "FMS/4,5,1,484"); 236 | version.put("capabilities", capabilities); 237 | version.put("mode", mode); 238 | byte[] versionByte = AMFUtil.writeObject(version); 239 | Map status = new HashMap(); 240 | double objectEncoding = 3.0; 241 | status.put("level", "status"); 242 | status.put("code", "NetConnection.Connect.Success"); 243 | status.put("description", "Connection succeeded."); 244 | status.put("objectEncoding", objectEncoding); 245 | byte[] statusVersion = AMFUtil.writeObject(status); 246 | handResult(txid, MsgType.MSG_CONTROL, versionByte, statusVersion, 0, ctx); 247 | } 248 | 249 | /** 250 | * @param amfClass 251 | * @param txid 252 | * @param ctx 253 | */ 254 | private void handPublish(AMFClass amfClass, double txid, ChannelHandlerContext ctx) { 255 | AMFUtil.load_amf(amfClass); 256 | String path = AMFUtil.load_amf_string(amfClass); //这个为发布的 url 协议 257 | System.out.println(path); 258 | ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(1024); 259 | byteBuf.writeBytes(AMFUtil.writeString("onStatus")); 260 | byteBuf.writeBytes(AMFUtil.writeNumber(0.0)); 261 | byteBuf.writeByte(AMFUtil.writeNull()); 262 | int length = byteBuf.readableBytes(); 263 | byte[] version = new byte[length]; 264 | byteBuf.readBytes(version); 265 | Map status = new HashMap(); 266 | status.put("level", "status"); 267 | status.put("code", "NetStream.Publish.Start"); 268 | status.put("description", "Stream is now published."); 269 | status.put("details", path); 270 | byte[] statusData = AMFUtil.writeObject(status); 271 | handData(MsgType.MSG_CONTROL, version, statusData, 0, ctx); 272 | 273 | handResult(txid, MsgType.MSG_CONTROL, new byte[]{AMFUtil.writeNull()}, new byte[]{AMFUtil.writeNull()}, 0, ctx); 274 | byteBuf.release(); 275 | } 276 | 277 | /** 278 | * @param amfClass 279 | * @param txid 280 | * @param ctx 281 | */ 282 | private void handPlay(AMFClass amfClass, double txid, ChannelHandlerContext ctx) { 283 | AMFUtil.load_amf(amfClass); 284 | String path = AMFUtil.load_amf_string(amfClass); //这个为发布的 url 协议 285 | this.path = path; 286 | isPlay = true; 287 | startPlayback(ctx, false); 288 | handResult(txid, MsgType.MSG_CONTROL, new byte[]{AMFUtil.writeNull()}, new byte[]{AMFUtil.writeNull()}, 0, ctx); 289 | } 290 | 291 | private void startPlayback(ChannelHandlerContext ctx, boolean isPause) { 292 | ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(1024); 293 | byteBuf.writeBytes(AMFUtil.writeString("onStatus")); 294 | byteBuf.writeBytes(AMFUtil.writeNumber(0.0)); 295 | byteBuf.writeByte(AMFUtil.writeNull()); 296 | int length = byteBuf.readableBytes(); 297 | byte[] version = new byte[length]; 298 | byteBuf.readBytes(version); 299 | 300 | Map status = new HashMap(); 301 | status.put("level", "status"); 302 | status.put("code", "NetStream.Play.Reset"); 303 | status.put("description", "Resetting and playing stream."); 304 | 305 | byte[] statusData = AMFUtil.writeObject(status); 306 | handData(MsgType.MSG_CONTROL, version, statusData, 1337, ctx); 307 | if (!isPause) { 308 | status = new HashMap(); 309 | status.put("level", "status"); 310 | status.put("code", "NetStream.Play.Start"); 311 | status.put("description", "Started playing."); 312 | statusData = AMFUtil.writeObject(status); 313 | 314 | byteBuf.writeBytes(AMFUtil.writeString("onStatus")); 315 | byteBuf.writeBytes(AMFUtil.writeNumber(0.0)); 316 | byteBuf.writeByte(AMFUtil.writeNull()); 317 | length = byteBuf.readableBytes(); 318 | version = new byte[length]; 319 | byteBuf.readBytes(version); 320 | handData(MsgType.MSG_CONTROL, version, statusData, 1337, ctx); 321 | 322 | System.out.println(path); 323 | Receive receive = new Receive(); 324 | receive.receive = ctx; 325 | receive.playing = true; 326 | receive.ready = false; 327 | ReceiveGroup.setChannel(path, receive); 328 | 329 | Publish publish = PublishGroup.getChannel(path); 330 | if (publish != null) { 331 | System.out.println("keyframe"); 332 | receive.keyframe = false; 333 | byteBuf.writeBytes(AMFUtil.writeString("onMetaData")); 334 | byteBuf.writeBytes(AMFUtil.writeMixedArray(publish.MetaData)); 335 | byte[] resultData = new byte[byteBuf.readableBytes()]; 336 | byteBuf.readBytes(resultData); 337 | sendData(resultData, MsgType.MSG_NOTIFY, 1337, ctx, 0); 338 | System.err.println("数据大小" + publish.chunk_size); 339 | // handSetChunkSize(ctx,chunkLength); 340 | // this.sendChunkLength = publish.chunk_size; 341 | } 342 | 343 | } 344 | byteBuf.release(); 345 | } 346 | 347 | /** 348 | * @param amfClass 349 | * @param txid 350 | * @param ctx 351 | */ 352 | private void handFCpublish(AMFClass amfClass, double txid, ChannelHandlerContext ctx) { 353 | AMFUtil.load_amf(amfClass); 354 | String path = AMFUtil.load_amf_string(amfClass); //这个为发布的 url 协议 355 | this.path = path; 356 | Publish client = new Publish(); 357 | client.path = path; 358 | client.publish = ctx; 359 | client.chunk_size = chunkLength; 360 | PublishGroup.setChannel(path, client); 361 | ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(1024); 362 | byteBuf.writeBytes(AMFUtil.writeString("onFCPublish")); 363 | byteBuf.writeBytes(AMFUtil.writeNumber(0.0)); 364 | byteBuf.writeByte(AMFUtil.writeNull()); 365 | int length = byteBuf.readableBytes(); 366 | byte[] version = new byte[length]; 367 | byteBuf.readBytes(version); 368 | 369 | Map status = new HashMap(); 370 | status.put("code", "NetStream.Publish.Start"); 371 | status.put("description", path); 372 | byte[] statusData = AMFUtil.writeObject(status); 373 | handData(MsgType.MSG_CONTROL, version, statusData, 0, ctx); 374 | 375 | handResult(txid, MsgType.MSG_CONTROL, new byte[]{AMFUtil.writeNull()}, new byte[]{AMFUtil.writeNull()}, 0, ctx); 376 | byteBuf.release(); 377 | } 378 | 379 | /** 380 | * 回复客户端设置 chunk 大小,OBS 如果没有回复此消息,不会继续推流 381 | * 382 | * @param ctx 383 | * @param chunkLength 384 | */ 385 | private void handSetChunkSize(ChannelHandlerContext ctx, double chunkLength) { 386 | ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(1024); 387 | byte[] data = AMFUtil.writeNumber(chunkLength); 388 | byteBuf.writeBytes(data); 389 | int length2 = byteBuf.readableBytes(); 390 | byte[] beginData2 = new byte[length2]; 391 | byteBuf.readBytes(beginData2); 392 | sendData(beginData2, MsgType.MSG_CHUNK_SIZE, 0, ctx, 0); 393 | } 394 | 395 | /** 396 | * 播放还是停止 397 | * 398 | * @param amfClass 399 | * @param txid 400 | * @param ctx 401 | */ 402 | private void handPause(AMFClass amfClass, double txid, ChannelHandlerContext ctx) { 403 | AMFUtil.load_amf(amfClass); /* NULL */ 404 | System.err.println("暂时或者开启服务"); 405 | Boolean paused = AMFUtil.load_amf_boolean(amfClass); 406 | List list = ReceiveGroup.getChannel(this.path); 407 | int listIndex = 0; 408 | for (Receive receive : list) { 409 | if (receive.receive == ctx) { 410 | if (receive.playing) { 411 | System.err.println("没有播放"); 412 | receive.playing = false; 413 | } else { 414 | System.err.println("开始播放"); 415 | receive.playing = true; 416 | } 417 | list.set(listIndex, receive); 418 | } 419 | listIndex++; 420 | } 421 | if (paused) { 422 | System.err.println("pausing\n"); 423 | ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(1024); 424 | byteBuf.writeBytes(AMFUtil.writeString("onStatus")); 425 | byteBuf.writeBytes(AMFUtil.writeNumber(0.0)); 426 | byteBuf.writeByte(AMFUtil.writeNull()); 427 | byte[] version = new byte[byteBuf.readableBytes()]; 428 | byteBuf.readBytes(version); 429 | Map status = new HashMap(); 430 | status.put("level", "status"); 431 | status.put("code", "NetStream.Pause.Notify"); 432 | status.put("description", "Pausing."); 433 | byte[] statusData = AMFUtil.writeObject(status); 434 | handResult(txid, MsgType.MSG_CONTROL, version, statusData, 0, ctx); 435 | byteBuf.release(); 436 | // client->playing = false; 437 | } else { 438 | System.out.println("进来了"); 439 | startPlayback(ctx, true); 440 | } 441 | handResult(txid, MsgType.MSG_CONTROL, new byte[]{AMFUtil.writeNull()}, new byte[]{AMFUtil.writeNull()}, 0, ctx); 442 | } 443 | 444 | /** 445 | * 处理message 消息 446 | * 447 | * @param message 448 | * @param ctx 449 | */ 450 | private void handMessage(byte[] message, ChannelHandlerContext ctx) { 451 | AMFClass amfClass = new AMFClass(); 452 | amfClass.message = message; 453 | amfClass.pos = 0; 454 | int index = 0; 455 | byte[] number; 456 | byte msgType = msyTypeMap.get(this.csid); 457 | switch (msgType) { 458 | case 0x01: 459 | System.err.println("msg_set_chunk"); 460 | if (amfClass.pos + 4 > amfClass.message.length) { 461 | System.err.println("数据不足"); 462 | } 463 | System.err.println(Common.bytes2hex(amfClass.message)); 464 | number = new byte[4]; 465 | index = 0; 466 | for (int i = amfClass.pos; i < amfClass.pos + 4; i++) { 467 | number[index] = amfClass.message[i]; 468 | index++; 469 | } 470 | chunkLength = Common.byteToInt(number); 471 | handSetChunkSize(ctx, Common.DEFAULT_CHUNK_MESSAGE_LENGTH); 472 | case 0x03: 473 | System.err.println("read byte"); 474 | if (amfClass.pos + 4 > amfClass.message.length) { 475 | System.out.println("数据不足"); 476 | } 477 | System.out.println(Common.bytes2hex(amfClass.message)); 478 | byte[] number2 = new byte[4]; 479 | int index2 = 0; 480 | for (int i = amfClass.pos; i < amfClass.pos + 4; i++) { 481 | number2[index2] = amfClass.message[i]; 482 | index2++; 483 | } 484 | int len = Common.byteToInt(number2); 485 | System.err.println("len" + len); 486 | case 0x05: 487 | // System.err.println("server window"); 488 | if (amfClass.pos + 4 > amfClass.message.length) { 489 | System.err.println("数据不足"); 490 | } 491 | // System.err.println(Common.bytes2hex(amfClass.message)); 492 | number = new byte[4]; 493 | index = 0; 494 | for (int i = amfClass.pos; i < amfClass.pos + 4; i++) { 495 | number[index] = amfClass.message[i]; 496 | index++; 497 | } 498 | int windowSize = Common.byteToInt(number); 499 | System.err.println("window size" + windowSize); 500 | break; 501 | case 0x14: 502 | System.err.println("消息控制服务"); 503 | String msg = AMFUtil.load_amf_string(amfClass); 504 | double txid = AMFUtil.load_amf_number(amfClass); 505 | System.err.println("======="); 506 | System.err.println(msg); 507 | System.err.println(txid); 508 | System.err.println("========"); 509 | 510 | if (this.streamId == Common.CONTROL_ID) { 511 | if (msg.equals("connect")) { 512 | Map data = AMFUtil.load_amf_object(amfClass); 513 | if (data.containsKey("app")) { 514 | System.out.println(data.toString()); 515 | String app = data.get("app").toString(); 516 | if (app.equals(Common.APP_NAME)) { 517 | System.out.println(this.streamId); 518 | //amfClass.pos = 0x02; 519 | ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(1024); 520 | byte[] resultString = AMFUtil.writeString("Stream Begin"); 521 | // byte[] resultString = AMFUtil.writeNumber(0); // vlc 522 | // byte[] resultString = Common.writeUnsignedInt16(0); 523 | byteBuf.writeBytes(resultString); 524 | byteBuf.writeBytes(Common.intToByte(0)); 525 | int length = byteBuf.readableBytes(); 526 | byte[] beginData = new byte[length]; 527 | byteBuf.readBytes(beginData); 528 | sendData(beginData, MsgType.MSG_USER_CONTROL, 0, ctx, 0); 529 | handConnect(txid, ctx); 530 | } 531 | } 532 | } else if (msg.equals("createStream")) { 533 | byte[] status = {AMFUtil.writeNull()}; 534 | byte[] version = AMFUtil.writeNumber(Common.STREAM_ID); 535 | handResult(txid, MsgType.MSG_CONTROL, status, version, 0, ctx); 536 | } else if (msg.equals("FCPublish")) { 537 | handFCpublish(amfClass, txid, ctx); 538 | } 539 | } 540 | List list2; 541 | int listIndex = 0; 542 | if (this.streamId == Common.STREAM_ID) { 543 | if (msg.equals("publish")) { 544 | handPublish(amfClass, txid, ctx); 545 | } else if (msg.equals("play")) { 546 | System.err.println("播放罗"); 547 | handPlay(amfClass, txid, ctx); 548 | } else if (msg.equals("pause")) { 549 | System.err.println("这里执行了几次"); 550 | handPause(amfClass, txid, ctx); 551 | } 552 | } 553 | break; 554 | case 0x12: //设置一些元信息 555 | String command = AMFUtil.load_amf_string(amfClass); 556 | System.err.println(command); 557 | if (command.equals("@setDataFrame")) { 558 | String type = AMFUtil.load_amf_string(amfClass); 559 | System.out.println(type); 560 | Map data = AMFUtil.load_amf_mixedArray(amfClass); 561 | Publish publish = PublishGroup.getChannel(this.path); 562 | if (publish != null) { 563 | publish.MetaData = data; 564 | } 565 | // this.MetaData = data; 566 | } 567 | case 0x08: 568 | // System.err.println("音频" + message.length); 569 | audioStreamDecoder.decode(message); 570 | List list = ReceiveGroup.getChannel(this.path); 571 | if (list != null) { 572 | for (Receive receive : list) { 573 | if (receive.ready && receive.keyframe) { 574 | if (!receive.playing) { 575 | continue; 576 | } 577 | int timestamp = timeMap.get(this.csid); 578 | System.out.println("audio发送数据 ===" + message.length +" 时间戳" + timestamp); 579 | sendData2(message, MsgType.MSG_AUDIO, 1337, receive.receive, timestamp); 580 | } 581 | } 582 | } 583 | break; 584 | case 0x09: 585 | // System.err.println("视频" + message.length); 586 | // Common.appendMethodA("D:\\test.h264",message); 587 | new VideoStreamDecoder(message); 588 | byte flags = message[0]; 589 | // System.out.println("======================="); 590 | // System.out.println(Common.bytes2hex(message)); 591 | // System.out.println("======================="); 592 | Publish publish = PublishGroup.getChannel(path); 593 | if (!publish.keyFrame) { 594 | publish.keyFrame = true; 595 | publish.keyFrameMessage = message; 596 | } 597 | List listVideo = ReceiveGroup.getChannel(this.path); 598 | if (listVideo != null) { 599 | for (Receive receive : listVideo) { 600 | if (!receive.playing) { 601 | continue; 602 | } 603 | if (receive != null && receive.playing) { 604 | if (flags >> 4 == FLV_KEY_FRAME && !receive.ready) { 605 | System.out.println("flags ====== " + flags); 606 | ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(1024); 607 | byteBuf.writeByte(0x00); 608 | byteBuf.writeByte(0x00); 609 | byteBuf.writeBytes(Common.intToByte(1337)); 610 | byte[] control = new byte[byteBuf.readableBytes()]; 611 | byteBuf.readBytes(control); 612 | sendData(control, MsgType.MSG_USER_CONTROL, 0, receive.receive, 0); 613 | receive.ready = true; 614 | byteBuf.release(); 615 | } 616 | if (!receive.keyframe) { 617 | System.out.println("关键进来了"); 618 | receive.keyframe = true; 619 | sendData2(publish.keyFrameMessage, MsgType.MSG_VIDEO, 1337, receive.receive, timestamp); 620 | } 621 | if (receive.ready && receive.keyframe) { 622 | int timestamp = timeMap.get(this.csid); 623 | //System.out.println("时间戳" + timestamp); 624 | System.out.println("video发送数据 ===" + message.length +" 时间戳" + timestamp); 625 | sendData2(message, MsgType.MSG_VIDEO, 1337, receive.receive, timestamp); 626 | } 627 | } 628 | } 629 | } 630 | break; 631 | default: 632 | System.err.println(this.msgType); 633 | // System.err.println(Common.bytes2hex(message)); 634 | System.out.println("有其他东西过来了"); 635 | break; 636 | } 637 | } 638 | 639 | /** 640 | * 统一数据返回格式 641 | * 642 | * @param msgType 643 | * @param version 644 | * @param status 645 | * @param streamId 646 | * @param ctx 647 | */ 648 | private void handData(byte msgType, byte[] version, byte[] status, int streamId, ChannelHandlerContext ctx) { 649 | ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(1024); 650 | byteBuf.writeBytes(version); 651 | byteBuf.writeBytes(status); 652 | int length = byteBuf.readableBytes(); 653 | byte[] data = new byte[length]; 654 | byteBuf.readBytes(data); 655 | sendData(data, msgType, streamId, ctx, 0); 656 | byteBuf.release(); 657 | } 658 | 659 | /** 660 | * 统一 _result 返回 661 | * 662 | * @param txid 663 | * @param msgType 664 | * @param version 665 | * @param status 666 | * @param streamId 667 | * @param ctx 668 | */ 669 | private void handResult(double txid, byte msgType, byte[] version, byte[] status, int streamId, ChannelHandlerContext ctx) { 670 | ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(1024); 671 | byte[] resultString = AMFUtil.writeString("_result"); 672 | byteBuf.writeBytes(resultString); 673 | byte[] resultNumber = AMFUtil.writeNumber(txid); 674 | byteBuf.writeBytes(resultNumber); 675 | byteBuf.writeBytes(version); 676 | byteBuf.writeBytes(status); 677 | int length = byteBuf.readableBytes(); 678 | byte[] data = new byte[length]; 679 | byteBuf.readBytes(data); 680 | sendData(data, msgType, streamId, ctx, 0); 681 | byteBuf.release(); 682 | } 683 | 684 | 685 | /** 686 | * 同意数据发送 687 | * 688 | * @param chunkData 689 | * @param msgType 690 | * @param streamId 691 | * @param ctx 692 | */ 693 | private void sendData(byte[] chunkData, byte msgType, int streamId, ChannelHandlerContext ctx, int timestamp) { 694 | ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(1024); 695 | byte flags; 696 | if (streamId == Common.STREAM_ID) { 697 | flags = (4 & 0x3f) | (0 << 6); 698 | } else { 699 | flags = (3 & 0x3f) | (0 << 6); 700 | } 701 | // byte[] timestamp = {0x00,0x00,0x00}; 702 | int msg_len = chunkData.length; 703 | byte[] msgLength = Common.reverseArray(Common.intToByte24(msg_len)); 704 | byte[] streamData = Common.intToByte(streamId); 705 | byteBuf.writeByte(flags); 706 | byteBuf.writeBytes(Common.reverseArray(Common.intToByte24(timestamp))); 707 | byteBuf.writeBytes(msgLength); 708 | byteBuf.writeByte(msgType); 709 | byteBuf.writeBytes(streamData); 710 | int pos = 0; 711 | while (pos < chunkData.length) { 712 | if (byteBuf.writableBytes() < chunkData.length) { 713 | byteBuf.ensureWritable(chunkData.length); 714 | } 715 | if (chunkData.length - pos < chunkLength) { 716 | for (int i = pos; i < chunkData.length; i++) { 717 | byteBuf.writeByte(chunkData[i]); 718 | } 719 | } else { 720 | if (byteBuf.writableBytes() < pos + chunkLength) { 721 | byteBuf.ensureWritable(pos + chunkLength); 722 | } 723 | for (int i = pos; i < pos + chunkLength; i++) { 724 | byteBuf.writeByte(chunkData[i]); 725 | } 726 | if (streamId == Common.STREAM_ID) { 727 | byteBuf.writeByte((byte) ((4 & 0x3f) | (3 << 6))); 728 | } else { 729 | byteBuf.writeByte((byte) ((3 & 0x3f) | (3 << 6))); 730 | } 731 | } 732 | pos += chunkLength; 733 | } 734 | int sendLength = byteBuf.readableBytes(); 735 | byte[] sendData = new byte[sendLength]; 736 | byteBuf.readBytes(sendData); 737 | ctx.writeAndFlush(Unpooled.copiedBuffer(sendData)); 738 | byteBuf.release(); 739 | } 740 | 741 | /** 742 | * 同意数据发送 743 | * 744 | * @param chunkData 745 | * @param msgType 746 | * @param streamId 747 | * @param ctx 748 | */ 749 | private void sendData2(byte[] chunkData, byte msgType, int streamId, ChannelHandlerContext ctx, int timestamp) { 750 | byte flags; 751 | if (streamId == Common.STREAM_ID) { 752 | flags = (4 & 0x3f) | (0 << 6); 753 | } else { 754 | flags = (3 & 0x3f) | (0 << 6); 755 | } 756 | // byte[] timestamp = {0x00,0x00,0x00}; 757 | int msg_len = chunkData.length; 758 | byte[] msgLength = Common.reverseArray(Common.intToByte24(msg_len)); 759 | byte[] streamData = Common.intToByte(streamId); 760 | streamByteBuf.writeByte(flags); 761 | // System.out.println(Common.bytes2hex(Common.reverseArray(Common.intToByte24(timestamp)))); 762 | streamByteBuf.writeBytes(Common.reverseArray(Common.intToByte24(timestamp))); 763 | streamByteBuf.writeBytes(msgLength); 764 | streamByteBuf.writeByte(msgType); 765 | streamByteBuf.writeBytes(streamData); 766 | int pos = 0; 767 | 768 | while (pos < chunkData.length) { 769 | if (streamByteBuf.writableBytes() < chunkData.length) { 770 | streamByteBuf.ensureWritable(chunkData.length); 771 | } 772 | if (chunkData.length - pos < sendChunkLength) { 773 | for (int i = pos; i < chunkData.length; i++) { 774 | streamByteBuf.writeByte(chunkData[i]); 775 | } 776 | } else { 777 | if (streamByteBuf.writableBytes() < pos + sendChunkLength) { 778 | streamByteBuf.ensureWritable(pos + sendChunkLength); 779 | } 780 | for (int i = pos; i < pos + sendChunkLength; i++) { 781 | streamByteBuf.writeByte(chunkData[i]); 782 | } 783 | if (pos + sendChunkLength != chunkData.length) { 784 | if (streamId == Common.STREAM_ID) { 785 | streamByteBuf.writeByte((byte) ((4 & 0x3f) | (3 << 6))); 786 | } else { 787 | streamByteBuf.writeByte((byte) ((3 & 0x3f) | (3 << 6))); 788 | } 789 | } 790 | } 791 | pos += sendChunkLength; 792 | } 793 | sendClient(ctx); 794 | } 795 | 796 | private void sendClient(ChannelHandlerContext ctx) { 797 | if (streamByteBuf.readableBytes() > 0) { 798 | int length = streamByteBuf.readableBytes(); 799 | //if(length > 4096) length = 4096; 800 | byte[] sendData = new byte[length]; 801 | streamByteBuf.readBytes(sendData); 802 | ctx.fireChannelRead(sendData); 803 | } 804 | } 805 | 806 | 807 | /** 808 | * 根据csidTS 获取 basic head 长度 809 | * 810 | * @param csidTs 811 | * @return 812 | */ 813 | private int getBasicHeadLength(int csidTs) { 814 | if (csidTs == 0) { 815 | return 2; 816 | } 817 | if (csidTs == 0x3f) { 818 | return 3; 819 | } 820 | return 1; 821 | } 822 | 823 | 824 | @Override 825 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 826 | System.err.println("错误信息"); 827 | System.out.println(cause.getMessage()); 828 | List listVideo = ReceiveGroup.getChannel(this.path); 829 | List newListVideo = new ArrayList(); 830 | for (int i = 0; i < listVideo.size(); i++) { 831 | Receive receive = listVideo.get(i); 832 | if (receive.receive != ctx) { 833 | newListVideo.add(receive); 834 | } 835 | } 836 | ReceiveGroup.setChannel(this.path, newListVideo); 837 | super.exceptionCaught(ctx, cause); 838 | } 839 | 840 | } -------------------------------------------------------------------------------- /src/main/java/Decoder/VideoStreamDecoder.java: -------------------------------------------------------------------------------- 1 | package Decoder; 2 | 3 | import Util.Common; 4 | import org.omg.PortableInterceptor.SYSTEM_EXCEPTION; 5 | import sun.rmi.runtime.Log; 6 | 7 | public class VideoStreamDecoder { 8 | //协议按照编写从头到尾排列 9 | private byte streamType; // 1byte 10 | private byte avPacketType; // 1byte 11 | private byte[] compositionTime; // 3byte 全为0 无意义 12 | private byte configurationVersion; //1byte 版本 13 | private byte AVCProfileIndication; //1byte 0x4d sps[1] 14 | private byte profile_compatibility;//1byte 0x00 sps[2] 15 | private byte AVCLevelIndication; // 1byte 0x2a sps[3] 16 | private byte lengthSizeMinusOne; // FLV中NALU包长数据所使用的字节数,包长= (lengthSizeMinusOne & 3) + 1 17 | private byte numOfSequenceParameterSets; // SPS个数,通常为0xe1 个数= numOfSequenceParameterSets & 01F 18 | private byte[] spsLength; //2 byte sps长度 19 | private byte[] spsData;//sps内容 sequenceParameterSetNALUnits 20 | private byte numOfPictureParameterSets; //pps个数 21 | private byte[] ppsLength; //2 byte pps长度 22 | private byte[] ppsData; // pps 内容 pictureParameterSetNALUnits 23 | private byte[] message; 24 | private int index = 0; //程序计算的下标 25 | public VideoStreamDecoder(byte[] data) { 26 | this.message = data; 27 | this.streamType = this.message[index++]; 28 | this.avPacketType = data[index++]; 29 | this.compositionTime = new byte[]{this.message[index++],this.message[index++],this.message[index++]}; 30 | if(this.streamType == 0x17) { 31 | // System.out.println("关键帧"); 32 | } 33 | if(this.avPacketType == 0x01) { 34 | H264NALUDecoder(); 35 | } else if(this.avPacketType == 0x00) { 36 | AvcSequenceHeaderDecoder(); 37 | } 38 | } 39 | private void AvcSequenceHeaderDecoder() { 40 | 41 | this.configurationVersion = this.message[index++]; 42 | this.AVCProfileIndication = this.message[index++]; 43 | this.profile_compatibility = this.message[index++]; 44 | this.AVCLevelIndication = this.message[index++]; 45 | this.lengthSizeMinusOne = this.message[index++]; 46 | 47 | this.numOfSequenceParameterSets = this.message[index++]; 48 | this.spsLength = new byte[]{this.message[index++],this.message[index++]}; 49 | int spsLength = Common.byteBigToInt16(this.spsLength); 50 | this.spsData = new byte[spsLength]; 51 | if(spsLength > this.message.length - index){ 52 | System.out.println("sps数据长度有问题"); 53 | return; 54 | } 55 | for(int i = 0;i < spsLength;i++) { 56 | this.spsData[i] = this.message[index++]; 57 | } 58 | this.numOfPictureParameterSets = this.message[index++]; 59 | this.ppsLength = new byte[]{this.message[index++],this.message[index++]}; 60 | int ppsLength = Common.byteBigToInt16(this.ppsLength); 61 | this.ppsData = new byte[ppsLength]; 62 | if(ppsLength > this.message.length - index){ 63 | // System.out.println("pps数据长度有问题"); 64 | return; 65 | } 66 | for(int i = 0;i < ppsLength;i++) { 67 | this.ppsData[i] = this.message[index++]; 68 | } 69 | // System.out.println("pps length" + ppsLength +" message length " + message.length + " index " + index); 70 | // 71 | Common.appendMethodA("D:\\test2.h264",new byte[]{0x00,0x00,0x00,0x01}); 72 | Common.appendMethodA("D:\\test2.h264",spsData); 73 | Common.appendMethodA("D:\\test2.h264",new byte[]{0x00,0x00,0x01}); 74 | Common.appendMethodA("D:\\test2.h264",ppsData); 75 | } 76 | 77 | private void H264NALUDecoder() { 78 | while (index <= this.message.length - 1){ 79 | byte[] naluLength = new byte[]{this.message[index++],this.message[index++],this.message[index++],this.message[index++]}; 80 | int len = Common.byteToInt(naluLength); 81 | byte[] nalu = new byte[len]; 82 | for(int i = 0;i < len;i++) { 83 | nalu[i] = this.message[index++]; 84 | } 85 | Common.appendMethodA("D:\\test2.h264",new byte[]{0x00,0x00,0x01}); 86 | // Common.appendMethodA("D:\\test2.h264",naluLength); 87 | 88 | Common.appendMethodA("D:\\test2.h264",nalu); 89 | } 90 | } 91 | 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/EnCoder/RtmpEncoder.java: -------------------------------------------------------------------------------- 1 | package EnCoder; 2 | 3 | import Util.Common; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.ByteBufOutputStream; 6 | import io.netty.handler.codec.MessageToByteEncoder; 7 | import javafx.scene.SubScene; 8 | import org.jboss.netty.channel.Channel; 9 | import org.jboss.netty.channel.ChannelHandlerContext; 10 | import org.jboss.netty.handler.codec.oneone.OneToOneEncoder; 11 | 12 | import java.io.ByteArrayOutputStream; 13 | import java.io.ObjectOutputStream; 14 | 15 | public class RtmpEncoder extends OneToOneEncoder { 16 | 17 | protected Object encode(ChannelHandlerContext channelHandlerContext, Channel channel, Object o) throws Exception { 18 | System.out.println("进来了"); 19 | return null; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/Handler/RtmpHandler.java: -------------------------------------------------------------------------------- 1 | package Handler; 2 | 3 | import Util.Common; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.Unpooled; 6 | import io.netty.channel.*; 7 | import org.omg.PortableInterceptor.SYSTEM_EXCEPTION; 8 | 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.ObjectOutputStream; 11 | import java.net.SocketAddress; 12 | 13 | public class RtmpHandler extends ChannelInboundHandlerAdapter { 14 | @Override 15 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 16 | super.channelRead(ctx, msg); 17 | byte[] data = (byte[]) msg; 18 | ctx.writeAndFlush(Unpooled.copiedBuffer(data)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: Rtmp 3 | 4 | -------------------------------------------------------------------------------- /src/main/java/Rtmp.java: -------------------------------------------------------------------------------- 1 | import Decoder.RtmpDecoder; 2 | import Decoder.RtmpDecoderbak; 3 | import EnCoder.RtmpEncoder; 4 | import Handler.RtmpHandler; 5 | import io.netty.bootstrap.ServerBootstrap; 6 | import io.netty.buffer.ByteBuf; 7 | import io.netty.channel.*; 8 | import io.netty.channel.nio.NioEventLoopGroup; 9 | import io.netty.channel.socket.SocketChannel; 10 | import io.netty.channel.socket.nio.NioServerSocketChannel; 11 | import io.netty.handler.codec.ByteToMessageDecoder; 12 | import io.netty.handler.codec.http.HttpObjectAggregator; 13 | import io.netty.handler.codec.http.HttpServerCodec; 14 | import io.netty.handler.codec.serialization.ObjectEncoder; 15 | import io.netty.handler.stream.ChunkedWriteHandler; 16 | 17 | import java.util.List; 18 | 19 | public class Rtmp { 20 | public static void main(String[] args) { 21 | System.out.println("启动rtmp server"); 22 | start(9999); 23 | } 24 | 25 | public static void start(int port){ 26 | 27 | 28 | NioEventLoopGroup boosGroup = new NioEventLoopGroup(); 29 | NioEventLoopGroup workGroup = new NioEventLoopGroup(); 30 | try { 31 | ServerBootstrap serverBootstrap = new ServerBootstrap(); 32 | serverBootstrap.group(boosGroup,workGroup). 33 | channel(NioServerSocketChannel.class). 34 | childHandler(new ChannelInitializer() { 35 | @Override 36 | protected void initChannel(SocketChannel socketChannel) throws Exception { 37 | socketChannel.pipeline().addLast(new RtmpDecoder()); 38 | socketChannel.pipeline().addLast(new RtmpHandler()); 39 | } 40 | }) 41 | .option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(131070)); 42 | Channel ch = serverBootstrap.bind(port).sync().channel(); 43 | 44 | ch.closeFuture().sync(); 45 | } catch (InterruptedException e) { 46 | e.printStackTrace(); 47 | } finally { 48 | boosGroup.shutdownGracefully(); 49 | workGroup.shutdownGracefully(); 50 | } 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/Rtmp/Amf.java: -------------------------------------------------------------------------------- 1 | package Rtmp; 2 | 3 | import AMF.AMFClass; 4 | 5 | public class Amf { 6 | protected AMFClass amfClass; 7 | public void setAmfClass(byte[] messageData) { 8 | amfClass = new AMFClass(); 9 | amfClass.message = messageData; 10 | amfClass.pos = 0; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/Rtmp/RtmpAudio.java: -------------------------------------------------------------------------------- 1 | package Rtmp; 2 | 3 | import Decoder.AudioStreamDecoder; 4 | import User.Receive; 5 | import User.ReceiveGroup; 6 | import Util.MsgType; 7 | 8 | import java.util.List; 9 | 10 | public class RtmpAudio extends Amf{ 11 | 12 | public void setAudioData(byte[] message,String path,int timestamp,AudioStreamDecoder audioStreamDecoder) { 13 | // audioStreamDecoder.decode(message); 14 | 15 | List list = ReceiveGroup.getChannel(path); 16 | if (list != null) { 17 | for (Receive receive : list) { 18 | if (receive.ready && receive.keyframe) { 19 | if (!receive.playing) { 20 | continue; 21 | } 22 | RtmpResponse.sendData2(message, MsgType.MSG_AUDIO, 1337, receive.receive, timestamp); 23 | } 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/Rtmp/RtmpBytesRead.java: -------------------------------------------------------------------------------- 1 | package Rtmp; 2 | 3 | import Util.Common; 4 | 5 | public class RtmpBytesRead extends Amf{ 6 | public void setBytesRead(byte[] message) { 7 | System.out.println("bytes read"); 8 | setAmfClass(message); 9 | if (amfClass.pos + 4 > amfClass.message.length) { 10 | System.out.println("数据不足"); 11 | } 12 | System.out.println(Common.bytes2hex(amfClass.message)); 13 | byte[] number2 = new byte[4]; 14 | int index2 = 0; 15 | for (int i = amfClass.pos; i < amfClass.pos + 4; i++) { 16 | number2[index2] = amfClass.message[i]; 17 | index2++; 18 | } 19 | int len = Common.byteToInt(number2); 20 | System.err.println("len" + len); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/Rtmp/RtmpChunk.java: -------------------------------------------------------------------------------- 1 | package Rtmp; 2 | 3 | public class RtmpChunk { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/Rtmp/RtmpChunkSize.java: -------------------------------------------------------------------------------- 1 | package Rtmp; 2 | 3 | import AMF.AMFClass; 4 | import AMF.AMFUtil; 5 | import Util.Common; 6 | import Util.MsgType; 7 | import io.netty.buffer.ByteBuf; 8 | import io.netty.buffer.ByteBufAllocator; 9 | import io.netty.channel.ChannelHandlerContext; 10 | import lombok.Getter; 11 | import lombok.Setter; 12 | 13 | import java.util.Arrays; 14 | 15 | public class RtmpChunkSize extends Amf { 16 | 17 | 18 | @Setter 19 | @Getter 20 | private int chunkSize; 21 | 22 | public void setChunSize(byte[] messageData) { 23 | setAmfClass(messageData); 24 | if (amfClass.pos + 4 > amfClass.message.length) { 25 | System.err.println("数据不足"); 26 | return; 27 | } 28 | chunkSize = Common.byteToInt(Arrays.copyOfRange(amfClass.message, amfClass.pos, amfClass.pos + 4)); 29 | } 30 | 31 | // 服务器 告诉客户端,自己是使用 多大的 chunk 大小 32 | public void responseChunkSize(ChannelHandlerContext ctx,int chunkLength) { 33 | ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(1024); 34 | byte[] data = AMFUtil.writeNumber(chunkLength); 35 | byteBuf.writeBytes(data); 36 | int length2 = byteBuf.readableBytes(); 37 | byte[] beginData2 = new byte[length2]; 38 | byteBuf.readBytes(beginData2); 39 | RtmpResponse.sendData(beginData2, MsgType.MSG_CHUNK_SIZE, 0, ctx, 0,chunkSize); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/Rtmp/RtmpControl.java: -------------------------------------------------------------------------------- 1 | package Rtmp; 2 | 3 | import AMF.AMFClass; 4 | import AMF.AMFUtil; 5 | import User.Publish; 6 | import User.PublishGroup; 7 | import User.Receive; 8 | import User.ReceiveGroup; 9 | import Util.Common; 10 | import Util.MsgType; 11 | import io.netty.buffer.ByteBuf; 12 | import io.netty.buffer.ByteBufAllocator; 13 | import io.netty.channel.ChannelHandlerContext; 14 | import lombok.Getter; 15 | import lombok.Setter; 16 | 17 | import java.util.HashMap; 18 | import java.util.List; 19 | import java.util.Map; 20 | 21 | public class RtmpControl extends Amf { 22 | 23 | @Getter 24 | @Setter 25 | private String msg; 26 | @Getter 27 | @Setter 28 | private double txid; 29 | 30 | private ChannelHandlerContext ctx; 31 | 32 | @Setter 33 | @Getter 34 | private String path; 35 | 36 | private boolean isPlay; 37 | 38 | private int chunkLength = Common.DEFAULT_CHUNK_MESSAGE_LENGTH; 39 | 40 | public void setControl(byte[] messageData, ChannelHandlerContext ctx,int chunkLength) { 41 | this.chunkLength = chunkLength; 42 | setAmfClass(messageData); 43 | this.ctx = ctx; 44 | msg = AMFUtil.load_amf_string(amfClass); 45 | txid = AMFUtil.load_amf_number(amfClass); 46 | System.out.println(msg); 47 | if(msg.equals("connect")) { 48 | handlerConnect(); 49 | } else if (msg.equals("createStream")) { 50 | byte[] status = {AMFUtil.writeNull()}; 51 | byte[] version = AMFUtil.writeNumber(Common.STREAM_ID); 52 | handResult(txid, MsgType.MSG_CONTROL, status, version, 0, ctx); 53 | } else if (msg.equals("FCPublish")) { 54 | handFCpublish(amfClass, txid, ctx); 55 | } 56 | 57 | if (msg.equals("publish")) { 58 | handPublish(amfClass, txid, ctx); 59 | } else if (msg.equals("play")) { 60 | System.err.println("播放罗"); 61 | handPlay(amfClass, txid, ctx); 62 | } else if (msg.equals("pause")) { 63 | System.err.println("这里执行了几次"); 64 | handPause(amfClass, txid, ctx); 65 | } 66 | } 67 | 68 | 69 | private void handlerConnect() { 70 | Map data = AMFUtil.load_amf_object(amfClass); 71 | if (data.containsKey("app")) { 72 | System.out.println(data.toString()); 73 | String app = data.get("app").toString(); 74 | if (app.equals(Common.APP_NAME)) { 75 | ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(1024); 76 | // byte[] resultString = AMFUtil.writeString("Stream Begin"); 77 | // byte[] resultString = AMFUtil.writeNumber(0); // vlc 错误的 78 | byte[] resultString = Common.writeUnsignedInt16(0); 79 | byteBuf.writeBytes(resultString); 80 | byteBuf.writeBytes(Common.intToByte(0)); 81 | int length = byteBuf.readableBytes(); 82 | byte[] beginData = new byte[length]; 83 | byteBuf.readBytes(beginData); 84 | RtmpResponse.sendData(beginData, MsgType.MSG_USER_CONTROL, 0, ctx, 0,chunkLength); 85 | handConnect(txid, ctx); 86 | } 87 | } 88 | } 89 | 90 | 91 | /** 92 | * 用户链接 93 | * 94 | * @param txid 95 | * @param ctx 96 | */ 97 | private void handConnect(double txid, ChannelHandlerContext ctx) { 98 | Map version = new HashMap(); 99 | double capabilities = 255.0; 100 | double mode = 1.0; 101 | version.put("fmsVer", "FMS/4,5,1,484"); 102 | version.put("capabilities", capabilities); 103 | version.put("mode", mode); 104 | byte[] versionByte = AMFUtil.writeObject(version); 105 | Map status = new HashMap(); 106 | double objectEncoding = 3.0; 107 | status.put("level", "status"); 108 | status.put("code", "NetConnection.Connect.Success"); 109 | status.put("description", "Connection succeeded."); 110 | status.put("objectEncoding", objectEncoding); 111 | byte[] statusVersion = AMFUtil.writeObject(status); 112 | handResult(txid, MsgType.MSG_CONTROL, versionByte, statusVersion, 0, ctx); 113 | } 114 | 115 | 116 | /** 117 | * @param amfClass 118 | * @param txid 119 | * @param ctx 120 | */ 121 | private void handFCpublish(AMFClass amfClass, double txid, ChannelHandlerContext ctx) { 122 | AMFUtil.load_amf(amfClass); 123 | String path = AMFUtil.load_amf_string(amfClass); //这个为发布的 url 协议 124 | this.path = path; 125 | Publish client = new Publish(); 126 | client.path = path; 127 | client.publish = ctx; 128 | client.chunk_size = chunkLength; //客户端上来的 chunkLength 大小 129 | PublishGroup.setChannel(path, client); 130 | ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(1024); 131 | byteBuf.writeBytes(AMFUtil.writeString("onFCPublish")); 132 | byteBuf.writeBytes(AMFUtil.writeNumber(0.0)); 133 | byteBuf.writeByte(AMFUtil.writeNull()); 134 | int length = byteBuf.readableBytes(); 135 | byte[] version = new byte[length]; 136 | byteBuf.readBytes(version); 137 | 138 | Map status = new HashMap(); 139 | status.put("code", "NetStream.Publish.Start"); 140 | status.put("description", path); 141 | byte[] statusData = AMFUtil.writeObject(status); 142 | handData(MsgType.MSG_CONTROL, version, statusData, 0, ctx); 143 | 144 | handResult(txid, MsgType.MSG_CONTROL, new byte[]{AMFUtil.writeNull()}, new byte[]{AMFUtil.writeNull()}, 0, ctx); 145 | byteBuf.release(); 146 | } 147 | 148 | 149 | /** 150 | * @param amfClass 151 | * @param txid 152 | * @param ctx 153 | */ 154 | private void handPublish(AMFClass amfClass, double txid, ChannelHandlerContext ctx) { 155 | AMFUtil.load_amf(amfClass); 156 | String path = AMFUtil.load_amf_string(amfClass); //这个为发布的 url 协议 157 | System.out.println(path); 158 | ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(1024); 159 | byteBuf.writeBytes(AMFUtil.writeString("onStatus")); 160 | byteBuf.writeBytes(AMFUtil.writeNumber(0.0)); 161 | byteBuf.writeByte(AMFUtil.writeNull()); 162 | int length = byteBuf.readableBytes(); 163 | byte[] version = new byte[length]; 164 | byteBuf.readBytes(version); 165 | Map status = new HashMap(); 166 | status.put("level", "status"); 167 | status.put("code", "NetStream.Publish.Start"); 168 | status.put("description", "Stream is now published."); 169 | status.put("details", path); 170 | byte[] statusData = AMFUtil.writeObject(status); 171 | handData(MsgType.MSG_CONTROL, version, statusData, 0, ctx); 172 | 173 | handResult(txid, MsgType.MSG_CONTROL, new byte[]{AMFUtil.writeNull()}, new byte[]{AMFUtil.writeNull()}, 0, ctx); 174 | byteBuf.release(); 175 | } 176 | 177 | /** 178 | * @param amfClass 179 | * @param txid 180 | * @param ctx 181 | */ 182 | private void handPlay(AMFClass amfClass, double txid, ChannelHandlerContext ctx) { 183 | AMFUtil.load_amf(amfClass); 184 | String path = AMFUtil.load_amf_string(amfClass); //这个为发布的 url 协议 185 | this.path = path; 186 | isPlay = true; 187 | startPlayback(ctx, false); 188 | handResult(txid, MsgType.MSG_CONTROL, new byte[]{AMFUtil.writeNull()}, new byte[]{AMFUtil.writeNull()}, 0, ctx); 189 | } 190 | 191 | private void startPlayback(ChannelHandlerContext ctx, boolean isPause) { 192 | ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(1024); 193 | byteBuf.writeBytes(AMFUtil.writeString("onStatus")); 194 | byteBuf.writeBytes(AMFUtil.writeNumber(0.0)); 195 | byteBuf.writeByte(AMFUtil.writeNull()); 196 | int length = byteBuf.readableBytes(); 197 | byte[] version = new byte[length]; 198 | byteBuf.readBytes(version); 199 | 200 | Map status = new HashMap(); 201 | status.put("level", "status"); 202 | status.put("code", "NetStream.Play.Reset"); 203 | status.put("description", "Resetting and playing stream."); 204 | 205 | byte[] statusData = AMFUtil.writeObject(status); 206 | handData(MsgType.MSG_CONTROL, version, statusData, 1337, ctx); 207 | if (!isPause) { 208 | status = new HashMap(); 209 | status.put("level", "status"); 210 | status.put("code", "NetStream.Play.Start"); 211 | status.put("description", "Started playing."); 212 | statusData = AMFUtil.writeObject(status); 213 | 214 | byteBuf.writeBytes(AMFUtil.writeString("onStatus")); 215 | byteBuf.writeBytes(AMFUtil.writeNumber(0.0)); 216 | byteBuf.writeByte(AMFUtil.writeNull()); 217 | length = byteBuf.readableBytes(); 218 | version = new byte[length]; 219 | byteBuf.readBytes(version); 220 | handData(MsgType.MSG_CONTROL, version, statusData, 1337, ctx); 221 | 222 | Receive receive = new Receive(); 223 | receive.receive = ctx; 224 | receive.playing = true; 225 | receive.ready = false; 226 | ReceiveGroup.setChannel(path, receive); 227 | 228 | Publish publish = PublishGroup.getChannel(path); 229 | if (publish != null) { 230 | System.out.println("keyframe"); 231 | receive.keyframe = false; 232 | byteBuf.writeBytes(AMFUtil.writeString("onMetaData")); 233 | byteBuf.writeBytes(AMFUtil.writeMixedArray(publish.MetaData)); 234 | byte[] resultData = new byte[byteBuf.readableBytes()]; 235 | byteBuf.readBytes(resultData); 236 | RtmpResponse.sendData(resultData, MsgType.MSG_NOTIFY, 1337, ctx, 0,chunkLength); 237 | System.err.println("数据大小" + publish.chunk_size); 238 | // handSetChunkSize(ctx,chunkLength); 239 | // this.sendChunkLength = publish.chunk_size; 240 | } 241 | 242 | } 243 | byteBuf.release(); 244 | } 245 | 246 | 247 | /** 248 | * 播放还是停止 249 | * 250 | * @param amfClass 251 | * @param txid 252 | * @param ctx 253 | */ 254 | private void handPause(AMFClass amfClass, double txid, ChannelHandlerContext ctx) { 255 | AMFUtil.load_amf(amfClass); /* NULL */ 256 | System.err.println("暂时或者开启服务"); 257 | Boolean paused = AMFUtil.load_amf_boolean(amfClass); 258 | List list = ReceiveGroup.getChannel(this.path); 259 | int listIndex = 0; 260 | for (Receive receive : list) { 261 | if (receive.receive == ctx) { 262 | if (receive.playing) { 263 | System.err.println("没有播放"); 264 | receive.playing = false; 265 | } else { 266 | System.err.println("开始播放"); 267 | receive.playing = true; 268 | } 269 | list.set(listIndex, receive); 270 | } 271 | listIndex++; 272 | } 273 | if (paused) { 274 | System.err.println("pausing\n"); 275 | ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(1024); 276 | byteBuf.writeBytes(AMFUtil.writeString("onStatus")); 277 | byteBuf.writeBytes(AMFUtil.writeNumber(0.0)); 278 | byteBuf.writeByte(AMFUtil.writeNull()); 279 | byte[] version = new byte[byteBuf.readableBytes()]; 280 | byteBuf.readBytes(version); 281 | Map status = new HashMap(); 282 | status.put("level", "status"); 283 | status.put("code", "NetStream.Pause.Notify"); 284 | status.put("description", "Pausing."); 285 | byte[] statusData = AMFUtil.writeObject(status); 286 | handResult(txid, MsgType.MSG_CONTROL, version, statusData, 0, ctx); 287 | byteBuf.release(); 288 | // client->playing = false; 289 | } else { 290 | System.out.println("进来了"); 291 | startPlayback(ctx, true); 292 | } 293 | handResult(txid, MsgType.MSG_CONTROL, new byte[]{AMFUtil.writeNull()}, new byte[]{AMFUtil.writeNull()}, 0, ctx); 294 | } 295 | 296 | 297 | 298 | /** 299 | * 统一 _result 返回 300 | * 301 | * @param txid 302 | * @param msgType 303 | * @param version 304 | * @param status 305 | * @param streamId 306 | * @param ctx 307 | */ 308 | private void handResult(double txid, byte msgType, byte[] version, byte[] status, int streamId, ChannelHandlerContext ctx) { 309 | ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(1024); 310 | byte[] resultString = AMFUtil.writeString("_result"); 311 | byteBuf.writeBytes(resultString); 312 | byte[] resultNumber = AMFUtil.writeNumber(txid); 313 | byteBuf.writeBytes(resultNumber); 314 | byteBuf.writeBytes(version); 315 | byteBuf.writeBytes(status); 316 | int length = byteBuf.readableBytes(); 317 | byte[] data = new byte[length]; 318 | byteBuf.readBytes(data); 319 | RtmpResponse.sendData(data, msgType, streamId, ctx, 0,chunkLength); 320 | byteBuf.release(); 321 | } 322 | 323 | 324 | /** 325 | * 统一数据返回格式 326 | * 327 | * @param msgType 328 | * @param version 329 | * @param status 330 | * @param streamId 331 | * @param ctx 332 | */ 333 | private void handData(byte msgType, byte[] version, byte[] status, int streamId, ChannelHandlerContext ctx) { 334 | ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(1024); 335 | byteBuf.writeBytes(version); 336 | byteBuf.writeBytes(status); 337 | int length = byteBuf.readableBytes(); 338 | byte[] data = new byte[length]; 339 | byteBuf.readBytes(data); 340 | RtmpResponse.sendData(data, msgType, streamId, ctx, 0,chunkLength); 341 | byteBuf.release(); 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /src/main/java/Rtmp/RtmpHandshake.java: -------------------------------------------------------------------------------- 1 | package Rtmp; 2 | 3 | import Util.Common; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.Unpooled; 6 | import io.netty.channel.ChannelHandlerContext; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Date; 10 | import java.util.List; 11 | import java.util.Random; 12 | 13 | public class RtmpHandshake { 14 | public byte[] c1Data = new byte[1536]; 15 | private boolean isVersion = false; //是否认证了版本信息 16 | private boolean isC1 = false; //是否认证了 c1 17 | private boolean isC2 = false; //是否认证了 c2 18 | public boolean isHandshake = false; //是否已经握手成功 19 | private List S1 = new ArrayList(); 20 | private byte[] zero = {0x00, 0x00, 0x00, 0x00}; 21 | 22 | /** 23 | * 解析握手数据 24 | * @param ctx 25 | */ 26 | 27 | // 握手以客户端发送 C0 和 C1 块开始。 28 | // 客户端必须等待接收到 S1 才能发送 C2。 29 | // 客户端必须等待接收到 S2 才能发送任何其他数据。 30 | // 服务器端必须等待接收到 C0 才能发送 S0 和 S1, 31 | // 服务器端必须等待接收到 C1 才能发送 S2。 32 | // 服务器端必须等待接收到 C2 才能发送任何其他数据。 33 | 34 | // c1 s1 数据格式 4 byte time 4 byte zero 1536 - 8 random byte 35 | 36 | public void handShake(ChannelHandlerContext ctx, ByteBuf byteBuf) { 37 | if (!isVersion) { // 认证 version 38 | byte[] flags = new byte[1]; 39 | byteBuf.readBytes(flags); 40 | if (flags[0] != Common.C0) { 41 | ctx.close(); 42 | return; 43 | } else { 44 | isVersion = true; 45 | } 46 | } 47 | 48 | if (!isC1) { 49 | if (byteBuf.readableBytes() >= Common.C1_LENGTH) { 50 | this.c1Data = new byte[Common.C1_LENGTH]; 51 | byteBuf.readBytes(this.c1Data); //读取c1 内容 52 | int time = (int) (new Date().getTime() / 1000); 53 | byte[] timeByte = Common.intToByte(time); 54 | ctx.writeAndFlush(Unpooled.copiedBuffer(new byte[]{Common.S0})); 55 | for (byte i : timeByte) { 56 | S1.add(i); 57 | } 58 | for (byte i : zero) { 59 | S1.add(i); 60 | } 61 | for (int i = 0; i < Common.RANDOM_LENGTH; i++) { 62 | Random random = new Random(); 63 | S1.add((byte) random.nextInt(9)); 64 | } 65 | ctx.writeAndFlush(Unpooled.copiedBuffer(Common.conversionByteArray(S1))); 66 | isC1 = true; 67 | 68 | int s2time = (int) (new Date().getTime() / 1000); //设置 s2 time 69 | byte[] timeByte2 = Common.intToByte(s2time); 70 | int index = 0; 71 | for (int i = 4; i < 8; i++) { 72 | this.c1Data[i] = timeByte2[index]; 73 | index++; 74 | } 75 | ctx.writeAndFlush(Unpooled.copiedBuffer(this.c1Data)); 76 | } 77 | } 78 | if (!isC2) { 79 | if (byteBuf.readableBytes() >= Common.C1_LENGTH) { 80 | byte[] s2 = new byte[Common.C1_LENGTH]; 81 | byteBuf.readBytes(s2); //读取c1 内容 82 | isHandshake = true; 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/Rtmp/RtmpMessage.java: -------------------------------------------------------------------------------- 1 | package Rtmp; 2 | 3 | public enum RtmpMessage { 4 | ChunkSize, //chunk 大小设置 5 | ABORT, 6 | BYTES_READ, 7 | PING, 8 | SERVER_WINDOW, 9 | AUDIO, 10 | VIDEO, 11 | NOTIFY, // 设置元消息 12 | Control, //命令控制 13 | NOT 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/Rtmp/RtmpNot.java: -------------------------------------------------------------------------------- 1 | package Rtmp; 2 | 3 | public class RtmpNot { 4 | public void setNot(byte[] message) { 5 | System.out.println("未知消息 长度 === " + message.length); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/Rtmp/RtmpNotify.java: -------------------------------------------------------------------------------- 1 | package Rtmp; 2 | 3 | import AMF.AMFUtil; 4 | import User.Publish; 5 | import User.PublishGroup; 6 | 7 | import java.util.Map; 8 | 9 | public class RtmpNotify extends Amf{ 10 | public void setNotify(byte[] messageData,String path) { 11 | setAmfClass(messageData); 12 | String command = AMFUtil.load_amf_string(amfClass); 13 | System.err.println(command); 14 | if (command.equals("@setDataFrame")) { 15 | String type = AMFUtil.load_amf_string(amfClass); 16 | System.out.println(type); 17 | Map data = AMFUtil.load_amf_mixedArray(amfClass); 18 | Publish publish = PublishGroup.getChannel(path); 19 | if (publish != null) { 20 | publish.MetaData = data; 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/Rtmp/RtmpPacket.java: -------------------------------------------------------------------------------- 1 | package Rtmp; 2 | 3 | import Decoder.AudioStreamDecoder; 4 | import Util.Common; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.buffer.ByteBufAllocator; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import lombok.Getter; 9 | import lombok.Setter; 10 | 11 | import java.io.File; 12 | import java.io.FileNotFoundException; 13 | import java.io.FileOutputStream; 14 | import java.io.IOException; 15 | 16 | // +--------------+ +-------------+----------------+-------------------+ 17 | // | Chunk Header | = | Basic header| Message Header |Extended Timestamp | 18 | // +--------------+ +-------------+----------------+-------------------+ 19 | public class RtmpPacket { 20 | 21 | @Setter 22 | @Getter 23 | protected int csid; 24 | 25 | @Setter 26 | @Getter 27 | protected int head_len; //头部长度 28 | 29 | @Setter 30 | @Getter 31 | protected int timestamp = 0; 32 | 33 | @Setter 34 | @Getter 35 | private int videoTimestamp; 36 | 37 | @Setter 38 | @Getter 39 | private int audioTimestamp; 40 | 41 | @Setter 42 | @Getter 43 | protected int messageLength; //消息数据长度 44 | 45 | @Setter 46 | @Getter 47 | protected RtmpMessage messageType; //消息类型 48 | 49 | @Setter 50 | @Getter 51 | protected int streamId; //流id 52 | 53 | @Setter 54 | @Getter 55 | protected int extTimestamp; //额外时间戳 56 | 57 | @Setter 58 | @Getter 59 | private boolean lackMessage = false; 60 | 61 | @Getter 62 | private byte[] messageData; 63 | 64 | private int fmt; //basic head fmt 65 | 66 | @Setter 67 | private int chunkLength = Common.DEFAULT_CHUNK_MESSAGE_LENGTH; 68 | 69 | @Setter 70 | @Getter 71 | private ByteBuf byteBuf; 72 | 73 | private ByteBuf copyByteBuf; 74 | 75 | //是否数据分包 76 | @Setter 77 | @Getter 78 | private boolean isSubpackage = false; 79 | 80 | private int readIndex = 0; 81 | 82 | File txt; 83 | 84 | private AudioStreamDecoder audioStreamDecoder = new AudioStreamDecoder(); 85 | 86 | //对应的 chunk head length 长度 87 | private int[] chunk_message_length = {11, 7, 3, 0}; 88 | 89 | // Basic head 类型 90 | // private enum BasicHead { 91 | // FULL, // Basic header 92 | // NO_MSG_STREAM_ID, 93 | // TIMESTAMP, // no timestamp 94 | // ONLY // chunk header == null 95 | // }; 96 | 97 | 98 | private void setCsid(byte flag) { 99 | this.csid = (byte) ((flag & 0xff & 0xff) & 0x3f); // 按位与 11 为 1 ,有0 为 0 100 | switch (this.csid){ 101 | case 0: 102 | System.err.println("rtmphead = 0");; 103 | this.csid = byteBuf.readByte() + 64; 104 | break; 105 | case 1: 106 | System.err.println("rtmphead = 0");; 107 | this.csid = byteBuf.readByte() * 256 + byteBuf.readByte() + 64; 108 | break; 109 | } 110 | } 111 | 112 | private void init() { 113 | lackMessage = false; 114 | byte flag = byteBuf.readByte(); 115 | setCsid(flag); 116 | this.fmt = (byte) ((flag & 0xff & 0xff) >> 6); 117 | this.head_len = chunk_message_length[this.fmt]; 118 | setChunkHeaderData(); 119 | setMessageData(); 120 | } 121 | 122 | public RtmpPacket(ChannelHandlerContext ctx, ByteBuf byteBuf) { 123 | this.byteBuf = byteBuf; 124 | this.prepare(); 125 | } 126 | 127 | public void reset(ByteBuf byteBuf) { //新的数据,重新设置 128 | if(this.byteBuf.readableBytes() == 0) { 129 | this.byteBuf.release(); 130 | this.byteBuf = byteBuf; 131 | } 132 | copyByteBuf.release(); 133 | prepare(); 134 | } 135 | 136 | private void prepare() { 137 | copyByteBuf = byteBuf.copy(); 138 | init(); 139 | } 140 | 141 | 142 | /** 143 | * 数据不足时候数据拷贝 144 | */ 145 | public void lackMessage(ByteBuf byteBuf) { 146 | int allMessageLength = byteBuf.readableBytes() + copyByteBuf.readableBytes(); 147 | int chunkLen = 0; 148 | if(isSubpackage) { //存在 分包情况,以chunkSzie 作为判断依据 149 | if(this.messageLength - this.readIndex >= chunkLength) { 150 | chunkLen = chunkLength + this.head_len + 1; 151 | } else { 152 | chunkLen = this.messageLength - this.readIndex + this.head_len + 1; 153 | } 154 | } else { 155 | chunkLen = this.messageLength + this.head_len + 1; 156 | } 157 | 158 | if(!chunkByteBufLength(chunkLen, allMessageLength)) { //数据太长,继续拷贝余下部分 159 | System.out.println("重复拷贝"); 160 | return; 161 | } 162 | ByteBuf tmpByteBuf = ByteBufAllocator.DEFAULT.buffer(allMessageLength); 163 | tmpByteBuf.writeBytes(copyByteBuf); 164 | tmpByteBuf.writeBytes(byteBuf); 165 | this.byteBuf = tmpByteBuf; 166 | init(); 167 | } 168 | 169 | private void setTimestamp() { 170 | byte[] timestampByte = new byte[Common.TIMESTAMP_BYTE_LENGTH]; 171 | byteBuf.readBytes(timestampByte); 172 | timestamp += Common.byteToInt24(timestampByte); // 为了解码 时候 客户端使用 所以 进行类加 173 | } 174 | 175 | private void setMessageLength() { 176 | byte[] msg_len = new byte[Common.TIMESTAMP_BYTE_LENGTH]; 177 | byteBuf.readBytes(msg_len); 178 | this.messageLength = Common.byteToInt24(msg_len); 179 | } 180 | 181 | private void setMessageType() { 182 | byte type = byteBuf.readByte(); 183 | switch (type) { 184 | case 0x01: 185 | this.messageType = RtmpMessage.ChunkSize; 186 | break; 187 | case 0x03: 188 | this.messageType = RtmpMessage.BYTES_READ; 189 | break; 190 | case 0x04: 191 | this.messageType = RtmpMessage.PING; 192 | break; 193 | case 0x05: 194 | this.messageType = RtmpMessage.SERVER_WINDOW; 195 | break; 196 | case 0x08: 197 | this.messageType = RtmpMessage.AUDIO; 198 | this.audioTimestamp = timestamp; 199 | break; 200 | case 0x09: 201 | this.messageType = RtmpMessage.VIDEO; 202 | this.videoTimestamp = timestamp; 203 | break; 204 | case 0x12: 205 | this.messageType = RtmpMessage.NOTIFY; 206 | break; 207 | case 0x14: 208 | this.messageType = RtmpMessage.Control; 209 | break; 210 | default: 211 | this.messageType = RtmpMessage.NOT; 212 | System.out.println("未知消息"); 213 | } 214 | } 215 | 216 | private void setStreamId(){ 217 | byte[] streamByte = new byte[Common.STREAM_ID_LENGTH]; 218 | byteBuf.readBytes(streamByte); 219 | this.streamId = Common.byteSmallToInt(streamByte); //只有 stream 是小端模式 220 | } 221 | 222 | /** 223 | * 检查是否数据不足 224 | * @param len 225 | * @return 226 | */ 227 | private boolean chunkByteBufLength(int len,int dataLen) { 228 | if(dataLen < len) { 229 | //if(!lackMessage){ 230 | byte[] lackData = new byte[this.byteBuf.readableBytes()]; 231 | this.byteBuf.readBytes(lackData); 232 | // } 233 | lackMessage = true; 234 | // System.out.println("chunkByteBufLength 数据不足"); 235 | return false; 236 | } 237 | return true; 238 | } 239 | 240 | /** 241 | * 根据头部设置 头部数据 242 | */ 243 | private void setChunkHeaderData() { 244 | if(!chunkByteBufLength(this.head_len,this.byteBuf.readableBytes())) { 245 | return; 246 | } 247 | 248 | if(this.head_len >= 3) 249 | this.setTimestamp(); 250 | 251 | if(this.head_len >= 7){ 252 | this.setMessageLength(); 253 | this.setMessageType(); 254 | } 255 | if(this.head_len >= 11) 256 | this.setStreamId(); 257 | 258 | if(this.timestamp == Common.TIMESTAMP_MAX_NUM) { 259 | System.out.println("额外数据"); 260 | byte[] timestampByte = new byte[Common.EXTEND_TIMESTAMP_LENGTH]; 261 | byteBuf.readBytes(timestampByte); 262 | this.extTimestamp = Common.byteToInt(timestampByte); 263 | } 264 | } 265 | 266 | /** 267 | * 当数据包超过chunkSize 设置时候,需要分包,每个包 head_len为 0 268 | */ 269 | private void subPackage() { 270 | isSubpackage = true; 271 | int chunkLen = 0; 272 | if(this.messageLength - this.readIndex > chunkLength) { 273 | chunkLen = chunkLength; 274 | } else { 275 | chunkLen = this.messageLength - this.readIndex; 276 | } 277 | if(!chunkByteBufLength(chunkLen,byteBuf.readableBytes())) { 278 | return; 279 | } 280 | byte[] chunkMessageData = new byte[chunkLen]; 281 | byteBuf.readBytes(chunkMessageData); 282 | for(int i = 0;i < chunkLen;i++) { 283 | messageData[readIndex++] = chunkMessageData[i]; 284 | } 285 | if(this.readIndex == this.messageLength) { 286 | this.readIndex = 0; 287 | isSubpackage = false; 288 | } 289 | } 290 | 291 | /** 292 | * 设置消息数据 293 | */ 294 | private void setMessageData() { 295 | if(!isSubpackage) { 296 | messageData = new byte[this.messageLength]; 297 | } 298 | 299 | if(this.messageLength > chunkLength) { //大数据,rtmp 分包提取 300 | subPackage(); 301 | return; 302 | } 303 | 304 | if(!chunkByteBufLength(this.messageLength,byteBuf.readableBytes())) { 305 | return; 306 | } 307 | byteBuf.readBytes(messageData); 308 | } 309 | } 310 | 311 | 312 | -------------------------------------------------------------------------------- /src/main/java/Rtmp/RtmpPing.java: -------------------------------------------------------------------------------- 1 | package Rtmp; 2 | 3 | public class RtmpPing extends Amf { 4 | public void setPing(byte[] message) { 5 | setAmfClass(message); 6 | System.out.println("ping === " + message.length); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/Rtmp/RtmpResponse.java: -------------------------------------------------------------------------------- 1 | package Rtmp; 2 | 3 | import Util.Common; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.ByteBufAllocator; 6 | import io.netty.buffer.Unpooled; 7 | import io.netty.channel.ChannelHandlerContext; 8 | 9 | public class RtmpResponse { 10 | 11 | // sendData 是 chunk设置了长度之后的返回, 发送端 跟 接收方的chunksize 不一致,需要重新修改,暂时还没修改 12 | private static ByteBuf streamByteBuf = ByteBufAllocator.DEFAULT.buffer(1024); 13 | private static int sendChunkLength = Common.DEFAULT_CHUNK_MESSAGE_LENGTH; 14 | public static void sendData(byte[] chunkData, byte msgType, int streamId, ChannelHandlerContext ctx, int timestamp,int chunkLength) { 15 | ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(1024); 16 | byte flags; 17 | if (streamId == Common.STREAM_ID) { 18 | flags = (4 & 0x3f) | (0 << 6); 19 | } else { 20 | flags = (3 & 0x3f) | (0 << 6); 21 | } 22 | // byte[] timestamp = {0x00,0x00,0x00}; 23 | int msg_len = chunkData.length; 24 | byte[] msgLength = Common.reverseArray(Common.intToByte24(msg_len)); 25 | byte[] streamData = Common.intToByte(streamId); 26 | byteBuf.writeByte(flags); 27 | byteBuf.writeBytes(Common.reverseArray(Common.intToByte24(timestamp))); 28 | byteBuf.writeBytes(msgLength); 29 | byteBuf.writeByte(msgType); 30 | byteBuf.writeBytes(streamData); 31 | int pos = 0; 32 | while (pos < chunkData.length) { 33 | if (byteBuf.writableBytes() < chunkData.length) { 34 | byteBuf.ensureWritable(chunkData.length); 35 | } 36 | if (chunkData.length - pos < chunkLength) { 37 | for (int i = pos; i < chunkData.length; i++) { 38 | byteBuf.writeByte(chunkData[i]); 39 | } 40 | } else { 41 | if (byteBuf.writableBytes() < pos + chunkLength) { 42 | byteBuf.ensureWritable(pos + chunkLength); 43 | } 44 | for (int i = pos; i < pos + chunkLength; i++) { 45 | byteBuf.writeByte(chunkData[i]); 46 | } 47 | if (streamId == Common.STREAM_ID) { 48 | byteBuf.writeByte((byte) ((4 & 0x3f) | (3 << 6))); 49 | } else { 50 | byteBuf.writeByte((byte) ((3 & 0x3f) | (3 << 6))); 51 | } 52 | } 53 | pos += chunkLength; 54 | } 55 | int sendLength = byteBuf.readableBytes(); 56 | byte[] sendData = new byte[sendLength]; 57 | byteBuf.readBytes(sendData); 58 | ctx.writeAndFlush(Unpooled.copiedBuffer(sendData)); 59 | byteBuf.release(); 60 | } 61 | 62 | 63 | 64 | /** 65 | * 同意数据发送 66 | * 67 | * @param chunkData 68 | * @param msgType 69 | * @param streamId 70 | * @param ctx 71 | */ 72 | public static void sendData2(byte[] chunkData, byte msgType, int streamId, ChannelHandlerContext ctx, int timestamp) { 73 | byte flags; 74 | if (streamId == Common.STREAM_ID) { 75 | flags = (4 & 0x3f) | (0 << 6); 76 | } else { 77 | flags = (3 & 0x3f) | (0 << 6); 78 | } 79 | // byte[] timestamp = {0x00,0x00,0x00}; 80 | int msg_len = chunkData.length; 81 | byte[] msgLength = Common.reverseArray(Common.intToByte24(msg_len)); 82 | byte[] streamData = Common.intToByte(streamId); 83 | streamByteBuf.writeByte(flags); 84 | // System.out.println(Common.bytes2hex(Common.reverseArray(Common.intToByte24(timestamp)))); 85 | streamByteBuf.writeBytes(Common.reverseArray(Common.intToByte24(timestamp))); 86 | streamByteBuf.writeBytes(msgLength); 87 | streamByteBuf.writeByte(msgType); 88 | streamByteBuf.writeBytes(streamData); 89 | int pos = 0; 90 | 91 | while (pos < chunkData.length) { 92 | if (streamByteBuf.writableBytes() < chunkData.length) { 93 | streamByteBuf.ensureWritable(chunkData.length); 94 | } 95 | if (chunkData.length - pos < sendChunkLength) { 96 | for (int i = pos; i < chunkData.length; i++) { 97 | streamByteBuf.writeByte(chunkData[i]); 98 | } 99 | } else { 100 | if (streamByteBuf.writableBytes() < pos + sendChunkLength) { 101 | streamByteBuf.ensureWritable(pos + sendChunkLength); 102 | } 103 | for (int i = pos; i < pos + sendChunkLength; i++) { 104 | streamByteBuf.writeByte(chunkData[i]); 105 | } 106 | if (pos + sendChunkLength != chunkData.length) { 107 | if (streamId == Common.STREAM_ID) { 108 | streamByteBuf.writeByte((byte) ((4 & 0x3f) | (3 << 6))); 109 | } else { 110 | streamByteBuf.writeByte((byte) ((3 & 0x3f) | (3 << 6))); 111 | } 112 | } 113 | } 114 | pos += sendChunkLength; 115 | } 116 | sendClient(ctx); 117 | } 118 | 119 | private static void sendClient(ChannelHandlerContext ctx) { 120 | if (streamByteBuf.readableBytes() > 0) { 121 | int length = streamByteBuf.readableBytes(); 122 | //if(length > 4096) length = 4096; 123 | byte[] sendData = new byte[length]; 124 | streamByteBuf.readBytes(sendData); 125 | ctx.fireChannelRead(sendData); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/Rtmp/RtmpServerWindow.java: -------------------------------------------------------------------------------- 1 | package Rtmp; 2 | 3 | import Util.Common; 4 | 5 | import java.util.Arrays; 6 | 7 | public class RtmpServerWindow extends Amf{ 8 | public void setRtmpServerWindow(byte[] message) { 9 | setAmfClass(message); 10 | if (amfClass.pos + 4 > amfClass.message.length) { 11 | System.err.println("数据不足"); 12 | } 13 | // System.err.println(Common.bytes2hex(amfClass.message)); 14 | byte[] number = new byte[4]; 15 | int index = 0; 16 | for (int i = amfClass.pos; i < amfClass.pos + 4; i++) { 17 | number[index] = amfClass.message[i]; 18 | index++; 19 | } 20 | int windowSize = Common.byteToInt(number); 21 | System.err.println("window size" + windowSize); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/Rtmp/RtmpVideo.java: -------------------------------------------------------------------------------- 1 | package Rtmp; 2 | 3 | import Decoder.VideoStreamDecoder; 4 | import User.Publish; 5 | import User.PublishGroup; 6 | import User.Receive; 7 | import User.ReceiveGroup; 8 | import Util.Common; 9 | import Util.MsgType; 10 | import io.netty.buffer.ByteBuf; 11 | import io.netty.buffer.ByteBufAllocator; 12 | 13 | import java.util.List; 14 | 15 | import static Util.Common.FLV_KEY_FRAME; 16 | 17 | public class RtmpVideo { 18 | 19 | public void setVideoData(byte[] message,String path,int timestamp) { 20 | byte flags = message[0]; 21 | // new VideoStreamDecoder(message); 22 | 23 | // System.out.println("======================="); 24 | // System.out.println(Common.bytes2hex(message)); 25 | // System.out.println("======================="); 26 | Publish publish = PublishGroup.getChannel(path); 27 | 28 | if (!publish.keyFrame) { 29 | publish.keyFrame = true; 30 | publish.keyFrameMessage = message; 31 | } 32 | List listVideo = ReceiveGroup.getChannel(path); 33 | if (listVideo != null) { 34 | for (Receive receive : listVideo) { 35 | if (!receive.playing) { 36 | continue; 37 | } 38 | if (receive != null && receive.playing) { 39 | if (flags >> 4 == FLV_KEY_FRAME && !receive.ready) { 40 | System.out.println("flags ====== " + flags); 41 | ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(1024); 42 | byteBuf.writeByte(0x00); 43 | byteBuf.writeByte(0x00); 44 | byteBuf.writeBytes(Common.intToByte(1337)); 45 | byte[] control = new byte[byteBuf.readableBytes()]; 46 | byteBuf.readBytes(control); 47 | RtmpResponse.sendData(control, MsgType.MSG_USER_CONTROL, 0, receive.receive, 0,Common.DEFAULT_CHUNK_MESSAGE_LENGTH); 48 | receive.ready = true; 49 | byteBuf.release(); 50 | } 51 | if (!receive.keyframe) { 52 | System.out.println("关键进来了"); 53 | receive.keyframe = true; 54 | RtmpResponse.sendData2(publish.keyFrameMessage, MsgType.MSG_VIDEO, 1337, receive.receive, timestamp); 55 | } 56 | if (receive.ready && receive.keyframe) { 57 | //System.out.println("时间戳" + timestamp); 58 | // System.out.println("video发送数据 ===" + message.length +" 时间戳" + timestamp); 59 | RtmpResponse.sendData2(message, MsgType.MSG_VIDEO, 1337, receive.receive, timestamp); 60 | } 61 | } 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/User/Publish.java: -------------------------------------------------------------------------------- 1 | package User; 2 | 3 | import Util.Common; 4 | import io.netty.channel.ChannelHandlerContext; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | public class Publish { 10 | public String path; 11 | public ChannelHandlerContext publish; 12 | public Map MetaData; 13 | public boolean keyFrame = false; 14 | public byte[] keyFrameMessage = null; 15 | public int chunk_size = Common.DEFAULT_CHUNK_MESSAGE_LENGTH; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/User/PublishGroup.java: -------------------------------------------------------------------------------- 1 | package User; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class PublishGroup { 7 | public static Map channel = new HashMap(); 8 | 9 | /** 10 | * 设置 channel 11 | * @param path 12 | * @param client 13 | */ 14 | public synchronized static void setChannel(String path, Publish client) { 15 | PublishGroup.channel.put(path,client); 16 | } 17 | 18 | /** 19 | * 获取 channel 20 | * @param path 21 | * @return 22 | */ 23 | public static Publish getChannel(String path) { 24 | if(channel.containsKey(path)){ 25 | return channel.get(path); 26 | } 27 | return null; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/User/Receive.java: -------------------------------------------------------------------------------- 1 | package User; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | 5 | public class Receive { 6 | public boolean playing = false; 7 | public ChannelHandlerContext receive; 8 | public boolean ready = false; 9 | public boolean keyframe = true; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/User/ReceiveGroup.java: -------------------------------------------------------------------------------- 1 | package User; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | public class ReceiveGroup { 9 | public static Map> channel = new HashMap>(); 10 | 11 | // /** 12 | // * 设置 channel 13 | // * @param path 14 | // * @param client 15 | // */ 16 | // public synchronized static void setChannel(String path, List client) { 17 | // channel.put(path,client); 18 | // } 19 | 20 | /** 21 | * 设置 channel 22 | * @param path 23 | * @param client 24 | */ 25 | public synchronized static void setChannel(String path,Receive client) { 26 | List list = new ArrayList(); 27 | if(channel.containsKey(path)){ 28 | list = channel.get(path); 29 | list.add(client); 30 | } else { 31 | list = new ArrayList(); 32 | list.add(client); 33 | } 34 | channel.put(path,list); 35 | } 36 | 37 | /** 38 | * 设置 channel 39 | * @param path 40 | */ 41 | public synchronized static void setChannel(String path,List list) { 42 | channel.put(path,list); 43 | } 44 | 45 | /** 46 | * 获取 channel 47 | * @param path 48 | * @return 49 | */ 50 | public static List getChannel(String path) { 51 | if(channel.containsKey(path)){ 52 | return channel.get(path); 53 | } 54 | return null; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/Util/AACDecoderSpecific.java: -------------------------------------------------------------------------------- 1 | package Util; 2 | 3 | public class AACDecoderSpecific { 4 | public byte nAudioFortmatType; 5 | public byte nAudioSampleType; 6 | public byte nAudioSizeType; 7 | public byte nAudioStereo; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/Util/AudioSpecificConfig.java: -------------------------------------------------------------------------------- 1 | package Util; 2 | 3 | public class AudioSpecificConfig { 4 | public byte nAudioObjectType; 5 | public byte nSampleFrequencyIndex; 6 | public byte nChannels; 7 | public byte nFrameLengthFlag; 8 | public byte nDependOnCoreCoder; 9 | public byte nExtensionFlag; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/Util/Common.java: -------------------------------------------------------------------------------- 1 | package Util; 2 | 3 | import java.io.IOException; 4 | import java.io.RandomAccessFile; 5 | import java.util.ArrayList; 6 | import java.util.Iterator; 7 | import java.util.List; 8 | 9 | /** 10 | * 公共配置工具类 11 | */ 12 | public class Common { 13 | public static int C0_LENGTH = 1; 14 | public static int C0_INDEX = 0; 15 | public static int C1_LENGTH = 1536; 16 | public static byte C0 = 0x03; 17 | public static byte S0 = 0x03; 18 | public static int HANDSHAKE_LENGTH = 3073; 19 | public static int RANDOM_LENGTH = 1536 - 8; 20 | // chunk head 21 | public static int MSG_LEN_LENGTH = 3; 22 | public static int TIMESTAMP_BYTE_LENGTH = 3; 23 | public static int MST_TYPE_LENGTH = 1; 24 | public static int STREAM_ID_LENGTH = 4; 25 | public static int TIMESTAMP_MAX_NUM = 16777215; 26 | public static int EXTEND_TIMESTAMP_LENGTH = 4; 27 | // chunk message 28 | public static int MESSAGE_MT_LENGTH = 1; 29 | public static int MESSAGE_PAYLOAD_LENGTH = 3; 30 | public static int MESSAGE_TIMESTAMP_LENGTH = 4; 31 | public static int MESSAGE_STREAM_ID_LENGTH = 3; 32 | public static int DEFAULT_CHUNK_MESSAGE_LENGTH = 128; 33 | public static double CONTROL_ID = 0; 34 | public static double STREAM_ID = 1337; 35 | // AMF 36 | public static int AFM_TYPE_LENGTH = 1; // afm 数据类型长度 37 | public static int AFM_DATA_LENGTH = 2; // afm 表示一个数据的长度 38 | public static int AFM_NUMBER_LENGTH = 8; //afm 一个 number 的长度,其实为 double 39 | 40 | 41 | public static int READ_CHUNK_LENGTH = 10240;//一次性读到 chunkdata 的数据长度 42 | public static String APP_NAME = "live"; 43 | 44 | public static byte FLV_KEY_FRAME = 0x01; 45 | public static byte CONTROL_CLEAR_STREAM = 0x00; 46 | 47 | /** 48 | * int 转换 为 byte 49 | * @param val 50 | * @return 51 | */ 52 | public static byte[] intToByte(int val){ 53 | byte[] b = new byte[4]; 54 | b[0] = (byte)(val & 0xff); 55 | b[1] = (byte)((val >> 8) & 0xff); 56 | b[2] = (byte)((val >> 16) & 0xff); 57 | b[3] = (byte)((val >> 24) & 0xff); 58 | return b; 59 | } 60 | 61 | public static void appendMethodA(String fileName, byte[] content) { 62 | 63 | try { 64 | 65 | // 打开一个随机访问文件流,按读写方式 66 | 67 | RandomAccessFile randomFile = new RandomAccessFile(fileName, "rw"); 68 | 69 | // 文件长度,字节数 70 | 71 | long fileLength = randomFile.length(); 72 | 73 | // 将写文件指针移到文件尾。 74 | 75 | randomFile.seek(fileLength); 76 | 77 | randomFile.write(content); 78 | 79 | randomFile.close(); 80 | 81 | } catch (IOException e) { 82 | 83 | e.printStackTrace(); 84 | 85 | } 86 | 87 | } 88 | 89 | 90 | /** 91 | * int 转换 为 byte 92 | * @param val 93 | * @return 94 | */ 95 | public static byte[] intToByte24(int val){ 96 | byte[] b = new byte[3]; 97 | b[0] = (byte)(val & 0xff); 98 | b[1] = (byte)((val >> 8) & 0xff); 99 | b[2] = (byte)((val >> 16) & 0xff); 100 | return b; 101 | } 102 | 103 | /** 104 | * 16 byte int 105 | * @param bytes 106 | * @return 107 | */ 108 | public static int byteToInt16(byte[] bytes) { 109 | return 0x00 << 24| 0x00 << 16 | (bytes[0] & 0xff) << 8 | bytes[1] & 0xff; 110 | } 111 | 112 | /** 113 | * 24 byte 114 | * @param bytes 115 | * @return 116 | */ 117 | public static int byteToInt24(byte[] bytes) { 118 | return 0x00 << 24| (bytes[0] & 0xff) << 16 | (bytes[1] & 0xff) << 8 | bytes[2] & 0xff; 119 | } 120 | 121 | /** 122 | * 24 byte 123 | * @param bytes 124 | * @return 125 | */ 126 | public static int byteBigToInt16(byte[] bytes) { 127 | return 0x00 << 24| (bytes[0] & 0xff) << 16 | (bytes[0] & 0xff) << 8 | bytes[1] & 0xff; 128 | } 129 | 130 | /** 131 | * 将byte 转换为int 直接大端转换 132 | * @param bytes 133 | * @return 134 | */ 135 | public static int byteToInt(byte[] bytes) { 136 | return (bytes[0] & 0xff) << 24| (bytes[1] & 0xff) << 16 | (bytes[2] & 0xff) << 8 | bytes[3] & 0xff; 137 | } 138 | 139 | /** 140 | * 将byte 转换为 int 小端转换 141 | * @param bytes 142 | * @return 143 | */ 144 | public static int byteSmallToInt(byte[] bytes) { 145 | return (bytes[3] & 0xff) << 24| (bytes[2] & 0xff) << 16 | (bytes[1] & 0xff) << 8 | bytes[0] & 0xff; 146 | } 147 | 148 | /** 149 | * 将一个8位字节数组转换为双精度浮点数。
150 | * 注意,函数中不会对字节数组长度进行判断,请自行保证传入参数的正确性。 151 | * 152 | * @param b 153 | * 字节数组 154 | * @return 双精度浮点数 155 | */ 156 | public static double bytesToDouble(byte[] b) { 157 | return Double.longBitsToDouble(bytesToLong(b)); 158 | } 159 | 160 | /** 161 | * 将一个8位字节数组转换为长整数。
162 | * 注意,函数中不会对字节数组长度进行判断,请自行保证传入参数的正确性。 163 | * 164 | * @param b 165 | * 字节数组 166 | * @return 长整数 167 | */ 168 | public static long bytesToLong(byte[] b) { 169 | int doubleSize = 8; 170 | long l = 0; 171 | for (int i = 0; i < doubleSize; i++) { 172 | // 如果不强制转换为long,那么默认会当作int,导致最高32位丢失 173 | l |= ((long) b[i] << (8 * i)) & (0xFFL << (8 * i)); 174 | } 175 | 176 | return l; 177 | } 178 | 179 | /** 180 | * int 转换 为 byte 数组 181 | * @param num 182 | * @return 183 | */ 184 | public static byte[] int2Bytes(int num) { 185 | byte[] bytes = new byte[4]; 186 | //通过移位运算,截取低8位的方式,将int保存到byte数组 187 | bytes[0] = (byte)(num >>> 24); 188 | bytes[1] = (byte)(num >>> 16); 189 | bytes[2] = (byte)(num >>> 8); 190 | bytes[3] = (byte)num; 191 | return bytes; 192 | } 193 | 194 | public static byte[] double2Bytes(double d) { 195 | 196 | long value = Double.doubleToRawLongBits(d); 197 | 198 | byte[] byteRet = new byte[8]; 199 | 200 | for (int i = 0; i < 8; i++) { 201 | 202 | byteRet[i] = (byte) ((value >> 8 * i) & 0xff); 203 | 204 | } 205 | 206 | return byteRet; 207 | 208 | } 209 | 210 | public static byte[] writeUnsignedInt16(int value){ 211 | byte[] bytes = new byte[2]; 212 | bytes[0] = (byte) (value >>> 8); 213 | bytes[1] = (byte) value; 214 | return bytes; 215 | } 216 | 217 | /** 218 | * 将 List 转换为 数组 219 | * @param val 220 | * @return 221 | */ 222 | public static byte[] conversionByteArray(List val) { 223 | byte[] s1 = new byte[val.size()]; 224 | for(int i = 0; i < val.size();i++) { 225 | s1[i] = val.get(i); 226 | } 227 | return s1; 228 | } 229 | 230 | /** 231 | * 格式打印 二进制数据 232 | * @param bytes 233 | * @return 234 | */ 235 | public static String bytes2hex(byte[] bytes) { 236 | StringBuilder sb = new StringBuilder(); 237 | String tmp = null; 238 | int i = 1; 239 | for (byte b : bytes) { 240 | // 将每个字节与0xFF进行与运算,然后转化为10进制,然后借助于Integer再转化为16进制 241 | tmp = Integer.toHexString(0xFF & b); 242 | if (tmp.length() == 1) { 243 | tmp = "0" + tmp; 244 | } 245 | tmp += " "; 246 | if(i % 20 == 0) { 247 | tmp += "\r\n"; 248 | } 249 | sb.append(tmp); 250 | i++; 251 | } 252 | return sb.toString(); 253 | } 254 | 255 | 256 | /** 257 | * 数组倒置 258 | * @param Array 259 | * @return 260 | */ 261 | public static byte[] reverseArray(byte[] Array) { 262 | byte[] new_array = new byte[Array.length]; 263 | for (int i = 0; i < Array.length; i++) { 264 | // 反转后数组的第一个元素等于源数组的最后一个元素: 265 | new_array[i] = Array[Array.length - i - 1]; 266 | } 267 | return new_array; 268 | } 269 | 270 | /** 271 | * 删除元素 272 | * @param data 273 | * @param start 274 | * @param end 275 | */ 276 | public static List removeList(List data,int start,int end) { 277 | List newData = new ArrayList(); 278 | for(int i = 0; i < data.size();i++) { 279 | if(i < start || i > end){ 280 | newData.add(data.get(i)); 281 | } 282 | // 删除元素后,需要把下标减一。这是因为在每次删除元素后,ArrayList会将后面部分的元素依次往上挪一个位置(就是copy),所以,下一个需要访问的下标还是当前下标,所以必须得减一才能把所有元素都遍历完 283 | } 284 | return newData; 285 | } 286 | 287 | 288 | public static byte[] CreateADTS(AudioSpecificConfig audioSpecificConfig,int packLength) { 289 | byte[] adts = new byte[7]; 290 | int chanCfg = audioSpecificConfig.nChannels; 291 | adts[0] = (byte) 0xFF; 292 | adts[1] = (byte)0xF1; 293 | adts[2] = (byte)(((audioSpecificConfig.nAudioObjectType - 1) << 6) + (audioSpecificConfig.nSampleFrequencyIndex << 2) + (chanCfg >> 2)); 294 | adts[3] = (byte)(((chanCfg & 3) << 6) + (packLength >> 11)); 295 | adts[4] = (byte)((packLength & 0x7FF) >> 3); 296 | adts[5] = (byte) (((packLength & 0x7) << 5)|0x1f); 297 | adts[6] = (byte)0xFC; 298 | return adts; 299 | } 300 | 301 | } 302 | -------------------------------------------------------------------------------- /src/main/java/Util/MsgType.java: -------------------------------------------------------------------------------- 1 | package Util; 2 | 3 | public class MsgType { 4 | public static byte MSG_CHUNK_SIZE = 0x01; 5 | public static byte MSG_CONTROL = 0x14; 6 | public static byte MSG_NOTIFY = 0x12; 7 | public static byte MSG_USER_CONTROL = 0x04; 8 | public static byte MSG_AUDIO = 0x08; 9 | public static byte MSG_VIDEO = 0x09; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/test.java: -------------------------------------------------------------------------------- 1 | public class test { 2 | public static void main(String[] args) { 3 | String str = "04 00 09 94 00 01 87 08 39 05 00 00 af 01 21 1a cb dd b7 f3 8c 03 8f 88 b3 51 ae 7c f3 5c fb 64 e7 35 3b e3 2f c6 aa f9 92 66 55 5f 31 56 de 8a cb 16 7f bf 8e 37 a5 63 b8 aa ef 5c 56 62 74 75 7c de 1d 8d 56 db 2c 98 89 a7 06 fa 05 1f ce 6a 13 be c9 35 31 28 4c 89 73 e2 3d 13 da b3 a5 dd db 44 87 a4 7c c4 48 32 9f 7f 3e e0 e9 6d 4f 4e 35 73 96 70 9a 4a 08 62 60 97 94 2d 8e 79 5b c6 cb 16 99 26 1e ca 5a 09 f2 5b 26 26 c4 48 88 10 57 74 ac c1 d8 c9 a6 83 13 81 20 21 48 23 59 dd 0c 50 02 67 ca b4 35 a4 1c 2c 9e e8 6b 89 00 ae f8 1c 2c 21 1d 5d 85 1f 08 0c 17 7c df 50 54 98 28 ab 5a e8 e3 a3 33 42 bd 7c f9 57 56 cb 9b b9 de 1c 07 07 f0 7d d0 c0 2b a3 ed 16 58 ce 4d cb d5 13 4b 9f 51 c7 22 ae 1e 6f 33 63 38 ad 7e 0d f5 ac 7d 66 8d 7e 08 fe 5f 84 67 b2 df b2 b2 ea aa de c5 84 96 d1 3b e6 29 a8 c1 67 0f c4 98 a8 31 51 1e e7 5e 79 ae fe 32 6b e7 5e 7e dc 5e 78 d5 5f 33 c6 b5 94 bd ca 46 4b 62 09 6f 8a a8 64 ad 4c 6a ef 15 b0 a4 ac f5 93 9d 05 ad f4 9c 10 67 32 5e 9e 60 36 78 7e 16 fe 39 a8 42 38 35 1c e8 46 96 28 24 2a fa d1 f8 68 43 bd 9e 58 df ab 7d cb a1 45 e5 69 4f 5c a5 ea d4 bd ae 8b 7c 82 6f 66 8a 13 17 97 d4 9e f2 48 72 f4 d8 8c 4e 97 8f 72 8e 21 ad 81 6b 5f b8 ba 92 78 10 b8 c4 be f2 58 b5 70 78"; 4 | String[] arr = str.split(" "); 5 | String test = ""; 6 | int index = 0; 7 | for(int i =0; i < arr.length;i++) { 8 | test += arr[i] + " "; 9 | index++; 10 | if(index % 20 == 0) { 11 | System.out.println(test); 12 | test = ""; 13 | index = 0; 14 | } 15 | 16 | } 17 | System.out.println(test); 18 | } 19 | } 20 | 21 | 22 | -------------------------------------------------------------------------------- /target/classes/AMF/AMF.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/AMF/AMF.class -------------------------------------------------------------------------------- /target/classes/AMF/AMFClass.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/AMF/AMFClass.class -------------------------------------------------------------------------------- /target/classes/AMF/AMFUtil.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/AMF/AMFUtil.class -------------------------------------------------------------------------------- /target/classes/Decoder/AudioStreamDecoder.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Decoder/AudioStreamDecoder.class -------------------------------------------------------------------------------- /target/classes/Decoder/RtmpDecoder$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Decoder/RtmpDecoder$1.class -------------------------------------------------------------------------------- /target/classes/Decoder/RtmpDecoder.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Decoder/RtmpDecoder.class -------------------------------------------------------------------------------- /target/classes/Decoder/RtmpDecoderbak.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Decoder/RtmpDecoderbak.class -------------------------------------------------------------------------------- /target/classes/Decoder/SendData.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Decoder/SendData.class -------------------------------------------------------------------------------- /target/classes/Decoder/SendDecoder.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Decoder/SendDecoder.class -------------------------------------------------------------------------------- /target/classes/Decoder/VideoStreamDecoder.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Decoder/VideoStreamDecoder.class -------------------------------------------------------------------------------- /target/classes/EnCoder/RtmpEncoder.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/EnCoder/RtmpEncoder.class -------------------------------------------------------------------------------- /target/classes/Handler/RtmpHandler.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Handler/RtmpHandler.class -------------------------------------------------------------------------------- /target/classes/META-INF/rtmpServer.kotlin_module: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /target/classes/Rtmp$1.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Rtmp$1.class -------------------------------------------------------------------------------- /target/classes/Rtmp.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Rtmp.class -------------------------------------------------------------------------------- /target/classes/Rtmp/Amf.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Rtmp/Amf.class -------------------------------------------------------------------------------- /target/classes/Rtmp/RtmpAudio.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Rtmp/RtmpAudio.class -------------------------------------------------------------------------------- /target/classes/Rtmp/RtmpBytesRead.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Rtmp/RtmpBytesRead.class -------------------------------------------------------------------------------- /target/classes/Rtmp/RtmpChunk.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Rtmp/RtmpChunk.class -------------------------------------------------------------------------------- /target/classes/Rtmp/RtmpChunkSize.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Rtmp/RtmpChunkSize.class -------------------------------------------------------------------------------- /target/classes/Rtmp/RtmpControl.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Rtmp/RtmpControl.class -------------------------------------------------------------------------------- /target/classes/Rtmp/RtmpHandshake.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Rtmp/RtmpHandshake.class -------------------------------------------------------------------------------- /target/classes/Rtmp/RtmpMessage.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Rtmp/RtmpMessage.class -------------------------------------------------------------------------------- /target/classes/Rtmp/RtmpNot.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Rtmp/RtmpNot.class -------------------------------------------------------------------------------- /target/classes/Rtmp/RtmpNotify.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Rtmp/RtmpNotify.class -------------------------------------------------------------------------------- /target/classes/Rtmp/RtmpPacket.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Rtmp/RtmpPacket.class -------------------------------------------------------------------------------- /target/classes/Rtmp/RtmpPing.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Rtmp/RtmpPing.class -------------------------------------------------------------------------------- /target/classes/Rtmp/RtmpResponse.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Rtmp/RtmpResponse.class -------------------------------------------------------------------------------- /target/classes/Rtmp/RtmpServerWindow.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Rtmp/RtmpServerWindow.class -------------------------------------------------------------------------------- /target/classes/Rtmp/RtmpVideo.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Rtmp/RtmpVideo.class -------------------------------------------------------------------------------- /target/classes/User/Publish.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/User/Publish.class -------------------------------------------------------------------------------- /target/classes/User/PublishGroup.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/User/PublishGroup.class -------------------------------------------------------------------------------- /target/classes/User/Receive.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/User/Receive.class -------------------------------------------------------------------------------- /target/classes/User/ReceiveGroup.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/User/ReceiveGroup.class -------------------------------------------------------------------------------- /target/classes/Util/AACDecoderSpecific.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Util/AACDecoderSpecific.class -------------------------------------------------------------------------------- /target/classes/Util/AudioSpecificConfig.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Util/AudioSpecificConfig.class -------------------------------------------------------------------------------- /target/classes/Util/Common.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Util/Common.class -------------------------------------------------------------------------------- /target/classes/Util/MsgType.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/Util/MsgType.class -------------------------------------------------------------------------------- /target/classes/test.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1392492818/rtmpServer/584ca8c27c76b7d63bf40ad286424fef5086017c/target/classes/test.class --------------------------------------------------------------------------------