Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
6147 | serge | 1 | /* |
2 | * RTMP network protocol |
||
3 | * Copyright (c) 2009 Konstantin Shishkov |
||
4 | * |
||
5 | * This file is part of FFmpeg. |
||
6 | * |
||
7 | * FFmpeg is free software; you can redistribute it and/or |
||
8 | * modify it under the terms of the GNU Lesser General Public |
||
9 | * License as published by the Free Software Foundation; either |
||
10 | * version 2.1 of the License, or (at your option) any later version. |
||
11 | * |
||
12 | * FFmpeg is distributed in the hope that it will be useful, |
||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||
15 | * Lesser General Public License for more details. |
||
16 | * |
||
17 | * You should have received a copy of the GNU Lesser General Public |
||
18 | * License along with FFmpeg; if not, write to the Free Software |
||
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||
20 | */ |
||
21 | |||
22 | /** |
||
23 | * @file |
||
24 | * RTMP protocol |
||
25 | */ |
||
26 | |||
27 | #include "libavcodec/bytestream.h" |
||
28 | #include "libavutil/avstring.h" |
||
29 | #include "libavutil/base64.h" |
||
30 | #include "libavutil/hmac.h" |
||
31 | #include "libavutil/intfloat.h" |
||
32 | #include "libavutil/lfg.h" |
||
33 | #include "libavutil/md5.h" |
||
34 | #include "libavutil/opt.h" |
||
35 | #include "libavutil/random_seed.h" |
||
36 | #include "avformat.h" |
||
37 | #include "internal.h" |
||
38 | |||
39 | #include "network.h" |
||
40 | |||
41 | #include "flv.h" |
||
42 | #include "rtmp.h" |
||
43 | #include "rtmpcrypt.h" |
||
44 | #include "rtmppkt.h" |
||
45 | #include "url.h" |
||
46 | |||
47 | #if CONFIG_ZLIB |
||
48 | #include |
||
49 | #endif |
||
50 | |||
51 | #define APP_MAX_LENGTH 1024 |
||
52 | #define PLAYPATH_MAX_LENGTH 512 |
||
53 | #define TCURL_MAX_LENGTH 1024 |
||
54 | #define FLASHVER_MAX_LENGTH 64 |
||
55 | #define RTMP_PKTDATA_DEFAULT_SIZE 4096 |
||
56 | #define RTMP_HEADER 11 |
||
57 | |||
58 | /** RTMP protocol handler state */ |
||
59 | typedef enum { |
||
60 | STATE_START, ///< client has not done anything yet |
||
61 | STATE_HANDSHAKED, ///< client has performed handshake |
||
62 | STATE_FCPUBLISH, ///< client FCPublishing stream (for output) |
||
63 | STATE_PLAYING, ///< client has started receiving multimedia data from server |
||
64 | STATE_SEEKING, ///< client has started the seek operation. Back on STATE_PLAYING when the time comes |
||
65 | STATE_PUBLISHING, ///< client has started sending multimedia data to server (for output) |
||
66 | STATE_RECEIVING, ///< received a publish command (for input) |
||
67 | STATE_SENDING, ///< received a play command (for output) |
||
68 | STATE_STOPPED, ///< the broadcast has been stopped |
||
69 | } ClientState; |
||
70 | |||
71 | typedef struct TrackedMethod { |
||
72 | char *name; |
||
73 | int id; |
||
74 | } TrackedMethod; |
||
75 | |||
76 | /** protocol handler context */ |
||
77 | typedef struct RTMPContext { |
||
78 | const AVClass *class; |
||
79 | URLContext* stream; ///< TCP stream used in interactions with RTMP server |
||
80 | RTMPPacket *prev_pkt[2]; ///< packet history used when reading and sending packets ([0] for reading, [1] for writing) |
||
81 | int nb_prev_pkt[2]; ///< number of elements in prev_pkt |
||
82 | int in_chunk_size; ///< size of the chunks incoming RTMP packets are divided into |
||
83 | int out_chunk_size; ///< size of the chunks outgoing RTMP packets are divided into |
||
84 | int is_input; ///< input/output flag |
||
85 | char *playpath; ///< stream identifier to play (with possible "mp4:" prefix) |
||
86 | int live; ///< 0: recorded, -1: live, -2: both |
||
87 | char *app; ///< name of application |
||
88 | char *conn; ///< append arbitrary AMF data to the Connect message |
||
89 | ClientState state; ///< current state |
||
90 | int stream_id; ///< ID assigned by the server for the stream |
||
91 | uint8_t* flv_data; ///< buffer with data for demuxer |
||
92 | int flv_size; ///< current buffer size |
||
93 | int flv_off; ///< number of bytes read from current buffer |
||
94 | int flv_nb_packets; ///< number of flv packets published |
||
95 | RTMPPacket out_pkt; ///< rtmp packet, created from flv a/v or metadata (for output) |
||
96 | uint32_t client_report_size; ///< number of bytes after which client should report to server |
||
97 | uint32_t bytes_read; ///< number of bytes read from server |
||
98 | uint32_t last_bytes_read; ///< number of bytes read last reported to server |
||
99 | uint32_t last_timestamp; ///< last timestamp received in a packet |
||
100 | int skip_bytes; ///< number of bytes to skip from the input FLV stream in the next write call |
||
101 | int has_audio; ///< presence of audio data |
||
102 | int has_video; ///< presence of video data |
||
103 | int received_metadata; ///< Indicates if we have received metadata about the streams |
||
104 | uint8_t flv_header[RTMP_HEADER]; ///< partial incoming flv packet header |
||
105 | int flv_header_bytes; ///< number of initialized bytes in flv_header |
||
106 | int nb_invokes; ///< keeps track of invoke messages |
||
107 | char* tcurl; ///< url of the target stream |
||
108 | char* flashver; ///< version of the flash plugin |
||
109 | char* swfhash; ///< SHA256 hash of the decompressed SWF file (32 bytes) |
||
110 | int swfhash_len; ///< length of the SHA256 hash |
||
111 | int swfsize; ///< size of the decompressed SWF file |
||
112 | char* swfurl; ///< url of the swf player |
||
113 | char* swfverify; ///< URL to player swf file, compute hash/size automatically |
||
114 | char swfverification[42]; ///< hash of the SWF verification |
||
115 | char* pageurl; ///< url of the web page |
||
116 | char* subscribe; ///< name of live stream to subscribe |
||
117 | int server_bw; ///< server bandwidth |
||
118 | int client_buffer_time; ///< client buffer time in ms |
||
119 | int flush_interval; ///< number of packets flushed in the same request (RTMPT only) |
||
120 | int encrypted; ///< use an encrypted connection (RTMPE only) |
||
121 | TrackedMethod*tracked_methods; ///< tracked methods buffer |
||
122 | int nb_tracked_methods; ///< number of tracked methods |
||
123 | int tracked_methods_size; ///< size of the tracked methods buffer |
||
124 | int listen; ///< listen mode flag |
||
125 | int listen_timeout; ///< listen timeout to wait for new connections |
||
126 | int nb_streamid; ///< The next stream id to return on createStream calls |
||
127 | double duration; ///< Duration of the stream in seconds as returned by the server (only valid if non-zero) |
||
128 | char username[50]; |
||
129 | char password[50]; |
||
130 | char auth_params[500]; |
||
131 | int do_reconnect; |
||
132 | int auth_tried; |
||
133 | } RTMPContext; |
||
134 | |||
135 | #define PLAYER_KEY_OPEN_PART_LEN 30 ///< length of partial key used for first client digest signing |
||
136 | /** Client key used for digest signing */ |
||
137 | static const uint8_t rtmp_player_key[] = { |
||
138 | 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', |
||
139 | 'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ', '0', '0', '1', |
||
140 | |||
141 | 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, 0x02, |
||
142 | 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, 0x93, 0xB8, |
||
143 | 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE |
||
144 | }; |
||
145 | |||
146 | #define SERVER_KEY_OPEN_PART_LEN 36 ///< length of partial key used for first server digest signing |
||
147 | /** Key used for RTMP server digest signing */ |
||
148 | static const uint8_t rtmp_server_key[] = { |
||
149 | 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', |
||
150 | 'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ', |
||
151 | 'S', 'e', 'r', 'v', 'e', 'r', ' ', '0', '0', '1', |
||
152 | |||
153 | 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, 0x02, |
||
154 | 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, 0x93, 0xB8, |
||
155 | 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE |
||
156 | }; |
||
157 | |||
158 | static int handle_chunk_size(URLContext *s, RTMPPacket *pkt); |
||
159 | |||
160 | static int add_tracked_method(RTMPContext *rt, const char *name, int id) |
||
161 | { |
||
162 | int err; |
||
163 | |||
164 | if (rt->nb_tracked_methods + 1 > rt->tracked_methods_size) { |
||
165 | rt->tracked_methods_size = (rt->nb_tracked_methods + 1) * 2; |
||
166 | if ((err = av_reallocp(&rt->tracked_methods, rt->tracked_methods_size * |
||
167 | sizeof(*rt->tracked_methods))) < 0) { |
||
168 | rt->nb_tracked_methods = 0; |
||
169 | rt->tracked_methods_size = 0; |
||
170 | return err; |
||
171 | } |
||
172 | } |
||
173 | |||
174 | rt->tracked_methods[rt->nb_tracked_methods].name = av_strdup(name); |
||
175 | if (!rt->tracked_methods[rt->nb_tracked_methods].name) |
||
176 | return AVERROR(ENOMEM); |
||
177 | rt->tracked_methods[rt->nb_tracked_methods].id = id; |
||
178 | rt->nb_tracked_methods++; |
||
179 | |||
180 | return 0; |
||
181 | } |
||
182 | |||
183 | static void del_tracked_method(RTMPContext *rt, int index) |
||
184 | { |
||
185 | memmove(&rt->tracked_methods[index], &rt->tracked_methods[index + 1], |
||
186 | sizeof(*rt->tracked_methods) * (rt->nb_tracked_methods - index - 1)); |
||
187 | rt->nb_tracked_methods--; |
||
188 | } |
||
189 | |||
190 | static int find_tracked_method(URLContext *s, RTMPPacket *pkt, int offset, |
||
191 | char **tracked_method) |
||
192 | { |
||
193 | RTMPContext *rt = s->priv_data; |
||
194 | GetByteContext gbc; |
||
195 | double pkt_id; |
||
196 | int ret; |
||
197 | int i; |
||
198 | |||
199 | bytestream2_init(&gbc, pkt->data + offset, pkt->size - offset); |
||
200 | if ((ret = ff_amf_read_number(&gbc, &pkt_id)) < 0) |
||
201 | return ret; |
||
202 | |||
203 | for (i = 0; i < rt->nb_tracked_methods; i++) { |
||
204 | if (rt->tracked_methods[i].id != pkt_id) |
||
205 | continue; |
||
206 | |||
207 | *tracked_method = rt->tracked_methods[i].name; |
||
208 | del_tracked_method(rt, i); |
||
209 | break; |
||
210 | } |
||
211 | |||
212 | return 0; |
||
213 | } |
||
214 | |||
215 | static void free_tracked_methods(RTMPContext *rt) |
||
216 | { |
||
217 | int i; |
||
218 | |||
219 | for (i = 0; i < rt->nb_tracked_methods; i ++) |
||
220 | av_freep(&rt->tracked_methods[i].name); |
||
221 | av_freep(&rt->tracked_methods); |
||
222 | rt->tracked_methods_size = 0; |
||
223 | rt->nb_tracked_methods = 0; |
||
224 | } |
||
225 | |||
226 | static int rtmp_send_packet(RTMPContext *rt, RTMPPacket *pkt, int track) |
||
227 | { |
||
228 | int ret; |
||
229 | |||
230 | if (pkt->type == RTMP_PT_INVOKE && track) { |
||
231 | GetByteContext gbc; |
||
232 | char name[128]; |
||
233 | double pkt_id; |
||
234 | int len; |
||
235 | |||
236 | bytestream2_init(&gbc, pkt->data, pkt->size); |
||
237 | if ((ret = ff_amf_read_string(&gbc, name, sizeof(name), &len)) < 0) |
||
238 | goto fail; |
||
239 | |||
240 | if ((ret = ff_amf_read_number(&gbc, &pkt_id)) < 0) |
||
241 | goto fail; |
||
242 | |||
243 | if ((ret = add_tracked_method(rt, name, pkt_id)) < 0) |
||
244 | goto fail; |
||
245 | } |
||
246 | |||
247 | ret = ff_rtmp_packet_write(rt->stream, pkt, rt->out_chunk_size, |
||
248 | &rt->prev_pkt[1], &rt->nb_prev_pkt[1]); |
||
249 | fail: |
||
250 | ff_rtmp_packet_destroy(pkt); |
||
251 | return ret; |
||
252 | } |
||
253 | |||
254 | static int rtmp_write_amf_data(URLContext *s, char *param, uint8_t **p) |
||
255 | { |
||
256 | char *field, *value; |
||
257 | char type; |
||
258 | |||
259 | /* The type must be B for Boolean, N for number, S for string, O for |
||
260 | * object, or Z for null. For Booleans the data must be either 0 or 1 for |
||
261 | * FALSE or TRUE, respectively. Likewise for Objects the data must be |
||
262 | * 0 or 1 to end or begin an object, respectively. Data items in subobjects |
||
263 | * may be named, by prefixing the type with 'N' and specifying the name |
||
264 | * before the value (ie. NB:myFlag:1). This option may be used multiple times |
||
265 | * to construct arbitrary AMF sequences. */ |
||
266 | if (param[0] && param[1] == ':') { |
||
267 | type = param[0]; |
||
268 | value = param + 2; |
||
269 | } else if (param[0] == 'N' && param[1] && param[2] == ':') { |
||
270 | type = param[1]; |
||
271 | field = param + 3; |
||
272 | value = strchr(field, ':'); |
||
273 | if (!value) |
||
274 | goto fail; |
||
275 | *value = '\0'; |
||
276 | value++; |
||
277 | |||
278 | ff_amf_write_field_name(p, field); |
||
279 | } else { |
||
280 | goto fail; |
||
281 | } |
||
282 | |||
283 | switch (type) { |
||
284 | case 'B': |
||
285 | ff_amf_write_bool(p, value[0] != '0'); |
||
286 | break; |
||
287 | case 'S': |
||
288 | ff_amf_write_string(p, value); |
||
289 | break; |
||
290 | case 'N': |
||
291 | ff_amf_write_number(p, strtod(value, NULL)); |
||
292 | break; |
||
293 | case 'Z': |
||
294 | ff_amf_write_null(p); |
||
295 | break; |
||
296 | case 'O': |
||
297 | if (value[0] != '0') |
||
298 | ff_amf_write_object_start(p); |
||
299 | else |
||
300 | ff_amf_write_object_end(p); |
||
301 | break; |
||
302 | default: |
||
303 | goto fail; |
||
304 | break; |
||
305 | } |
||
306 | |||
307 | return 0; |
||
308 | |||
309 | fail: |
||
310 | av_log(s, AV_LOG_ERROR, "Invalid AMF parameter: %s\n", param); |
||
311 | return AVERROR(EINVAL); |
||
312 | } |
||
313 | |||
314 | /** |
||
315 | * Generate 'connect' call and send it to the server. |
||
316 | */ |
||
317 | static int gen_connect(URLContext *s, RTMPContext *rt) |
||
318 | { |
||
319 | RTMPPacket pkt; |
||
320 | uint8_t *p; |
||
321 | int ret; |
||
322 | |||
323 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, |
||
324 | 0, 4096 + APP_MAX_LENGTH)) < 0) |
||
325 | return ret; |
||
326 | |||
327 | p = pkt.data; |
||
328 | |||
329 | ff_amf_write_string(&p, "connect"); |
||
330 | ff_amf_write_number(&p, ++rt->nb_invokes); |
||
331 | ff_amf_write_object_start(&p); |
||
332 | ff_amf_write_field_name(&p, "app"); |
||
333 | ff_amf_write_string2(&p, rt->app, rt->auth_params); |
||
334 | |||
335 | if (!rt->is_input) { |
||
336 | ff_amf_write_field_name(&p, "type"); |
||
337 | ff_amf_write_string(&p, "nonprivate"); |
||
338 | } |
||
339 | ff_amf_write_field_name(&p, "flashVer"); |
||
340 | ff_amf_write_string(&p, rt->flashver); |
||
341 | |||
342 | if (rt->swfurl) { |
||
343 | ff_amf_write_field_name(&p, "swfUrl"); |
||
344 | ff_amf_write_string(&p, rt->swfurl); |
||
345 | } |
||
346 | |||
347 | ff_amf_write_field_name(&p, "tcUrl"); |
||
348 | ff_amf_write_string2(&p, rt->tcurl, rt->auth_params); |
||
349 | if (rt->is_input) { |
||
350 | ff_amf_write_field_name(&p, "fpad"); |
||
351 | ff_amf_write_bool(&p, 0); |
||
352 | ff_amf_write_field_name(&p, "capabilities"); |
||
353 | ff_amf_write_number(&p, 15.0); |
||
354 | |||
355 | /* Tell the server we support all the audio codecs except |
||
356 | * SUPPORT_SND_INTEL (0x0008) and SUPPORT_SND_UNUSED (0x0010) |
||
357 | * which are unused in the RTMP protocol implementation. */ |
||
358 | ff_amf_write_field_name(&p, "audioCodecs"); |
||
359 | ff_amf_write_number(&p, 4071.0); |
||
360 | ff_amf_write_field_name(&p, "videoCodecs"); |
||
361 | ff_amf_write_number(&p, 252.0); |
||
362 | ff_amf_write_field_name(&p, "videoFunction"); |
||
363 | ff_amf_write_number(&p, 1.0); |
||
364 | |||
365 | if (rt->pageurl) { |
||
366 | ff_amf_write_field_name(&p, "pageUrl"); |
||
367 | ff_amf_write_string(&p, rt->pageurl); |
||
368 | } |
||
369 | } |
||
370 | ff_amf_write_object_end(&p); |
||
371 | |||
372 | if (rt->conn) { |
||
373 | char *param = rt->conn; |
||
374 | |||
375 | // Write arbitrary AMF data to the Connect message. |
||
376 | while (param) { |
||
377 | char *sep; |
||
378 | param += strspn(param, " "); |
||
379 | if (!*param) |
||
380 | break; |
||
381 | sep = strchr(param, ' '); |
||
382 | if (sep) |
||
383 | *sep = '\0'; |
||
384 | if ((ret = rtmp_write_amf_data(s, param, &p)) < 0) { |
||
385 | // Invalid AMF parameter. |
||
386 | ff_rtmp_packet_destroy(&pkt); |
||
387 | return ret; |
||
388 | } |
||
389 | |||
390 | if (sep) |
||
391 | param = sep + 1; |
||
392 | else |
||
393 | break; |
||
394 | } |
||
395 | } |
||
396 | |||
397 | pkt.size = p - pkt.data; |
||
398 | |||
399 | return rtmp_send_packet(rt, &pkt, 1); |
||
400 | } |
||
401 | |||
402 | static int read_connect(URLContext *s, RTMPContext *rt) |
||
403 | { |
||
404 | RTMPPacket pkt = { 0 }; |
||
405 | uint8_t *p; |
||
406 | const uint8_t *cp; |
||
407 | int ret; |
||
408 | char command[64]; |
||
409 | int stringlen; |
||
410 | double seqnum; |
||
411 | uint8_t tmpstr[256]; |
||
412 | GetByteContext gbc; |
||
413 | |||
414 | if ((ret = ff_rtmp_packet_read(rt->stream, &pkt, rt->in_chunk_size, |
||
415 | &rt->prev_pkt[0], &rt->nb_prev_pkt[0])) < 0) |
||
416 | return ret; |
||
417 | |||
418 | if (pkt.type == RTMP_PT_CHUNK_SIZE) { |
||
419 | if ((ret = handle_chunk_size(s, &pkt)) < 0) |
||
420 | return ret; |
||
421 | |||
422 | ff_rtmp_packet_destroy(&pkt); |
||
423 | if ((ret = ff_rtmp_packet_read(rt->stream, &pkt, rt->in_chunk_size, |
||
424 | &rt->prev_pkt[0], &rt->nb_prev_pkt[0])) < 0) |
||
425 | return ret; |
||
426 | } |
||
427 | |||
428 | cp = pkt.data; |
||
429 | bytestream2_init(&gbc, cp, pkt.size); |
||
430 | if (ff_amf_read_string(&gbc, command, sizeof(command), &stringlen)) { |
||
431 | av_log(s, AV_LOG_ERROR, "Unable to read command string\n"); |
||
432 | ff_rtmp_packet_destroy(&pkt); |
||
433 | return AVERROR_INVALIDDATA; |
||
434 | } |
||
435 | if (strcmp(command, "connect")) { |
||
436 | av_log(s, AV_LOG_ERROR, "Expecting connect, got %s\n", command); |
||
437 | ff_rtmp_packet_destroy(&pkt); |
||
438 | return AVERROR_INVALIDDATA; |
||
439 | } |
||
440 | ret = ff_amf_read_number(&gbc, &seqnum); |
||
441 | if (ret) |
||
442 | av_log(s, AV_LOG_WARNING, "SeqNum not found\n"); |
||
443 | /* Here one could parse an AMF Object with data as flashVers and others. */ |
||
444 | ret = ff_amf_get_field_value(gbc.buffer, |
||
445 | gbc.buffer + bytestream2_get_bytes_left(&gbc), |
||
446 | "app", tmpstr, sizeof(tmpstr)); |
||
447 | if (ret) |
||
448 | av_log(s, AV_LOG_WARNING, "App field not found in connect\n"); |
||
449 | if (!ret && strcmp(tmpstr, rt->app)) |
||
450 | av_log(s, AV_LOG_WARNING, "App field don't match up: %s <-> %s\n", |
||
451 | tmpstr, rt->app); |
||
452 | ff_rtmp_packet_destroy(&pkt); |
||
453 | |||
454 | // Send Window Acknowledgement Size (as defined in speficication) |
||
455 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, |
||
456 | RTMP_PT_SERVER_BW, 0, 4)) < 0) |
||
457 | return ret; |
||
458 | p = pkt.data; |
||
459 | bytestream_put_be32(&p, rt->server_bw); |
||
460 | pkt.size = p - pkt.data; |
||
461 | ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size, |
||
462 | &rt->prev_pkt[1], &rt->nb_prev_pkt[1]); |
||
463 | ff_rtmp_packet_destroy(&pkt); |
||
464 | if (ret < 0) |
||
465 | return ret; |
||
466 | // Send Peer Bandwidth |
||
467 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, |
||
468 | RTMP_PT_CLIENT_BW, 0, 5)) < 0) |
||
469 | return ret; |
||
470 | p = pkt.data; |
||
471 | bytestream_put_be32(&p, rt->server_bw); |
||
472 | bytestream_put_byte(&p, 2); // dynamic |
||
473 | pkt.size = p - pkt.data; |
||
474 | ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size, |
||
475 | &rt->prev_pkt[1], &rt->nb_prev_pkt[1]); |
||
476 | ff_rtmp_packet_destroy(&pkt); |
||
477 | if (ret < 0) |
||
478 | return ret; |
||
479 | |||
480 | // Ping request |
||
481 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, |
||
482 | RTMP_PT_PING, 0, 6)) < 0) |
||
483 | return ret; |
||
484 | |||
485 | p = pkt.data; |
||
486 | bytestream_put_be16(&p, 0); // 0 -> Stream Begin |
||
487 | bytestream_put_be32(&p, 0); |
||
488 | ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size, |
||
489 | &rt->prev_pkt[1], &rt->nb_prev_pkt[1]); |
||
490 | ff_rtmp_packet_destroy(&pkt); |
||
491 | if (ret < 0) |
||
492 | return ret; |
||
493 | |||
494 | // Chunk size |
||
495 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, |
||
496 | RTMP_PT_CHUNK_SIZE, 0, 4)) < 0) |
||
497 | return ret; |
||
498 | |||
499 | p = pkt.data; |
||
500 | bytestream_put_be32(&p, rt->out_chunk_size); |
||
501 | ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size, |
||
502 | &rt->prev_pkt[1], &rt->nb_prev_pkt[1]); |
||
503 | ff_rtmp_packet_destroy(&pkt); |
||
504 | if (ret < 0) |
||
505 | return ret; |
||
506 | |||
507 | // Send _result NetConnection.Connect.Success to connect |
||
508 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, |
||
509 | RTMP_PT_INVOKE, 0, |
||
510 | RTMP_PKTDATA_DEFAULT_SIZE)) < 0) |
||
511 | return ret; |
||
512 | |||
513 | p = pkt.data; |
||
514 | ff_amf_write_string(&p, "_result"); |
||
515 | ff_amf_write_number(&p, seqnum); |
||
516 | |||
517 | ff_amf_write_object_start(&p); |
||
518 | ff_amf_write_field_name(&p, "fmsVer"); |
||
519 | ff_amf_write_string(&p, "FMS/3,0,1,123"); |
||
520 | ff_amf_write_field_name(&p, "capabilities"); |
||
521 | ff_amf_write_number(&p, 31); |
||
522 | ff_amf_write_object_end(&p); |
||
523 | |||
524 | ff_amf_write_object_start(&p); |
||
525 | ff_amf_write_field_name(&p, "level"); |
||
526 | ff_amf_write_string(&p, "status"); |
||
527 | ff_amf_write_field_name(&p, "code"); |
||
528 | ff_amf_write_string(&p, "NetConnection.Connect.Success"); |
||
529 | ff_amf_write_field_name(&p, "description"); |
||
530 | ff_amf_write_string(&p, "Connection succeeded."); |
||
531 | ff_amf_write_field_name(&p, "objectEncoding"); |
||
532 | ff_amf_write_number(&p, 0); |
||
533 | ff_amf_write_object_end(&p); |
||
534 | |||
535 | pkt.size = p - pkt.data; |
||
536 | ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size, |
||
537 | &rt->prev_pkt[1], &rt->nb_prev_pkt[1]); |
||
538 | ff_rtmp_packet_destroy(&pkt); |
||
539 | if (ret < 0) |
||
540 | return ret; |
||
541 | |||
542 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, |
||
543 | RTMP_PT_INVOKE, 0, 30)) < 0) |
||
544 | return ret; |
||
545 | p = pkt.data; |
||
546 | ff_amf_write_string(&p, "onBWDone"); |
||
547 | ff_amf_write_number(&p, 0); |
||
548 | ff_amf_write_null(&p); |
||
549 | ff_amf_write_number(&p, 8192); |
||
550 | pkt.size = p - pkt.data; |
||
551 | ret = ff_rtmp_packet_write(rt->stream, &pkt, rt->out_chunk_size, |
||
552 | &rt->prev_pkt[1], &rt->nb_prev_pkt[1]); |
||
553 | ff_rtmp_packet_destroy(&pkt); |
||
554 | |||
555 | return ret; |
||
556 | } |
||
557 | |||
558 | /** |
||
559 | * Generate 'releaseStream' call and send it to the server. It should make |
||
560 | * the server release some channel for media streams. |
||
561 | */ |
||
562 | static int gen_release_stream(URLContext *s, RTMPContext *rt) |
||
563 | { |
||
564 | RTMPPacket pkt; |
||
565 | uint8_t *p; |
||
566 | int ret; |
||
567 | |||
568 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, |
||
569 | 0, 29 + strlen(rt->playpath))) < 0) |
||
570 | return ret; |
||
571 | |||
572 | av_log(s, AV_LOG_DEBUG, "Releasing stream...\n"); |
||
573 | p = pkt.data; |
||
574 | ff_amf_write_string(&p, "releaseStream"); |
||
575 | ff_amf_write_number(&p, ++rt->nb_invokes); |
||
576 | ff_amf_write_null(&p); |
||
577 | ff_amf_write_string(&p, rt->playpath); |
||
578 | |||
579 | return rtmp_send_packet(rt, &pkt, 1); |
||
580 | } |
||
581 | |||
582 | /** |
||
583 | * Generate 'FCPublish' call and send it to the server. It should make |
||
584 | * the server preapare for receiving media streams. |
||
585 | */ |
||
586 | static int gen_fcpublish_stream(URLContext *s, RTMPContext *rt) |
||
587 | { |
||
588 | RTMPPacket pkt; |
||
589 | uint8_t *p; |
||
590 | int ret; |
||
591 | |||
592 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, |
||
593 | 0, 25 + strlen(rt->playpath))) < 0) |
||
594 | return ret; |
||
595 | |||
596 | av_log(s, AV_LOG_DEBUG, "FCPublish stream...\n"); |
||
597 | p = pkt.data; |
||
598 | ff_amf_write_string(&p, "FCPublish"); |
||
599 | ff_amf_write_number(&p, ++rt->nb_invokes); |
||
600 | ff_amf_write_null(&p); |
||
601 | ff_amf_write_string(&p, rt->playpath); |
||
602 | |||
603 | return rtmp_send_packet(rt, &pkt, 1); |
||
604 | } |
||
605 | |||
606 | /** |
||
607 | * Generate 'FCUnpublish' call and send it to the server. It should make |
||
608 | * the server destroy stream. |
||
609 | */ |
||
610 | static int gen_fcunpublish_stream(URLContext *s, RTMPContext *rt) |
||
611 | { |
||
612 | RTMPPacket pkt; |
||
613 | uint8_t *p; |
||
614 | int ret; |
||
615 | |||
616 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, |
||
617 | 0, 27 + strlen(rt->playpath))) < 0) |
||
618 | return ret; |
||
619 | |||
620 | av_log(s, AV_LOG_DEBUG, "UnPublishing stream...\n"); |
||
621 | p = pkt.data; |
||
622 | ff_amf_write_string(&p, "FCUnpublish"); |
||
623 | ff_amf_write_number(&p, ++rt->nb_invokes); |
||
624 | ff_amf_write_null(&p); |
||
625 | ff_amf_write_string(&p, rt->playpath); |
||
626 | |||
627 | return rtmp_send_packet(rt, &pkt, 0); |
||
628 | } |
||
629 | |||
630 | /** |
||
631 | * Generate 'createStream' call and send it to the server. It should make |
||
632 | * the server allocate some channel for media streams. |
||
633 | */ |
||
634 | static int gen_create_stream(URLContext *s, RTMPContext *rt) |
||
635 | { |
||
636 | RTMPPacket pkt; |
||
637 | uint8_t *p; |
||
638 | int ret; |
||
639 | |||
640 | av_log(s, AV_LOG_DEBUG, "Creating stream...\n"); |
||
641 | |||
642 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, |
||
643 | 0, 25)) < 0) |
||
644 | return ret; |
||
645 | |||
646 | p = pkt.data; |
||
647 | ff_amf_write_string(&p, "createStream"); |
||
648 | ff_amf_write_number(&p, ++rt->nb_invokes); |
||
649 | ff_amf_write_null(&p); |
||
650 | |||
651 | return rtmp_send_packet(rt, &pkt, 1); |
||
652 | } |
||
653 | |||
654 | |||
655 | /** |
||
656 | * Generate 'deleteStream' call and send it to the server. It should make |
||
657 | * the server remove some channel for media streams. |
||
658 | */ |
||
659 | static int gen_delete_stream(URLContext *s, RTMPContext *rt) |
||
660 | { |
||
661 | RTMPPacket pkt; |
||
662 | uint8_t *p; |
||
663 | int ret; |
||
664 | |||
665 | av_log(s, AV_LOG_DEBUG, "Deleting stream...\n"); |
||
666 | |||
667 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, |
||
668 | 0, 34)) < 0) |
||
669 | return ret; |
||
670 | |||
671 | p = pkt.data; |
||
672 | ff_amf_write_string(&p, "deleteStream"); |
||
673 | ff_amf_write_number(&p, ++rt->nb_invokes); |
||
674 | ff_amf_write_null(&p); |
||
675 | ff_amf_write_number(&p, rt->stream_id); |
||
676 | |||
677 | return rtmp_send_packet(rt, &pkt, 0); |
||
678 | } |
||
679 | |||
680 | /** |
||
681 | * Generate 'getStreamLength' call and send it to the server. If the server |
||
682 | * knows the duration of the selected stream, it will reply with the duration |
||
683 | * in seconds. |
||
684 | */ |
||
685 | static int gen_get_stream_length(URLContext *s, RTMPContext *rt) |
||
686 | { |
||
687 | RTMPPacket pkt; |
||
688 | uint8_t *p; |
||
689 | int ret; |
||
690 | |||
691 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SOURCE_CHANNEL, RTMP_PT_INVOKE, |
||
692 | 0, 31 + strlen(rt->playpath))) < 0) |
||
693 | return ret; |
||
694 | |||
695 | p = pkt.data; |
||
696 | ff_amf_write_string(&p, "getStreamLength"); |
||
697 | ff_amf_write_number(&p, ++rt->nb_invokes); |
||
698 | ff_amf_write_null(&p); |
||
699 | ff_amf_write_string(&p, rt->playpath); |
||
700 | |||
701 | return rtmp_send_packet(rt, &pkt, 1); |
||
702 | } |
||
703 | |||
704 | /** |
||
705 | * Generate client buffer time and send it to the server. |
||
706 | */ |
||
707 | static int gen_buffer_time(URLContext *s, RTMPContext *rt) |
||
708 | { |
||
709 | RTMPPacket pkt; |
||
710 | uint8_t *p; |
||
711 | int ret; |
||
712 | |||
713 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING, |
||
714 | 1, 10)) < 0) |
||
715 | return ret; |
||
716 | |||
717 | p = pkt.data; |
||
718 | bytestream_put_be16(&p, 3); |
||
719 | bytestream_put_be32(&p, rt->stream_id); |
||
720 | bytestream_put_be32(&p, rt->client_buffer_time); |
||
721 | |||
722 | return rtmp_send_packet(rt, &pkt, 0); |
||
723 | } |
||
724 | |||
725 | /** |
||
726 | * Generate 'play' call and send it to the server, then ping the server |
||
727 | * to start actual playing. |
||
728 | */ |
||
729 | static int gen_play(URLContext *s, RTMPContext *rt) |
||
730 | { |
||
731 | RTMPPacket pkt; |
||
732 | uint8_t *p; |
||
733 | int ret; |
||
734 | |||
735 | av_log(s, AV_LOG_DEBUG, "Sending play command for '%s'\n", rt->playpath); |
||
736 | |||
737 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SOURCE_CHANNEL, RTMP_PT_INVOKE, |
||
738 | 0, 29 + strlen(rt->playpath))) < 0) |
||
739 | return ret; |
||
740 | |||
741 | pkt.extra = rt->stream_id; |
||
742 | |||
743 | p = pkt.data; |
||
744 | ff_amf_write_string(&p, "play"); |
||
745 | ff_amf_write_number(&p, ++rt->nb_invokes); |
||
746 | ff_amf_write_null(&p); |
||
747 | ff_amf_write_string(&p, rt->playpath); |
||
748 | ff_amf_write_number(&p, rt->live * 1000); |
||
749 | |||
750 | return rtmp_send_packet(rt, &pkt, 1); |
||
751 | } |
||
752 | |||
753 | static int gen_seek(URLContext *s, RTMPContext *rt, int64_t timestamp) |
||
754 | { |
||
755 | RTMPPacket pkt; |
||
756 | uint8_t *p; |
||
757 | int ret; |
||
758 | |||
759 | av_log(s, AV_LOG_DEBUG, "Sending seek command for timestamp %"PRId64"\n", |
||
760 | timestamp); |
||
761 | |||
762 | if ((ret = ff_rtmp_packet_create(&pkt, 3, RTMP_PT_INVOKE, 0, 26)) < 0) |
||
763 | return ret; |
||
764 | |||
765 | pkt.extra = rt->stream_id; |
||
766 | |||
767 | p = pkt.data; |
||
768 | ff_amf_write_string(&p, "seek"); |
||
769 | ff_amf_write_number(&p, 0); //no tracking back responses |
||
770 | ff_amf_write_null(&p); //as usual, the first null param |
||
771 | ff_amf_write_number(&p, timestamp); //where we want to jump |
||
772 | |||
773 | return rtmp_send_packet(rt, &pkt, 1); |
||
774 | } |
||
775 | |||
776 | /** |
||
777 | * Generate a pause packet that either pauses or unpauses the current stream. |
||
778 | */ |
||
779 | static int gen_pause(URLContext *s, RTMPContext *rt, int pause, uint32_t timestamp) |
||
780 | { |
||
781 | RTMPPacket pkt; |
||
782 | uint8_t *p; |
||
783 | int ret; |
||
784 | |||
785 | av_log(s, AV_LOG_DEBUG, "Sending pause command for timestamp %d\n", |
||
786 | timestamp); |
||
787 | |||
788 | if ((ret = ff_rtmp_packet_create(&pkt, 3, RTMP_PT_INVOKE, 0, 29)) < 0) |
||
789 | return ret; |
||
790 | |||
791 | pkt.extra = rt->stream_id; |
||
792 | |||
793 | p = pkt.data; |
||
794 | ff_amf_write_string(&p, "pause"); |
||
795 | ff_amf_write_number(&p, 0); //no tracking back responses |
||
796 | ff_amf_write_null(&p); //as usual, the first null param |
||
797 | ff_amf_write_bool(&p, pause); // pause or unpause |
||
798 | ff_amf_write_number(&p, timestamp); //where we pause the stream |
||
799 | |||
800 | return rtmp_send_packet(rt, &pkt, 1); |
||
801 | } |
||
802 | |||
803 | /** |
||
804 | * Generate 'publish' call and send it to the server. |
||
805 | */ |
||
806 | static int gen_publish(URLContext *s, RTMPContext *rt) |
||
807 | { |
||
808 | RTMPPacket pkt; |
||
809 | uint8_t *p; |
||
810 | int ret; |
||
811 | |||
812 | av_log(s, AV_LOG_DEBUG, "Sending publish command for '%s'\n", rt->playpath); |
||
813 | |||
814 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SOURCE_CHANNEL, RTMP_PT_INVOKE, |
||
815 | 0, 30 + strlen(rt->playpath))) < 0) |
||
816 | return ret; |
||
817 | |||
818 | pkt.extra = rt->stream_id; |
||
819 | |||
820 | p = pkt.data; |
||
821 | ff_amf_write_string(&p, "publish"); |
||
822 | ff_amf_write_number(&p, ++rt->nb_invokes); |
||
823 | ff_amf_write_null(&p); |
||
824 | ff_amf_write_string(&p, rt->playpath); |
||
825 | ff_amf_write_string(&p, "live"); |
||
826 | |||
827 | return rtmp_send_packet(rt, &pkt, 1); |
||
828 | } |
||
829 | |||
830 | /** |
||
831 | * Generate ping reply and send it to the server. |
||
832 | */ |
||
833 | static int gen_pong(URLContext *s, RTMPContext *rt, RTMPPacket *ppkt) |
||
834 | { |
||
835 | RTMPPacket pkt; |
||
836 | uint8_t *p; |
||
837 | int ret; |
||
838 | |||
839 | if (ppkt->size < 6) { |
||
840 | av_log(s, AV_LOG_ERROR, "Too short ping packet (%d)\n", |
||
841 | ppkt->size); |
||
842 | return AVERROR_INVALIDDATA; |
||
843 | } |
||
844 | |||
845 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING, |
||
846 | ppkt->timestamp + 1, 6)) < 0) |
||
847 | return ret; |
||
848 | |||
849 | p = pkt.data; |
||
850 | bytestream_put_be16(&p, 7); |
||
851 | bytestream_put_be32(&p, AV_RB32(ppkt->data+2)); |
||
852 | |||
853 | return rtmp_send_packet(rt, &pkt, 0); |
||
854 | } |
||
855 | |||
856 | /** |
||
857 | * Generate SWF verification message and send it to the server. |
||
858 | */ |
||
859 | static int gen_swf_verification(URLContext *s, RTMPContext *rt) |
||
860 | { |
||
861 | RTMPPacket pkt; |
||
862 | uint8_t *p; |
||
863 | int ret; |
||
864 | |||
865 | av_log(s, AV_LOG_DEBUG, "Sending SWF verification...\n"); |
||
866 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING, |
||
867 | 0, 44)) < 0) |
||
868 | return ret; |
||
869 | |||
870 | p = pkt.data; |
||
871 | bytestream_put_be16(&p, 27); |
||
872 | memcpy(p, rt->swfverification, 42); |
||
873 | |||
874 | return rtmp_send_packet(rt, &pkt, 0); |
||
875 | } |
||
876 | |||
877 | /** |
||
878 | * Generate server bandwidth message and send it to the server. |
||
879 | */ |
||
880 | static int gen_server_bw(URLContext *s, RTMPContext *rt) |
||
881 | { |
||
882 | RTMPPacket pkt; |
||
883 | uint8_t *p; |
||
884 | int ret; |
||
885 | |||
886 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_SERVER_BW, |
||
887 | 0, 4)) < 0) |
||
888 | return ret; |
||
889 | |||
890 | p = pkt.data; |
||
891 | bytestream_put_be32(&p, rt->server_bw); |
||
892 | |||
893 | return rtmp_send_packet(rt, &pkt, 0); |
||
894 | } |
||
895 | |||
896 | /** |
||
897 | * Generate check bandwidth message and send it to the server. |
||
898 | */ |
||
899 | static int gen_check_bw(URLContext *s, RTMPContext *rt) |
||
900 | { |
||
901 | RTMPPacket pkt; |
||
902 | uint8_t *p; |
||
903 | int ret; |
||
904 | |||
905 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, |
||
906 | 0, 21)) < 0) |
||
907 | return ret; |
||
908 | |||
909 | p = pkt.data; |
||
910 | ff_amf_write_string(&p, "_checkbw"); |
||
911 | ff_amf_write_number(&p, ++rt->nb_invokes); |
||
912 | ff_amf_write_null(&p); |
||
913 | |||
914 | return rtmp_send_packet(rt, &pkt, 1); |
||
915 | } |
||
916 | |||
917 | /** |
||
918 | * Generate report on bytes read so far and send it to the server. |
||
919 | */ |
||
920 | static int gen_bytes_read(URLContext *s, RTMPContext *rt, uint32_t ts) |
||
921 | { |
||
922 | RTMPPacket pkt; |
||
923 | uint8_t *p; |
||
924 | int ret; |
||
925 | |||
926 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_BYTES_READ, |
||
927 | ts, 4)) < 0) |
||
928 | return ret; |
||
929 | |||
930 | p = pkt.data; |
||
931 | bytestream_put_be32(&p, rt->bytes_read); |
||
932 | |||
933 | return rtmp_send_packet(rt, &pkt, 0); |
||
934 | } |
||
935 | |||
936 | static int gen_fcsubscribe_stream(URLContext *s, RTMPContext *rt, |
||
937 | const char *subscribe) |
||
938 | { |
||
939 | RTMPPacket pkt; |
||
940 | uint8_t *p; |
||
941 | int ret; |
||
942 | |||
943 | if ((ret = ff_rtmp_packet_create(&pkt, RTMP_SYSTEM_CHANNEL, RTMP_PT_INVOKE, |
||
944 | 0, 27 + strlen(subscribe))) < 0) |
||
945 | return ret; |
||
946 | |||
947 | p = pkt.data; |
||
948 | ff_amf_write_string(&p, "FCSubscribe"); |
||
949 | ff_amf_write_number(&p, ++rt->nb_invokes); |
||
950 | ff_amf_write_null(&p); |
||
951 | ff_amf_write_string(&p, subscribe); |
||
952 | |||
953 | return rtmp_send_packet(rt, &pkt, 1); |
||
954 | } |
||
955 | |||
956 | int ff_rtmp_calc_digest(const uint8_t *src, int len, int gap, |
||
957 | const uint8_t *key, int keylen, uint8_t *dst) |
||
958 | { |
||
959 | AVHMAC *hmac; |
||
960 | |||
961 | hmac = av_hmac_alloc(AV_HMAC_SHA256); |
||
962 | if (!hmac) |
||
963 | return AVERROR(ENOMEM); |
||
964 | |||
965 | av_hmac_init(hmac, key, keylen); |
||
966 | if (gap <= 0) { |
||
967 | av_hmac_update(hmac, src, len); |
||
968 | } else { //skip 32 bytes used for storing digest |
||
969 | av_hmac_update(hmac, src, gap); |
||
970 | av_hmac_update(hmac, src + gap + 32, len - gap - 32); |
||
971 | } |
||
972 | av_hmac_final(hmac, dst, 32); |
||
973 | |||
974 | av_hmac_free(hmac); |
||
975 | |||
976 | return 0; |
||
977 | } |
||
978 | |||
979 | int ff_rtmp_calc_digest_pos(const uint8_t *buf, int off, int mod_val, |
||
980 | int add_val) |
||
981 | { |
||
982 | int i, digest_pos = 0; |
||
983 | |||
984 | for (i = 0; i < 4; i++) |
||
985 | digest_pos += buf[i + off]; |
||
986 | digest_pos = digest_pos % mod_val + add_val; |
||
987 | |||
988 | return digest_pos; |
||
989 | } |
||
990 | |||
991 | /** |
||
992 | * Put HMAC-SHA2 digest of packet data (except for the bytes where this digest |
||
993 | * will be stored) into that packet. |
||
994 | * |
||
995 | * @param buf handshake data (1536 bytes) |
||
996 | * @param encrypted use an encrypted connection (RTMPE) |
||
997 | * @return offset to the digest inside input data |
||
998 | */ |
||
999 | static int rtmp_handshake_imprint_with_digest(uint8_t *buf, int encrypted) |
||
1000 | { |
||
1001 | int ret, digest_pos; |
||
1002 | |||
1003 | if (encrypted) |
||
1004 | digest_pos = ff_rtmp_calc_digest_pos(buf, 772, 728, 776); |
||
1005 | else |
||
1006 | digest_pos = ff_rtmp_calc_digest_pos(buf, 8, 728, 12); |
||
1007 | |||
1008 | ret = ff_rtmp_calc_digest(buf, RTMP_HANDSHAKE_PACKET_SIZE, digest_pos, |
||
1009 | rtmp_player_key, PLAYER_KEY_OPEN_PART_LEN, |
||
1010 | buf + digest_pos); |
||
1011 | if (ret < 0) |
||
1012 | return ret; |
||
1013 | |||
1014 | return digest_pos; |
||
1015 | } |
||
1016 | |||
1017 | /** |
||
1018 | * Verify that the received server response has the expected digest value. |
||
1019 | * |
||
1020 | * @param buf handshake data received from the server (1536 bytes) |
||
1021 | * @param off position to search digest offset from |
||
1022 | * @return 0 if digest is valid, digest position otherwise |
||
1023 | */ |
||
1024 | static int rtmp_validate_digest(uint8_t *buf, int off) |
||
1025 | { |
||
1026 | uint8_t digest[32]; |
||
1027 | int ret, digest_pos; |
||
1028 | |||
1029 | digest_pos = ff_rtmp_calc_digest_pos(buf, off, 728, off + 4); |
||
1030 | |||
1031 | ret = ff_rtmp_calc_digest(buf, RTMP_HANDSHAKE_PACKET_SIZE, digest_pos, |
||
1032 | rtmp_server_key, SERVER_KEY_OPEN_PART_LEN, |
||
1033 | digest); |
||
1034 | if (ret < 0) |
||
1035 | return ret; |
||
1036 | |||
1037 | if (!memcmp(digest, buf + digest_pos, 32)) |
||
1038 | return digest_pos; |
||
1039 | return 0; |
||
1040 | } |
||
1041 | |||
1042 | static int rtmp_calc_swf_verification(URLContext *s, RTMPContext *rt, |
||
1043 | uint8_t *buf) |
||
1044 | { |
||
1045 | uint8_t *p; |
||
1046 | int ret; |
||
1047 | |||
1048 | if (rt->swfhash_len != 32) { |
||
1049 | av_log(s, AV_LOG_ERROR, |
||
1050 | "Hash of the decompressed SWF file is not 32 bytes long.\n"); |
||
1051 | return AVERROR(EINVAL); |
||
1052 | } |
||
1053 | |||
1054 | p = &rt->swfverification[0]; |
||
1055 | bytestream_put_byte(&p, 1); |
||
1056 | bytestream_put_byte(&p, 1); |
||
1057 | bytestream_put_be32(&p, rt->swfsize); |
||
1058 | bytestream_put_be32(&p, rt->swfsize); |
||
1059 | |||
1060 | if ((ret = ff_rtmp_calc_digest(rt->swfhash, 32, 0, buf, 32, p)) < 0) |
||
1061 | return ret; |
||
1062 | |||
1063 | return 0; |
||
1064 | } |
||
1065 | |||
1066 | #if CONFIG_ZLIB |
||
1067 | static int rtmp_uncompress_swfplayer(uint8_t *in_data, int64_t in_size, |
||
1068 | uint8_t **out_data, int64_t *out_size) |
||
1069 | { |
||
1070 | z_stream zs = { 0 }; |
||
1071 | void *ptr; |
||
1072 | int size; |
||
1073 | int ret = 0; |
||
1074 | |||
1075 | zs.avail_in = in_size; |
||
1076 | zs.next_in = in_data; |
||
1077 | ret = inflateInit(&zs); |
||
1078 | if (ret != Z_OK) |
||
1079 | return AVERROR_UNKNOWN; |
||
1080 | |||
1081 | do { |
||
1082 | uint8_t tmp_buf[16384]; |
||
1083 | |||
1084 | zs.avail_out = sizeof(tmp_buf); |
||
1085 | zs.next_out = tmp_buf; |
||
1086 | |||
1087 | ret = inflate(&zs, Z_NO_FLUSH); |
||
1088 | if (ret != Z_OK && ret != Z_STREAM_END) { |
||
1089 | ret = AVERROR_UNKNOWN; |
||
1090 | goto fail; |
||
1091 | } |
||
1092 | |||
1093 | size = sizeof(tmp_buf) - zs.avail_out; |
||
1094 | if (!(ptr = av_realloc(*out_data, *out_size + size))) { |
||
1095 | ret = AVERROR(ENOMEM); |
||
1096 | goto fail; |
||
1097 | } |
||
1098 | *out_data = ptr; |
||
1099 | |||
1100 | memcpy(*out_data + *out_size, tmp_buf, size); |
||
1101 | *out_size += size; |
||
1102 | } while (zs.avail_out == 0); |
||
1103 | |||
1104 | fail: |
||
1105 | inflateEnd(&zs); |
||
1106 | return ret; |
||
1107 | } |
||
1108 | #endif |
||
1109 | |||
1110 | static int rtmp_calc_swfhash(URLContext *s) |
||
1111 | { |
||
1112 | RTMPContext *rt = s->priv_data; |
||
1113 | uint8_t *in_data = NULL, *out_data = NULL, *swfdata; |
||
1114 | int64_t in_size, out_size; |
||
1115 | URLContext *stream; |
||
1116 | char swfhash[32]; |
||
1117 | int swfsize; |
||
1118 | int ret = 0; |
||
1119 | |||
1120 | /* Get the SWF player file. */ |
||
1121 | if ((ret = ffurl_open(&stream, rt->swfverify, AVIO_FLAG_READ, |
||
1122 | &s->interrupt_callback, NULL)) < 0) { |
||
1123 | av_log(s, AV_LOG_ERROR, "Cannot open connection %s.\n", rt->swfverify); |
||
1124 | goto fail; |
||
1125 | } |
||
1126 | |||
1127 | if ((in_size = ffurl_seek(stream, 0, AVSEEK_SIZE)) < 0) { |
||
1128 | ret = AVERROR(EIO); |
||
1129 | goto fail; |
||
1130 | } |
||
1131 | |||
1132 | if (!(in_data = av_malloc(in_size))) { |
||
1133 | ret = AVERROR(ENOMEM); |
||
1134 | goto fail; |
||
1135 | } |
||
1136 | |||
1137 | if ((ret = ffurl_read_complete(stream, in_data, in_size)) < 0) |
||
1138 | goto fail; |
||
1139 | |||
1140 | if (in_size < 3) { |
||
1141 | ret = AVERROR_INVALIDDATA; |
||
1142 | goto fail; |
||
1143 | } |
||
1144 | |||
1145 | if (!memcmp(in_data, "CWS", 3)) { |
||
1146 | /* Decompress the SWF player file using Zlib. */ |
||
1147 | if (!(out_data = av_malloc(8))) { |
||
1148 | ret = AVERROR(ENOMEM); |
||
1149 | goto fail; |
||
1150 | } |
||
1151 | *in_data = 'F'; // magic stuff |
||
1152 | memcpy(out_data, in_data, 8); |
||
1153 | out_size = 8; |
||
1154 | |||
1155 | #if CONFIG_ZLIB |
||
1156 | if ((ret = rtmp_uncompress_swfplayer(in_data + 8, in_size - 8, |
||
1157 | &out_data, &out_size)) < 0) |
||
1158 | goto fail; |
||
1159 | #else |
||
1160 | av_log(s, AV_LOG_ERROR, |
||
1161 | "Zlib is required for decompressing the SWF player file.\n"); |
||
1162 | ret = AVERROR(EINVAL); |
||
1163 | goto fail; |
||
1164 | #endif |
||
1165 | swfsize = out_size; |
||
1166 | swfdata = out_data; |
||
1167 | } else { |
||
1168 | swfsize = in_size; |
||
1169 | swfdata = in_data; |
||
1170 | } |
||
1171 | |||
1172 | /* Compute the SHA256 hash of the SWF player file. */ |
||
1173 | if ((ret = ff_rtmp_calc_digest(swfdata, swfsize, 0, |
||
1174 | "Genuine Adobe Flash Player 001", 30, |
||
1175 | swfhash)) < 0) |
||
1176 | goto fail; |
||
1177 | |||
1178 | /* Set SWFVerification parameters. */ |
||
1179 | av_opt_set_bin(rt, "rtmp_swfhash", swfhash, 32, 0); |
||
1180 | rt->swfsize = swfsize; |
||
1181 | |||
1182 | fail: |
||
1183 | av_freep(&in_data); |
||
1184 | av_freep(&out_data); |
||
1185 | ffurl_close(stream); |
||
1186 | return ret; |
||
1187 | } |
||
1188 | |||
1189 | /** |
||
1190 | * Perform handshake with the server by means of exchanging pseudorandom data |
||
1191 | * signed with HMAC-SHA2 digest. |
||
1192 | * |
||
1193 | * @return 0 if handshake succeeds, negative value otherwise |
||
1194 | */ |
||
1195 | static int rtmp_handshake(URLContext *s, RTMPContext *rt) |
||
1196 | { |
||
1197 | AVLFG rnd; |
||
1198 | uint8_t tosend [RTMP_HANDSHAKE_PACKET_SIZE+1] = { |
||
1199 | 3, // unencrypted data |
||
1200 | 0, 0, 0, 0, // client uptime |
||
1201 | RTMP_CLIENT_VER1, |
||
1202 | RTMP_CLIENT_VER2, |
||
1203 | RTMP_CLIENT_VER3, |
||
1204 | RTMP_CLIENT_VER4, |
||
1205 | }; |
||
1206 | uint8_t clientdata[RTMP_HANDSHAKE_PACKET_SIZE]; |
||
1207 | uint8_t serverdata[RTMP_HANDSHAKE_PACKET_SIZE+1]; |
||
1208 | int i; |
||
1209 | int server_pos, client_pos; |
||
1210 | uint8_t digest[32], signature[32]; |
||
1211 | int ret, type = 0; |
||
1212 | |||
1213 | av_log(s, AV_LOG_DEBUG, "Handshaking...\n"); |
||
1214 | |||
1215 | av_lfg_init(&rnd, 0xDEADC0DE); |
||
1216 | // generate handshake packet - 1536 bytes of pseudorandom data |
||
1217 | for (i = 9; i <= RTMP_HANDSHAKE_PACKET_SIZE; i++) |
||
1218 | tosend[i] = av_lfg_get(&rnd) >> 24; |
||
1219 | |||
1220 | if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { |
||
1221 | /* When the client wants to use RTMPE, we have to change the command |
||
1222 | * byte to 0x06 which means to use encrypted data and we have to set |
||
1223 | * the flash version to at least 9.0.115.0. */ |
||
1224 | tosend[0] = 6; |
||
1225 | tosend[5] = 128; |
||
1226 | tosend[6] = 0; |
||
1227 | tosend[7] = 3; |
||
1228 | tosend[8] = 2; |
||
1229 | |||
1230 | /* Initialize the Diffie-Hellmann context and generate the public key |
||
1231 | * to send to the server. */ |
||
1232 | if ((ret = ff_rtmpe_gen_pub_key(rt->stream, tosend + 1)) < 0) |
||
1233 | return ret; |
||
1234 | } |
||
1235 | |||
1236 | client_pos = rtmp_handshake_imprint_with_digest(tosend + 1, rt->encrypted); |
||
1237 | if (client_pos < 0) |
||
1238 | return client_pos; |
||
1239 | |||
1240 | if ((ret = ffurl_write(rt->stream, tosend, |
||
1241 | RTMP_HANDSHAKE_PACKET_SIZE + 1)) < 0) { |
||
1242 | av_log(s, AV_LOG_ERROR, "Cannot write RTMP handshake request\n"); |
||
1243 | return ret; |
||
1244 | } |
||
1245 | |||
1246 | if ((ret = ffurl_read_complete(rt->stream, serverdata, |
||
1247 | RTMP_HANDSHAKE_PACKET_SIZE + 1)) < 0) { |
||
1248 | av_log(s, AV_LOG_ERROR, "Cannot read RTMP handshake response\n"); |
||
1249 | return ret; |
||
1250 | } |
||
1251 | |||
1252 | if ((ret = ffurl_read_complete(rt->stream, clientdata, |
||
1253 | RTMP_HANDSHAKE_PACKET_SIZE)) < 0) { |
||
1254 | av_log(s, AV_LOG_ERROR, "Cannot read RTMP handshake response\n"); |
||
1255 | return ret; |
||
1256 | } |
||
1257 | |||
1258 | av_log(s, AV_LOG_DEBUG, "Type answer %d\n", serverdata[0]); |
||
1259 | av_log(s, AV_LOG_DEBUG, "Server version %d.%d.%d.%d\n", |
||
1260 | serverdata[5], serverdata[6], serverdata[7], serverdata[8]); |
||
1261 | |||
1262 | if (rt->is_input && serverdata[5] >= 3) { |
||
1263 | server_pos = rtmp_validate_digest(serverdata + 1, 772); |
||
1264 | if (server_pos < 0) |
||
1265 | return server_pos; |
||
1266 | |||
1267 | if (!server_pos) { |
||
1268 | type = 1; |
||
1269 | server_pos = rtmp_validate_digest(serverdata + 1, 8); |
||
1270 | if (server_pos < 0) |
||
1271 | return server_pos; |
||
1272 | |||
1273 | if (!server_pos) { |
||
1274 | av_log(s, AV_LOG_ERROR, "Server response validating failed\n"); |
||
1275 | return AVERROR(EIO); |
||
1276 | } |
||
1277 | } |
||
1278 | |||
1279 | /* Generate SWFVerification token (SHA256 HMAC hash of decompressed SWF, |
||
1280 | * key are the last 32 bytes of the server handshake. */ |
||
1281 | if (rt->swfsize) { |
||
1282 | if ((ret = rtmp_calc_swf_verification(s, rt, serverdata + 1 + |
||
1283 | RTMP_HANDSHAKE_PACKET_SIZE - 32)) < 0) |
||
1284 | return ret; |
||
1285 | } |
||
1286 | |||
1287 | ret = ff_rtmp_calc_digest(tosend + 1 + client_pos, 32, 0, |
||
1288 | rtmp_server_key, sizeof(rtmp_server_key), |
||
1289 | digest); |
||
1290 | if (ret < 0) |
||
1291 | return ret; |
||
1292 | |||
1293 | ret = ff_rtmp_calc_digest(clientdata, RTMP_HANDSHAKE_PACKET_SIZE - 32, |
||
1294 | 0, digest, 32, signature); |
||
1295 | if (ret < 0) |
||
1296 | return ret; |
||
1297 | |||
1298 | if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { |
||
1299 | /* Compute the shared secret key sent by the server and initialize |
||
1300 | * the RC4 encryption. */ |
||
1301 | if ((ret = ff_rtmpe_compute_secret_key(rt->stream, serverdata + 1, |
||
1302 | tosend + 1, type)) < 0) |
||
1303 | return ret; |
||
1304 | |||
1305 | /* Encrypt the signature received by the server. */ |
||
1306 | ff_rtmpe_encrypt_sig(rt->stream, signature, digest, serverdata[0]); |
||
1307 | } |
||
1308 | |||
1309 | if (memcmp(signature, clientdata + RTMP_HANDSHAKE_PACKET_SIZE - 32, 32)) { |
||
1310 | av_log(s, AV_LOG_ERROR, "Signature mismatch\n"); |
||
1311 | return AVERROR(EIO); |
||
1312 | } |
||
1313 | |||
1314 | for (i = 0; i < RTMP_HANDSHAKE_PACKET_SIZE; i++) |
||
1315 | tosend[i] = av_lfg_get(&rnd) >> 24; |
||
1316 | ret = ff_rtmp_calc_digest(serverdata + 1 + server_pos, 32, 0, |
||
1317 | rtmp_player_key, sizeof(rtmp_player_key), |
||
1318 | digest); |
||
1319 | if (ret < 0) |
||
1320 | return ret; |
||
1321 | |||
1322 | ret = ff_rtmp_calc_digest(tosend, RTMP_HANDSHAKE_PACKET_SIZE - 32, 0, |
||
1323 | digest, 32, |
||
1324 | tosend + RTMP_HANDSHAKE_PACKET_SIZE - 32); |
||
1325 | if (ret < 0) |
||
1326 | return ret; |
||
1327 | |||
1328 | if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { |
||
1329 | /* Encrypt the signature to be send to the server. */ |
||
1330 | ff_rtmpe_encrypt_sig(rt->stream, tosend + |
||
1331 | RTMP_HANDSHAKE_PACKET_SIZE - 32, digest, |
||
1332 | serverdata[0]); |
||
1333 | } |
||
1334 | |||
1335 | // write reply back to the server |
||
1336 | if ((ret = ffurl_write(rt->stream, tosend, |
||
1337 | RTMP_HANDSHAKE_PACKET_SIZE)) < 0) |
||
1338 | return ret; |
||
1339 | |||
1340 | if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { |
||
1341 | /* Set RC4 keys for encryption and update the keystreams. */ |
||
1342 | if ((ret = ff_rtmpe_update_keystream(rt->stream)) < 0) |
||
1343 | return ret; |
||
1344 | } |
||
1345 | } else { |
||
1346 | if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { |
||
1347 | /* Compute the shared secret key sent by the server and initialize |
||
1348 | * the RC4 encryption. */ |
||
1349 | if ((ret = ff_rtmpe_compute_secret_key(rt->stream, serverdata + 1, |
||
1350 | tosend + 1, 1)) < 0) |
||
1351 | return ret; |
||
1352 | |||
1353 | if (serverdata[0] == 9) { |
||
1354 | /* Encrypt the signature received by the server. */ |
||
1355 | ff_rtmpe_encrypt_sig(rt->stream, signature, digest, |
||
1356 | serverdata[0]); |
||
1357 | } |
||
1358 | } |
||
1359 | |||
1360 | if ((ret = ffurl_write(rt->stream, serverdata + 1, |
||
1361 | RTMP_HANDSHAKE_PACKET_SIZE)) < 0) |
||
1362 | return ret; |
||
1363 | |||
1364 | if (CONFIG_FFRTMPCRYPT_PROTOCOL && rt->encrypted) { |
||
1365 | /* Set RC4 keys for encryption and update the keystreams. */ |
||
1366 | if ((ret = ff_rtmpe_update_keystream(rt->stream)) < 0) |
||
1367 | return ret; |
||
1368 | } |
||
1369 | } |
||
1370 | |||
1371 | return 0; |
||
1372 | } |
||
1373 | |||
1374 | static int rtmp_receive_hs_packet(RTMPContext* rt, uint32_t *first_int, |
||
1375 | uint32_t *second_int, char *arraydata, |
||
1376 | int size) |
||
1377 | { |
||
1378 | int inoutsize; |
||
1379 | |||
1380 | inoutsize = ffurl_read_complete(rt->stream, arraydata, |
||
1381 | RTMP_HANDSHAKE_PACKET_SIZE); |
||
1382 | if (inoutsize <= 0) |
||
1383 | return AVERROR(EIO); |
||
1384 | if (inoutsize != RTMP_HANDSHAKE_PACKET_SIZE) { |
||
1385 | av_log(rt, AV_LOG_ERROR, "Erroneous Message size %d" |
||
1386 | " not following standard\n", (int)inoutsize); |
||
1387 | return AVERROR(EINVAL); |
||
1388 | } |
||
1389 | |||
1390 | *first_int = AV_RB32(arraydata); |
||
1391 | *second_int = AV_RB32(arraydata + 4); |
||
1392 | return 0; |
||
1393 | } |
||
1394 | |||
1395 | static int rtmp_send_hs_packet(RTMPContext* rt, uint32_t first_int, |
||
1396 | uint32_t second_int, char *arraydata, int size) |
||
1397 | { |
||
1398 | int inoutsize; |
||
1399 | |||
1400 | AV_WB32(arraydata, first_int); |
||
1401 | AV_WB32(arraydata + 4, second_int); |
||
1402 | inoutsize = ffurl_write(rt->stream, arraydata, |
||
1403 | RTMP_HANDSHAKE_PACKET_SIZE); |
||
1404 | if (inoutsize != RTMP_HANDSHAKE_PACKET_SIZE) { |
||
1405 | av_log(rt, AV_LOG_ERROR, "Unable to write answer\n"); |
||
1406 | return AVERROR(EIO); |
||
1407 | } |
||
1408 | |||
1409 | return 0; |
||
1410 | } |
||
1411 | |||
1412 | /** |
||
1413 | * rtmp handshake server side |
||
1414 | */ |
||
1415 | static int rtmp_server_handshake(URLContext *s, RTMPContext *rt) |
||
1416 | { |
||
1417 | uint8_t buffer[RTMP_HANDSHAKE_PACKET_SIZE]; |
||
1418 | uint32_t hs_epoch; |
||
1419 | uint32_t hs_my_epoch; |
||
1420 | uint8_t hs_c1[RTMP_HANDSHAKE_PACKET_SIZE]; |
||
1421 | uint8_t hs_s1[RTMP_HANDSHAKE_PACKET_SIZE]; |
||
1422 | uint32_t zeroes; |
||
1423 | uint32_t temp = 0; |
||
1424 | int randomidx = 0; |
||
1425 | int inoutsize = 0; |
||
1426 | int ret; |
||
1427 | |||
1428 | inoutsize = ffurl_read_complete(rt->stream, buffer, 1); // Receive C0 |
||
1429 | if (inoutsize <= 0) { |
||
1430 | av_log(s, AV_LOG_ERROR, "Unable to read handshake\n"); |
||
1431 | return AVERROR(EIO); |
||
1432 | } |
||
1433 | // Check Version |
||
1434 | if (buffer[0] != 3) { |
||
1435 | av_log(s, AV_LOG_ERROR, "RTMP protocol version mismatch\n"); |
||
1436 | return AVERROR(EIO); |
||
1437 | } |
||
1438 | if (ffurl_write(rt->stream, buffer, 1) <= 0) { // Send S0 |
||
1439 | av_log(s, AV_LOG_ERROR, |
||
1440 | "Unable to write answer - RTMP S0\n"); |
||
1441 | return AVERROR(EIO); |
||
1442 | } |
||
1443 | /* Receive C1 */ |
||
1444 | ret = rtmp_receive_hs_packet(rt, &hs_epoch, &zeroes, hs_c1, |
||
1445 | RTMP_HANDSHAKE_PACKET_SIZE); |
||
1446 | if (ret) { |
||
1447 | av_log(s, AV_LOG_ERROR, "RTMP Handshake C1 Error\n"); |
||
1448 | return ret; |
||
1449 | } |
||
1450 | /* Send S1 */ |
||
1451 | /* By now same epoch will be sent */ |
||
1452 | hs_my_epoch = hs_epoch; |
||
1453 | /* Generate random */ |
||
1454 | for (randomidx = 8; randomidx < (RTMP_HANDSHAKE_PACKET_SIZE); |
||
1455 | randomidx += 4) |
||
1456 | AV_WB32(hs_s1 + randomidx, av_get_random_seed()); |
||
1457 | |||
1458 | ret = rtmp_send_hs_packet(rt, hs_my_epoch, 0, hs_s1, |
||
1459 | RTMP_HANDSHAKE_PACKET_SIZE); |
||
1460 | if (ret) { |
||
1461 | av_log(s, AV_LOG_ERROR, "RTMP Handshake S1 Error\n"); |
||
1462 | return ret; |
||
1463 | } |
||
1464 | /* Send S2 */ |
||
1465 | ret = rtmp_send_hs_packet(rt, hs_epoch, 0, hs_c1, |
||
1466 | RTMP_HANDSHAKE_PACKET_SIZE); |
||
1467 | if (ret) { |
||
1468 | av_log(s, AV_LOG_ERROR, "RTMP Handshake S2 Error\n"); |
||
1469 | return ret; |
||
1470 | } |
||
1471 | /* Receive C2 */ |
||
1472 | ret = rtmp_receive_hs_packet(rt, &temp, &zeroes, buffer, |
||
1473 | RTMP_HANDSHAKE_PACKET_SIZE); |
||
1474 | if (ret) { |
||
1475 | av_log(s, AV_LOG_ERROR, "RTMP Handshake C2 Error\n"); |
||
1476 | return ret; |
||
1477 | } |
||
1478 | if (temp != hs_my_epoch) |
||
1479 | av_log(s, AV_LOG_WARNING, |
||
1480 | "Erroneous C2 Message epoch does not match up with C1 epoch\n"); |
||
1481 | if (memcmp(buffer + 8, hs_s1 + 8, |
||
1482 | RTMP_HANDSHAKE_PACKET_SIZE - 8)) |
||
1483 | av_log(s, AV_LOG_WARNING, |
||
1484 | "Erroneous C2 Message random does not match up\n"); |
||
1485 | |||
1486 | return 0; |
||
1487 | } |
||
1488 | |||
1489 | static int handle_chunk_size(URLContext *s, RTMPPacket *pkt) |
||
1490 | { |
||
1491 | RTMPContext *rt = s->priv_data; |
||
1492 | int ret; |
||
1493 | |||
1494 | if (pkt->size < 4) { |
||
1495 | av_log(s, AV_LOG_ERROR, |
||
1496 | "Too short chunk size change packet (%d)\n", |
||
1497 | pkt->size); |
||
1498 | return AVERROR_INVALIDDATA; |
||
1499 | } |
||
1500 | |||
1501 | if (!rt->is_input) { |
||
1502 | /* Send the same chunk size change packet back to the server, |
||
1503 | * setting the outgoing chunk size to the same as the incoming one. */ |
||
1504 | if ((ret = ff_rtmp_packet_write(rt->stream, pkt, rt->out_chunk_size, |
||
1505 | &rt->prev_pkt[1], &rt->nb_prev_pkt[1])) < 0) |
||
1506 | return ret; |
||
1507 | rt->out_chunk_size = AV_RB32(pkt->data); |
||
1508 | } |
||
1509 | |||
1510 | rt->in_chunk_size = AV_RB32(pkt->data); |
||
1511 | if (rt->in_chunk_size <= 0) { |
||
1512 | av_log(s, AV_LOG_ERROR, "Incorrect chunk size %d\n", |
||
1513 | rt->in_chunk_size); |
||
1514 | return AVERROR_INVALIDDATA; |
||
1515 | } |
||
1516 | av_log(s, AV_LOG_DEBUG, "New incoming chunk size = %d\n", |
||
1517 | rt->in_chunk_size); |
||
1518 | |||
1519 | return 0; |
||
1520 | } |
||
1521 | |||
1522 | static int handle_ping(URLContext *s, RTMPPacket *pkt) |
||
1523 | { |
||
1524 | RTMPContext *rt = s->priv_data; |
||
1525 | int t, ret; |
||
1526 | |||
1527 | if (pkt->size < 2) { |
||
1528 | av_log(s, AV_LOG_ERROR, "Too short ping packet (%d)\n", |
||
1529 | pkt->size); |
||
1530 | return AVERROR_INVALIDDATA; |
||
1531 | } |
||
1532 | |||
1533 | t = AV_RB16(pkt->data); |
||
1534 | if (t == 6) { |
||
1535 | if ((ret = gen_pong(s, rt, pkt)) < 0) |
||
1536 | return ret; |
||
1537 | } else if (t == 26) { |
||
1538 | if (rt->swfsize) { |
||
1539 | if ((ret = gen_swf_verification(s, rt)) < 0) |
||
1540 | return ret; |
||
1541 | } else { |
||
1542 | av_log(s, AV_LOG_WARNING, "Ignoring SWFVerification request.\n"); |
||
1543 | } |
||
1544 | } |
||
1545 | |||
1546 | return 0; |
||
1547 | } |
||
1548 | |||
1549 | static int handle_client_bw(URLContext *s, RTMPPacket *pkt) |
||
1550 | { |
||
1551 | RTMPContext *rt = s->priv_data; |
||
1552 | |||
1553 | if (pkt->size < 4) { |
||
1554 | av_log(s, AV_LOG_ERROR, |
||
1555 | "Client bandwidth report packet is less than 4 bytes long (%d)\n", |
||
1556 | pkt->size); |
||
1557 | return AVERROR_INVALIDDATA; |
||
1558 | } |
||
1559 | |||
1560 | rt->client_report_size = AV_RB32(pkt->data); |
||
1561 | if (rt->client_report_size <= 0) { |
||
1562 | av_log(s, AV_LOG_ERROR, "Incorrect client bandwidth %d\n", |
||
1563 | rt->client_report_size); |
||
1564 | return AVERROR_INVALIDDATA; |
||
1565 | |||
1566 | } |
||
1567 | av_log(s, AV_LOG_DEBUG, "Client bandwidth = %d\n", rt->client_report_size); |
||
1568 | rt->client_report_size >>= 1; |
||
1569 | |||
1570 | return 0; |
||
1571 | } |
||
1572 | |||
1573 | static int handle_server_bw(URLContext *s, RTMPPacket *pkt) |
||
1574 | { |
||
1575 | RTMPContext *rt = s->priv_data; |
||
1576 | |||
1577 | if (pkt->size < 4) { |
||
1578 | av_log(s, AV_LOG_ERROR, |
||
1579 | "Too short server bandwidth report packet (%d)\n", |
||
1580 | pkt->size); |
||
1581 | return AVERROR_INVALIDDATA; |
||
1582 | } |
||
1583 | |||
1584 | rt->server_bw = AV_RB32(pkt->data); |
||
1585 | if (rt->server_bw <= 0) { |
||
1586 | av_log(s, AV_LOG_ERROR, "Incorrect server bandwidth %d\n", |
||
1587 | rt->server_bw); |
||
1588 | return AVERROR_INVALIDDATA; |
||
1589 | } |
||
1590 | av_log(s, AV_LOG_DEBUG, "Server bandwidth = %d\n", rt->server_bw); |
||
1591 | |||
1592 | return 0; |
||
1593 | } |
||
1594 | |||
1595 | static int do_adobe_auth(RTMPContext *rt, const char *user, const char *salt, |
||
1596 | const char *opaque, const char *challenge) |
||
1597 | { |
||
1598 | uint8_t hash[16]; |
||
1599 | char hashstr[AV_BASE64_SIZE(sizeof(hash))], challenge2[10]; |
||
1600 | struct AVMD5 *md5 = av_md5_alloc(); |
||
1601 | if (!md5) |
||
1602 | return AVERROR(ENOMEM); |
||
1603 | |||
1604 | snprintf(challenge2, sizeof(challenge2), "%08x", av_get_random_seed()); |
||
1605 | |||
1606 | av_md5_init(md5); |
||
1607 | av_md5_update(md5, user, strlen(user)); |
||
1608 | av_md5_update(md5, salt, strlen(salt)); |
||
1609 | av_md5_update(md5, rt->password, strlen(rt->password)); |
||
1610 | av_md5_final(md5, hash); |
||
1611 | av_base64_encode(hashstr, sizeof(hashstr), hash, |
||
1612 | sizeof(hash)); |
||
1613 | av_md5_init(md5); |
||
1614 | av_md5_update(md5, hashstr, strlen(hashstr)); |
||
1615 | if (opaque) |
||
1616 | av_md5_update(md5, opaque, strlen(opaque)); |
||
1617 | else if (challenge) |
||
1618 | av_md5_update(md5, challenge, strlen(challenge)); |
||
1619 | av_md5_update(md5, challenge2, strlen(challenge2)); |
||
1620 | av_md5_final(md5, hash); |
||
1621 | av_base64_encode(hashstr, sizeof(hashstr), hash, |
||
1622 | sizeof(hash)); |
||
1623 | snprintf(rt->auth_params, sizeof(rt->auth_params), |
||
1624 | "?authmod=%s&user=%s&challenge=%s&response=%s", |
||
1625 | "adobe", user, challenge2, hashstr); |
||
1626 | if (opaque) |
||
1627 | av_strlcatf(rt->auth_params, sizeof(rt->auth_params), |
||
1628 | "&opaque=%s", opaque); |
||
1629 | |||
1630 | av_free(md5); |
||
1631 | return 0; |
||
1632 | } |
||
1633 | |||
1634 | static int do_llnw_auth(RTMPContext *rt, const char *user, const char *nonce) |
||
1635 | { |
||
1636 | uint8_t hash[16]; |
||
1637 | char hashstr1[33], hashstr2[33]; |
||
1638 | const char *realm = "live"; |
||
1639 | const char *method = "publish"; |
||
1640 | const char *qop = "auth"; |
||
1641 | const char *nc = "00000001"; |
||
1642 | char cnonce[10]; |
||
1643 | struct AVMD5 *md5 = av_md5_alloc(); |
||
1644 | if (!md5) |
||
1645 | return AVERROR(ENOMEM); |
||
1646 | |||
1647 | snprintf(cnonce, sizeof(cnonce), "%08x", av_get_random_seed()); |
||
1648 | |||
1649 | av_md5_init(md5); |
||
1650 | av_md5_update(md5, user, strlen(user)); |
||
1651 | av_md5_update(md5, ":", 1); |
||
1652 | av_md5_update(md5, realm, strlen(realm)); |
||
1653 | av_md5_update(md5, ":", 1); |
||
1654 | av_md5_update(md5, rt->password, strlen(rt->password)); |
||
1655 | av_md5_final(md5, hash); |
||
1656 | ff_data_to_hex(hashstr1, hash, 16, 1); |
||
1657 | hashstr1[32] = '\0'; |
||
1658 | |||
1659 | av_md5_init(md5); |
||
1660 | av_md5_update(md5, method, strlen(method)); |
||
1661 | av_md5_update(md5, ":/", 2); |
||
1662 | av_md5_update(md5, rt->app, strlen(rt->app)); |
||
1663 | if (!strchr(rt->app, '/')) |
||
1664 | av_md5_update(md5, "/_definst_", strlen("/_definst_")); |
||
1665 | av_md5_final(md5, hash); |
||
1666 | ff_data_to_hex(hashstr2, hash, 16, 1); |
||
1667 | hashstr2[32] = '\0'; |
||
1668 | |||
1669 | av_md5_init(md5); |
||
1670 | av_md5_update(md5, hashstr1, strlen(hashstr1)); |
||
1671 | av_md5_update(md5, ":", 1); |
||
1672 | if (nonce) |
||
1673 | av_md5_update(md5, nonce, strlen(nonce)); |
||
1674 | av_md5_update(md5, ":", 1); |
||
1675 | av_md5_update(md5, nc, strlen(nc)); |
||
1676 | av_md5_update(md5, ":", 1); |
||
1677 | av_md5_update(md5, cnonce, strlen(cnonce)); |
||
1678 | av_md5_update(md5, ":", 1); |
||
1679 | av_md5_update(md5, qop, strlen(qop)); |
||
1680 | av_md5_update(md5, ":", 1); |
||
1681 | av_md5_update(md5, hashstr2, strlen(hashstr2)); |
||
1682 | av_md5_final(md5, hash); |
||
1683 | ff_data_to_hex(hashstr1, hash, 16, 1); |
||
1684 | |||
1685 | snprintf(rt->auth_params, sizeof(rt->auth_params), |
||
1686 | "?authmod=%s&user=%s&nonce=%s&cnonce=%s&nc=%s&response=%s", |
||
1687 | "llnw", user, nonce, cnonce, nc, hashstr1); |
||
1688 | |||
1689 | av_free(md5); |
||
1690 | return 0; |
||
1691 | } |
||
1692 | |||
1693 | static int handle_connect_error(URLContext *s, const char *desc) |
||
1694 | { |
||
1695 | RTMPContext *rt = s->priv_data; |
||
1696 | char buf[300], *ptr, authmod[15]; |
||
1697 | int i = 0, ret = 0; |
||
1698 | const char *user = "", *salt = "", *opaque = NULL, |
||
1699 | *challenge = NULL, *cptr = NULL, *nonce = NULL; |
||
1700 | |||
1701 | if (!(cptr = strstr(desc, "authmod=adobe")) && |
||
1702 | !(cptr = strstr(desc, "authmod=llnw"))) { |
||
1703 | av_log(s, AV_LOG_ERROR, |
||
1704 | "Unknown connect error (unsupported authentication method?)\n"); |
||
1705 | return AVERROR_UNKNOWN; |
||
1706 | } |
||
1707 | cptr += strlen("authmod="); |
||
1708 | while (*cptr && *cptr != ' ' && i < sizeof(authmod) - 1) |
||
1709 | authmod[i++] = *cptr++; |
||
1710 | authmod[i] = '\0'; |
||
1711 | |||
1712 | if (!rt->username[0] || !rt->password[0]) { |
||
1713 | av_log(s, AV_LOG_ERROR, "No credentials set\n"); |
||
1714 | return AVERROR_UNKNOWN; |
||
1715 | } |
||
1716 | |||
1717 | if (strstr(desc, "?reason=authfailed")) { |
||
1718 | av_log(s, AV_LOG_ERROR, "Incorrect username/password\n"); |
||
1719 | return AVERROR_UNKNOWN; |
||
1720 | } else if (strstr(desc, "?reason=nosuchuser")) { |
||
1721 | av_log(s, AV_LOG_ERROR, "Incorrect username\n"); |
||
1722 | return AVERROR_UNKNOWN; |
||
1723 | } |
||
1724 | |||
1725 | if (rt->auth_tried) { |
||
1726 | av_log(s, AV_LOG_ERROR, "Authentication failed\n"); |
||
1727 | return AVERROR_UNKNOWN; |
||
1728 | } |
||
1729 | |||
1730 | rt->auth_params[0] = '\0'; |
||
1731 | |||
1732 | if (strstr(desc, "code=403 need auth")) { |
||
1733 | snprintf(rt->auth_params, sizeof(rt->auth_params), |
||
1734 | "?authmod=%s&user=%s", authmod, rt->username); |
||
1735 | return 0; |
||
1736 | } |
||
1737 | |||
1738 | if (!(cptr = strstr(desc, "?reason=needauth"))) { |
||
1739 | av_log(s, AV_LOG_ERROR, "No auth parameters found\n"); |
||
1740 | return AVERROR_UNKNOWN; |
||
1741 | } |
||
1742 | |||
1743 | av_strlcpy(buf, cptr + 1, sizeof(buf)); |
||
1744 | ptr = buf; |
||
1745 | |||
1746 | while (ptr) { |
||
1747 | char *next = strchr(ptr, '&'); |
||
1748 | char *value = strchr(ptr, '='); |
||
1749 | if (next) |
||
1750 | *next++ = '\0'; |
||
1751 | if (value) { |
||
1752 | *value++ = '\0'; |
||
1753 | if (!strcmp(ptr, "user")) { |
||
1754 | user = value; |
||
1755 | } else if (!strcmp(ptr, "salt")) { |
||
1756 | salt = value; |
||
1757 | } else if (!strcmp(ptr, "opaque")) { |
||
1758 | opaque = value; |
||
1759 | } else if (!strcmp(ptr, "challenge")) { |
||
1760 | challenge = value; |
||
1761 | } else if (!strcmp(ptr, "nonce")) { |
||
1762 | nonce = value; |
||
1763 | } else { |
||
1764 | av_log(s, AV_LOG_INFO, "Ignoring unsupported var %s\n", ptr); |
||
1765 | } |
||
1766 | } else { |
||
1767 | av_log(s, AV_LOG_WARNING, "Variable %s has NULL value\n", ptr); |
||
1768 | } |
||
1769 | ptr = next; |
||
1770 | } |
||
1771 | |||
1772 | if (!strcmp(authmod, "adobe")) { |
||
1773 | if ((ret = do_adobe_auth(rt, user, salt, opaque, challenge)) < 0) |
||
1774 | return ret; |
||
1775 | } else { |
||
1776 | if ((ret = do_llnw_auth(rt, user, nonce)) < 0) |
||
1777 | return ret; |
||
1778 | } |
||
1779 | |||
1780 | rt->auth_tried = 1; |
||
1781 | return 0; |
||
1782 | } |
||
1783 | |||
1784 | static int handle_invoke_error(URLContext *s, RTMPPacket *pkt) |
||
1785 | { |
||
1786 | RTMPContext *rt = s->priv_data; |
||
1787 | const uint8_t *data_end = pkt->data + pkt->size; |
||
1788 | char *tracked_method = NULL; |
||
1789 | int level = AV_LOG_ERROR; |
||
1790 | uint8_t tmpstr[256]; |
||
1791 | int ret; |
||
1792 | |||
1793 | if ((ret = find_tracked_method(s, pkt, 9, &tracked_method)) < 0) |
||
1794 | return ret; |
||
1795 | |||
1796 | if (!ff_amf_get_field_value(pkt->data + 9, data_end, |
||
1797 | "description", tmpstr, sizeof(tmpstr))) { |
||
1798 | if (tracked_method && (!strcmp(tracked_method, "_checkbw") || |
||
1799 | !strcmp(tracked_method, "releaseStream") || |
||
1800 | !strcmp(tracked_method, "FCSubscribe") || |
||
1801 | !strcmp(tracked_method, "FCPublish"))) { |
||
1802 | /* Gracefully ignore Adobe-specific historical artifact errors. */ |
||
1803 | level = AV_LOG_WARNING; |
||
1804 | ret = 0; |
||
1805 | } else if (tracked_method && !strcmp(tracked_method, "getStreamLength")) { |
||
1806 | level = rt->live ? AV_LOG_DEBUG : AV_LOG_WARNING; |
||
1807 | ret = 0; |
||
1808 | } else if (tracked_method && !strcmp(tracked_method, "connect")) { |
||
1809 | ret = handle_connect_error(s, tmpstr); |
||
1810 | if (!ret) { |
||
1811 | rt->do_reconnect = 1; |
||
1812 | level = AV_LOG_VERBOSE; |
||
1813 | } |
||
1814 | } else |
||
1815 | ret = AVERROR_UNKNOWN; |
||
1816 | av_log(s, level, "Server error: %s\n", tmpstr); |
||
1817 | } |
||
1818 | |||
1819 | av_free(tracked_method); |
||
1820 | return ret; |
||
1821 | } |
||
1822 | |||
1823 | static int write_begin(URLContext *s) |
||
1824 | { |
||
1825 | RTMPContext *rt = s->priv_data; |
||
1826 | PutByteContext pbc; |
||
1827 | RTMPPacket spkt = { 0 }; |
||
1828 | int ret; |
||
1829 | |||
1830 | // Send Stream Begin 1 |
||
1831 | if ((ret = ff_rtmp_packet_create(&spkt, RTMP_NETWORK_CHANNEL, |
||
1832 | RTMP_PT_PING, 0, 6)) < 0) { |
||
1833 | av_log(s, AV_LOG_ERROR, "Unable to create response packet\n"); |
||
1834 | return ret; |
||
1835 | } |
||
1836 | |||
1837 | bytestream2_init_writer(&pbc, spkt.data, spkt.size); |
||
1838 | bytestream2_put_be16(&pbc, 0); // 0 -> Stream Begin |
||
1839 | bytestream2_put_be32(&pbc, rt->nb_streamid); |
||
1840 | |||
1841 | ret = ff_rtmp_packet_write(rt->stream, &spkt, rt->out_chunk_size, |
||
1842 | &rt->prev_pkt[1], &rt->nb_prev_pkt[1]); |
||
1843 | |||
1844 | ff_rtmp_packet_destroy(&spkt); |
||
1845 | |||
1846 | return ret; |
||
1847 | } |
||
1848 | |||
1849 | static int write_status(URLContext *s, RTMPPacket *pkt, |
||
1850 | const char *status, const char *filename) |
||
1851 | { |
||
1852 | RTMPContext *rt = s->priv_data; |
||
1853 | RTMPPacket spkt = { 0 }; |
||
1854 | char statusmsg[128]; |
||
1855 | uint8_t *pp; |
||
1856 | int ret; |
||
1857 | |||
1858 | if ((ret = ff_rtmp_packet_create(&spkt, RTMP_SYSTEM_CHANNEL, |
||
1859 | RTMP_PT_INVOKE, 0, |
||
1860 | RTMP_PKTDATA_DEFAULT_SIZE)) < 0) { |
||
1861 | av_log(s, AV_LOG_ERROR, "Unable to create response packet\n"); |
||
1862 | return ret; |
||
1863 | } |
||
1864 | |||
1865 | pp = spkt.data; |
||
1866 | spkt.extra = pkt->extra; |
||
1867 | ff_amf_write_string(&pp, "onStatus"); |
||
1868 | ff_amf_write_number(&pp, 0); |
||
1869 | ff_amf_write_null(&pp); |
||
1870 | |||
1871 | ff_amf_write_object_start(&pp); |
||
1872 | ff_amf_write_field_name(&pp, "level"); |
||
1873 | ff_amf_write_string(&pp, "status"); |
||
1874 | ff_amf_write_field_name(&pp, "code"); |
||
1875 | ff_amf_write_string(&pp, status); |
||
1876 | ff_amf_write_field_name(&pp, "description"); |
||
1877 | snprintf(statusmsg, sizeof(statusmsg), |
||
1878 | "%s is now published", filename); |
||
1879 | ff_amf_write_string(&pp, statusmsg); |
||
1880 | ff_amf_write_field_name(&pp, "details"); |
||
1881 | ff_amf_write_string(&pp, filename); |
||
1882 | ff_amf_write_field_name(&pp, "clientid"); |
||
1883 | snprintf(statusmsg, sizeof(statusmsg), "%s", LIBAVFORMAT_IDENT); |
||
1884 | ff_amf_write_string(&pp, statusmsg); |
||
1885 | ff_amf_write_object_end(&pp); |
||
1886 | |||
1887 | spkt.size = pp - spkt.data; |
||
1888 | ret = ff_rtmp_packet_write(rt->stream, &spkt, rt->out_chunk_size, |
||
1889 | &rt->prev_pkt[1], &rt->nb_prev_pkt[1]); |
||
1890 | ff_rtmp_packet_destroy(&spkt); |
||
1891 | |||
1892 | return ret; |
||
1893 | } |
||
1894 | |||
1895 | static int send_invoke_response(URLContext *s, RTMPPacket *pkt) |
||
1896 | { |
||
1897 | RTMPContext *rt = s->priv_data; |
||
1898 | double seqnum; |
||
1899 | char filename[64]; |
||
1900 | char command[64]; |
||
1901 | int stringlen; |
||
1902 | char *pchar; |
||
1903 | const uint8_t *p = pkt->data; |
||
1904 | uint8_t *pp = NULL; |
||
1905 | RTMPPacket spkt = { 0 }; |
||
1906 | GetByteContext gbc; |
||
1907 | int ret; |
||
1908 | |||
1909 | bytestream2_init(&gbc, p, pkt->size); |
||
1910 | if (ff_amf_read_string(&gbc, command, sizeof(command), |
||
1911 | &stringlen)) { |
||
1912 | av_log(s, AV_LOG_ERROR, "Error in PT_INVOKE\n"); |
||
1913 | return AVERROR_INVALIDDATA; |
||
1914 | } |
||
1915 | |||
1916 | ret = ff_amf_read_number(&gbc, &seqnum); |
||
1917 | if (ret) |
||
1918 | return ret; |
||
1919 | ret = ff_amf_read_null(&gbc); |
||
1920 | if (ret) |
||
1921 | return ret; |
||
1922 | if (!strcmp(command, "FCPublish") || |
||
1923 | !strcmp(command, "publish")) { |
||
1924 | ret = ff_amf_read_string(&gbc, filename, |
||
1925 | sizeof(filename), &stringlen); |
||
1926 | // check with url |
||
1927 | if (s->filename) { |
||
1928 | pchar = strrchr(s->filename, '/'); |
||
1929 | if (!pchar) { |
||
1930 | av_log(s, AV_LOG_WARNING, |
||
1931 | "Unable to find / in url %s, bad format\n", |
||
1932 | s->filename); |
||
1933 | pchar = s->filename; |
||
1934 | } |
||
1935 | pchar++; |
||
1936 | if (strcmp(pchar, filename)) |
||
1937 | av_log(s, AV_LOG_WARNING, "Unexpected stream %s, expecting" |
||
1938 | " %s\n", filename, pchar); |
||
1939 | } |
||
1940 | rt->state = STATE_RECEIVING; |
||
1941 | } |
||
1942 | |||
1943 | if (!strcmp(command, "FCPublish")) { |
||
1944 | if ((ret = ff_rtmp_packet_create(&spkt, RTMP_SYSTEM_CHANNEL, |
||
1945 | RTMP_PT_INVOKE, 0, |
||
1946 | RTMP_PKTDATA_DEFAULT_SIZE)) < 0) { |
||
1947 | av_log(s, AV_LOG_ERROR, "Unable to create response packet\n"); |
||
1948 | return ret; |
||
1949 | } |
||
1950 | pp = spkt.data; |
||
1951 | ff_amf_write_string(&pp, "onFCPublish"); |
||
1952 | } else if (!strcmp(command, "publish")) { |
||
1953 | ret = write_begin(s); |
||
1954 | if (ret < 0) |
||
1955 | return ret; |
||
1956 | |||
1957 | // Send onStatus(NetStream.Publish.Start) |
||
1958 | return write_status(s, pkt, "NetStream.Publish.Start", |
||
1959 | filename); |
||
1960 | } else if (!strcmp(command, "play")) { |
||
1961 | ret = write_begin(s); |
||
1962 | if (ret < 0) |
||
1963 | return ret; |
||
1964 | rt->state = STATE_SENDING; |
||
1965 | return write_status(s, pkt, "NetStream.Play.Start", |
||
1966 | filename); |
||
1967 | } else { |
||
1968 | if ((ret = ff_rtmp_packet_create(&spkt, RTMP_SYSTEM_CHANNEL, |
||
1969 | RTMP_PT_INVOKE, 0, |
||
1970 | RTMP_PKTDATA_DEFAULT_SIZE)) < 0) { |
||
1971 | av_log(s, AV_LOG_ERROR, "Unable to create response packet\n"); |
||
1972 | return ret; |
||
1973 | } |
||
1974 | pp = spkt.data; |
||
1975 | ff_amf_write_string(&pp, "_result"); |
||
1976 | ff_amf_write_number(&pp, seqnum); |
||
1977 | ff_amf_write_null(&pp); |
||
1978 | if (!strcmp(command, "createStream")) { |
||
1979 | rt->nb_streamid++; |
||
1980 | if (rt->nb_streamid == 0 || rt->nb_streamid == 2) |
||
1981 | rt->nb_streamid++; /* Values 0 and 2 are reserved */ |
||
1982 | ff_amf_write_number(&pp, rt->nb_streamid); |
||
1983 | /* By now we don't control which streams are removed in |
||
1984 | * deleteStream. There is no stream creation control |
||
1985 | * if a client creates more than 2^32 - 2 streams. */ |
||
1986 | } |
||
1987 | } |
||
1988 | spkt.size = pp - spkt.data; |
||
1989 | ret = ff_rtmp_packet_write(rt->stream, &spkt, rt->out_chunk_size, |
||
1990 | &rt->prev_pkt[1], &rt->nb_prev_pkt[1]); |
||
1991 | ff_rtmp_packet_destroy(&spkt); |
||
1992 | return ret; |
||
1993 | } |
||
1994 | |||
1995 | /** |
||
1996 | * Read the AMF_NUMBER response ("_result") to a function call |
||
1997 | * (e.g. createStream()). This response should be made up of the AMF_STRING |
||
1998 | * "result", a NULL object and then the response encoded as AMF_NUMBER. On a |
||
1999 | * successful response, we will return set the value to number (otherwise number |
||
2000 | * will not be changed). |
||
2001 | * |
||
2002 | * @return 0 if reading the value succeeds, negative value otherwiss |
||
2003 | */ |
||
2004 | static int read_number_result(RTMPPacket *pkt, double *number) |
||
2005 | { |
||
2006 | // We only need to fit "_result" in this. |
||
2007 | uint8_t strbuffer[8]; |
||
2008 | int stringlen; |
||
2009 | double numbuffer; |
||
2010 | GetByteContext gbc; |
||
2011 | |||
2012 | bytestream2_init(&gbc, pkt->data, pkt->size); |
||
2013 | |||
2014 | // Value 1/4: "_result" as AMF_STRING |
||
2015 | if (ff_amf_read_string(&gbc, strbuffer, sizeof(strbuffer), &stringlen)) |
||
2016 | return AVERROR_INVALIDDATA; |
||
2017 | if (strcmp(strbuffer, "_result")) |
||
2018 | return AVERROR_INVALIDDATA; |
||
2019 | // Value 2/4: The callee reference number |
||
2020 | if (ff_amf_read_number(&gbc, &numbuffer)) |
||
2021 | return AVERROR_INVALIDDATA; |
||
2022 | // Value 3/4: Null |
||
2023 | if (ff_amf_read_null(&gbc)) |
||
2024 | return AVERROR_INVALIDDATA; |
||
2025 | // Value 4/4: The resonse as AMF_NUMBER |
||
2026 | if (ff_amf_read_number(&gbc, &numbuffer)) |
||
2027 | return AVERROR_INVALIDDATA; |
||
2028 | else |
||
2029 | *number = numbuffer; |
||
2030 | |||
2031 | return 0; |
||
2032 | } |
||
2033 | |||
2034 | static int handle_invoke_result(URLContext *s, RTMPPacket *pkt) |
||
2035 | { |
||
2036 | RTMPContext *rt = s->priv_data; |
||
2037 | char *tracked_method = NULL; |
||
2038 | int ret = 0; |
||
2039 | |||
2040 | if ((ret = find_tracked_method(s, pkt, 10, &tracked_method)) < 0) |
||
2041 | return ret; |
||
2042 | |||
2043 | if (!tracked_method) { |
||
2044 | /* Ignore this reply when the current method is not tracked. */ |
||
2045 | return ret; |
||
2046 | } |
||
2047 | |||
2048 | if (!strcmp(tracked_method, "connect")) { |
||
2049 | if (!rt->is_input) { |
||
2050 | if ((ret = gen_release_stream(s, rt)) < 0) |
||
2051 | goto fail; |
||
2052 | |||
2053 | if ((ret = gen_fcpublish_stream(s, rt)) < 0) |
||
2054 | goto fail; |
||
2055 | } else { |
||
2056 | if ((ret = gen_server_bw(s, rt)) < 0) |
||
2057 | goto fail; |
||
2058 | } |
||
2059 | |||
2060 | if ((ret = gen_create_stream(s, rt)) < 0) |
||
2061 | goto fail; |
||
2062 | |||
2063 | if (rt->is_input) { |
||
2064 | /* Send the FCSubscribe command when the name of live |
||
2065 | * stream is defined by the user or if it's a live stream. */ |
||
2066 | if (rt->subscribe) { |
||
2067 | if ((ret = gen_fcsubscribe_stream(s, rt, rt->subscribe)) < 0) |
||
2068 | goto fail; |
||
2069 | } else if (rt->live == -1) { |
||
2070 | if ((ret = gen_fcsubscribe_stream(s, rt, rt->playpath)) < 0) |
||
2071 | goto fail; |
||
2072 | } |
||
2073 | } |
||
2074 | } else if (!strcmp(tracked_method, "createStream")) { |
||
2075 | double stream_id; |
||
2076 | if (read_number_result(pkt, &stream_id)) { |
||
2077 | av_log(s, AV_LOG_WARNING, "Unexpected reply on connect()\n"); |
||
2078 | } else { |
||
2079 | rt->stream_id = stream_id; |
||
2080 | } |
||
2081 | |||
2082 | if (!rt->is_input) { |
||
2083 | if ((ret = gen_publish(s, rt)) < 0) |
||
2084 | goto fail; |
||
2085 | } else { |
||
2086 | if (rt->live != -1) { |
||
2087 | if ((ret = gen_get_stream_length(s, rt)) < 0) |
||
2088 | goto fail; |
||
2089 | } |
||
2090 | if ((ret = gen_play(s, rt)) < 0) |
||
2091 | goto fail; |
||
2092 | if ((ret = gen_buffer_time(s, rt)) < 0) |
||
2093 | goto fail; |
||
2094 | } |
||
2095 | } else if (!strcmp(tracked_method, "getStreamLength")) { |
||
2096 | if (read_number_result(pkt, &rt->duration)) { |
||
2097 | av_log(s, AV_LOG_WARNING, "Unexpected reply on getStreamLength()\n"); |
||
2098 | } |
||
2099 | } |
||
2100 | |||
2101 | fail: |
||
2102 | av_free(tracked_method); |
||
2103 | return ret; |
||
2104 | } |
||
2105 | |||
2106 | static int handle_invoke_status(URLContext *s, RTMPPacket *pkt) |
||
2107 | { |
||
2108 | RTMPContext *rt = s->priv_data; |
||
2109 | const uint8_t *data_end = pkt->data + pkt->size; |
||
2110 | const uint8_t *ptr = pkt->data + RTMP_HEADER; |
||
2111 | uint8_t tmpstr[256]; |
||
2112 | int i, t; |
||
2113 | |||
2114 | for (i = 0; i < 2; i++) { |
||
2115 | t = ff_amf_tag_size(ptr, data_end); |
||
2116 | if (t < 0) |
||
2117 | return 1; |
||
2118 | ptr += t; |
||
2119 | } |
||
2120 | |||
2121 | t = ff_amf_get_field_value(ptr, data_end, "level", tmpstr, sizeof(tmpstr)); |
||
2122 | if (!t && !strcmp(tmpstr, "error")) { |
||
2123 | t = ff_amf_get_field_value(ptr, data_end, |
||
2124 | "description", tmpstr, sizeof(tmpstr)); |
||
2125 | if (t || !tmpstr[0]) |
||
2126 | t = ff_amf_get_field_value(ptr, data_end, "code", |
||
2127 | tmpstr, sizeof(tmpstr)); |
||
2128 | if (!t) |
||
2129 | av_log(s, AV_LOG_ERROR, "Server error: %s\n", tmpstr); |
||
2130 | return -1; |
||
2131 | } |
||
2132 | |||
2133 | t = ff_amf_get_field_value(ptr, data_end, "code", tmpstr, sizeof(tmpstr)); |
||
2134 | if (!t && !strcmp(tmpstr, "NetStream.Play.Start")) rt->state = STATE_PLAYING; |
||
2135 | if (!t && !strcmp(tmpstr, "NetStream.Play.Stop")) rt->state = STATE_STOPPED; |
||
2136 | if (!t && !strcmp(tmpstr, "NetStream.Play.UnpublishNotify")) rt->state = STATE_STOPPED; |
||
2137 | if (!t && !strcmp(tmpstr, "NetStream.Publish.Start")) rt->state = STATE_PUBLISHING; |
||
2138 | if (!t && !strcmp(tmpstr, "NetStream.Seek.Notify")) rt->state = STATE_PLAYING; |
||
2139 | |||
2140 | return 0; |
||
2141 | } |
||
2142 | |||
2143 | static int handle_invoke(URLContext *s, RTMPPacket *pkt) |
||
2144 | { |
||
2145 | RTMPContext *rt = s->priv_data; |
||
2146 | int ret = 0; |
||
2147 | |||
2148 | //TODO: check for the messages sent for wrong state? |
||
2149 | if (ff_amf_match_string(pkt->data, pkt->size, "_error")) { |
||
2150 | if ((ret = handle_invoke_error(s, pkt)) < 0) |
||
2151 | return ret; |
||
2152 | } else if (ff_amf_match_string(pkt->data, pkt->size, "_result")) { |
||
2153 | if ((ret = handle_invoke_result(s, pkt)) < 0) |
||
2154 | return ret; |
||
2155 | } else if (ff_amf_match_string(pkt->data, pkt->size, "onStatus")) { |
||
2156 | if ((ret = handle_invoke_status(s, pkt)) < 0) |
||
2157 | return ret; |
||
2158 | } else if (ff_amf_match_string(pkt->data, pkt->size, "onBWDone")) { |
||
2159 | if ((ret = gen_check_bw(s, rt)) < 0) |
||
2160 | return ret; |
||
2161 | } else if (ff_amf_match_string(pkt->data, pkt->size, "releaseStream") || |
||
2162 | ff_amf_match_string(pkt->data, pkt->size, "FCPublish") || |
||
2163 | ff_amf_match_string(pkt->data, pkt->size, "publish") || |
||
2164 | ff_amf_match_string(pkt->data, pkt->size, "play") || |
||
2165 | ff_amf_match_string(pkt->data, pkt->size, "_checkbw") || |
||
2166 | ff_amf_match_string(pkt->data, pkt->size, "createStream")) { |
||
2167 | if ((ret = send_invoke_response(s, pkt)) < 0) |
||
2168 | return ret; |
||
2169 | } |
||
2170 | |||
2171 | return ret; |
||
2172 | } |
||
2173 | |||
2174 | static int update_offset(RTMPContext *rt, int size) |
||
2175 | { |
||
2176 | int old_flv_size; |
||
2177 | |||
2178 | // generate packet header and put data into buffer for FLV demuxer |
||
2179 | if (rt->flv_off < rt->flv_size) { |
||
2180 | // There is old unread data in the buffer, thus append at the end |
||
2181 | old_flv_size = rt->flv_size; |
||
2182 | rt->flv_size += size; |
||
2183 | } else { |
||
2184 | // All data has been read, write the new data at the start of the buffer |
||
2185 | old_flv_size = 0; |
||
2186 | rt->flv_size = size; |
||
2187 | rt->flv_off = 0; |
||
2188 | } |
||
2189 | |||
2190 | return old_flv_size; |
||
2191 | } |
||
2192 | |||
2193 | static int append_flv_data(RTMPContext *rt, RTMPPacket *pkt, int skip) |
||
2194 | { |
||
2195 | int old_flv_size, ret; |
||
2196 | PutByteContext pbc; |
||
2197 | const uint8_t *data = pkt->data + skip; |
||
2198 | const int size = pkt->size - skip; |
||
2199 | uint32_t ts = pkt->timestamp; |
||
2200 | |||
2201 | if (pkt->type == RTMP_PT_AUDIO) { |
||
2202 | rt->has_audio = 1; |
||
2203 | } else if (pkt->type == RTMP_PT_VIDEO) { |
||
2204 | rt->has_video = 1; |
||
2205 | } |
||
2206 | |||
2207 | old_flv_size = update_offset(rt, size + 15); |
||
2208 | |||
2209 | if ((ret = av_reallocp(&rt->flv_data, rt->flv_size)) < 0) { |
||
2210 | rt->flv_size = rt->flv_off = 0; |
||
2211 | return ret; |
||
2212 | } |
||
2213 | bytestream2_init_writer(&pbc, rt->flv_data, rt->flv_size); |
||
2214 | bytestream2_skip_p(&pbc, old_flv_size); |
||
2215 | bytestream2_put_byte(&pbc, pkt->type); |
||
2216 | bytestream2_put_be24(&pbc, size); |
||
2217 | bytestream2_put_be24(&pbc, ts); |
||
2218 | bytestream2_put_byte(&pbc, ts >> 24); |
||
2219 | bytestream2_put_be24(&pbc, 0); |
||
2220 | bytestream2_put_buffer(&pbc, data, size); |
||
2221 | bytestream2_put_be32(&pbc, 0); |
||
2222 | |||
2223 | return 0; |
||
2224 | } |
||
2225 | |||
2226 | static int handle_notify(URLContext *s, RTMPPacket *pkt) |
||
2227 | { |
||
2228 | RTMPContext *rt = s->priv_data; |
||
2229 | uint8_t commandbuffer[64]; |
||
2230 | char statusmsg[128]; |
||
2231 | int stringlen, ret, skip = 0; |
||
2232 | GetByteContext gbc; |
||
2233 | |||
2234 | bytestream2_init(&gbc, pkt->data, pkt->size); |
||
2235 | if (ff_amf_read_string(&gbc, commandbuffer, sizeof(commandbuffer), |
||
2236 | &stringlen)) |
||
2237 | return AVERROR_INVALIDDATA; |
||
2238 | |||
2239 | if (!strcmp(commandbuffer, "onMetaData")) { |
||
2240 | // metadata properties should be stored in a mixed array |
||
2241 | if (bytestream2_get_byte(&gbc) == AMF_DATA_TYPE_MIXEDARRAY) { |
||
2242 | // We have found a metaData Array so flv can determine the streams |
||
2243 | // from this. |
||
2244 | rt->received_metadata = 1; |
||
2245 | // skip 32-bit max array index |
||
2246 | bytestream2_skip(&gbc, 4); |
||
2247 | while (bytestream2_get_bytes_left(&gbc) > 3) { |
||
2248 | if (ff_amf_get_string(&gbc, statusmsg, sizeof(statusmsg), |
||
2249 | &stringlen)) |
||
2250 | return AVERROR_INVALIDDATA; |
||
2251 | // We do not care about the content of the property (yet). |
||
2252 | stringlen = ff_amf_tag_size(gbc.buffer, gbc.buffer_end); |
||
2253 | if (stringlen < 0) |
||
2254 | return AVERROR_INVALIDDATA; |
||
2255 | bytestream2_skip(&gbc, stringlen); |
||
2256 | |||
2257 | // The presence of the following properties indicates that the |
||
2258 | // respective streams are present. |
||
2259 | if (!strcmp(statusmsg, "videocodecid")) { |
||
2260 | rt->has_video = 1; |
||
2261 | } |
||
2262 | if (!strcmp(statusmsg, "audiocodecid")) { |
||
2263 | rt->has_audio = 1; |
||
2264 | } |
||
2265 | } |
||
2266 | if (bytestream2_get_be24(&gbc) != AMF_END_OF_OBJECT) |
||
2267 | return AVERROR_INVALIDDATA; |
||
2268 | } |
||
2269 | } |
||
2270 | |||
2271 | // Skip the @setDataFrame string and validate it is a notification |
||
2272 | if (!strcmp(commandbuffer, "@setDataFrame")) { |
||
2273 | skip = gbc.buffer - pkt->data; |
||
2274 | ret = ff_amf_read_string(&gbc, statusmsg, |
||
2275 | sizeof(statusmsg), &stringlen); |
||
2276 | if (ret < 0) |
||
2277 | return AVERROR_INVALIDDATA; |
||
2278 | } |
||
2279 | |||
2280 | return append_flv_data(rt, pkt, skip); |
||
2281 | } |
||
2282 | |||
2283 | /** |
||
2284 | * Parse received packet and possibly perform some action depending on |
||
2285 | * the packet contents. |
||
2286 | * @return 0 for no errors, negative values for serious errors which prevent |
||
2287 | * further communications, positive values for uncritical errors |
||
2288 | */ |
||
2289 | static int rtmp_parse_result(URLContext *s, RTMPContext *rt, RTMPPacket *pkt) |
||
2290 | { |
||
2291 | int ret; |
||
2292 | |||
2293 | #ifdef DEBUG |
||
2294 | ff_rtmp_packet_dump(s, pkt); |
||
2295 | #endif |
||
2296 | |||
2297 | switch (pkt->type) { |
||
2298 | case RTMP_PT_BYTES_READ: |
||
2299 | av_log(s, AV_LOG_TRACE, "received bytes read report\n"); |
||
2300 | break; |
||
2301 | case RTMP_PT_CHUNK_SIZE: |
||
2302 | if ((ret = handle_chunk_size(s, pkt)) < 0) |
||
2303 | return ret; |
||
2304 | break; |
||
2305 | case RTMP_PT_PING: |
||
2306 | if ((ret = handle_ping(s, pkt)) < 0) |
||
2307 | return ret; |
||
2308 | break; |
||
2309 | case RTMP_PT_CLIENT_BW: |
||
2310 | if ((ret = handle_client_bw(s, pkt)) < 0) |
||
2311 | return ret; |
||
2312 | break; |
||
2313 | case RTMP_PT_SERVER_BW: |
||
2314 | if ((ret = handle_server_bw(s, pkt)) < 0) |
||
2315 | return ret; |
||
2316 | break; |
||
2317 | case RTMP_PT_INVOKE: |
||
2318 | if ((ret = handle_invoke(s, pkt)) < 0) |
||
2319 | return ret; |
||
2320 | break; |
||
2321 | case RTMP_PT_VIDEO: |
||
2322 | case RTMP_PT_AUDIO: |
||
2323 | case RTMP_PT_METADATA: |
||
2324 | case RTMP_PT_NOTIFY: |
||
2325 | /* Audio, Video and Metadata packets are parsed in get_packet() */ |
||
2326 | break; |
||
2327 | default: |
||
2328 | av_log(s, AV_LOG_VERBOSE, "Unknown packet type received 0x%02X\n", pkt->type); |
||
2329 | break; |
||
2330 | } |
||
2331 | return 0; |
||
2332 | } |
||
2333 | |||
2334 | static int handle_metadata(RTMPContext *rt, RTMPPacket *pkt) |
||
2335 | { |
||
2336 | int ret, old_flv_size, type; |
||
2337 | const uint8_t *next; |
||
2338 | uint8_t *p; |
||
2339 | uint32_t size; |
||
2340 | uint32_t ts, cts, pts = 0; |
||
2341 | |||
2342 | old_flv_size = update_offset(rt, pkt->size); |
||
2343 | |||
2344 | if ((ret = av_reallocp(&rt->flv_data, rt->flv_size)) < 0) { |
||
2345 | rt->flv_size = rt->flv_off = 0; |
||
2346 | return ret; |
||
2347 | } |
||
2348 | |||
2349 | next = pkt->data; |
||
2350 | p = rt->flv_data + old_flv_size; |
||
2351 | |||
2352 | /* copy data while rewriting timestamps */ |
||
2353 | ts = pkt->timestamp; |
||
2354 | |||
2355 | while (next - pkt->data < pkt->size - RTMP_HEADER) { |
||
2356 | type = bytestream_get_byte(&next); |
||
2357 | size = bytestream_get_be24(&next); |
||
2358 | cts = bytestream_get_be24(&next); |
||
2359 | cts |= bytestream_get_byte(&next) << 24; |
||
2360 | if (!pts) |
||
2361 | pts = cts; |
||
2362 | ts += cts - pts; |
||
2363 | pts = cts; |
||
2364 | if (size + 3 + 4 > pkt->data + pkt->size - next) |
||
2365 | break; |
||
2366 | bytestream_put_byte(&p, type); |
||
2367 | bytestream_put_be24(&p, size); |
||
2368 | bytestream_put_be24(&p, ts); |
||
2369 | bytestream_put_byte(&p, ts >> 24); |
||
2370 | memcpy(p, next, size + 3 + 4); |
||
2371 | next += size + 3 + 4; |
||
2372 | p += size + 3 + 4; |
||
2373 | } |
||
2374 | if (p != rt->flv_data + rt->flv_size) { |
||
2375 | av_log(NULL, AV_LOG_WARNING, "Incomplete flv packets in " |
||
2376 | "RTMP_PT_METADATA packet\n"); |
||
2377 | rt->flv_size = p - rt->flv_data; |
||
2378 | } |
||
2379 | |||
2380 | return 0; |
||
2381 | } |
||
2382 | |||
2383 | /** |
||
2384 | * Interact with the server by receiving and sending RTMP packets until |
||
2385 | * there is some significant data (media data or expected status notification). |
||
2386 | * |
||
2387 | * @param s reading context |
||
2388 | * @param for_header non-zero value tells function to work until it |
||
2389 | * gets notification from the server that playing has been started, |
||
2390 | * otherwise function will work until some media data is received (or |
||
2391 | * an error happens) |
||
2392 | * @return 0 for successful operation, negative value in case of error |
||
2393 | */ |
||
2394 | static int get_packet(URLContext *s, int for_header) |
||
2395 | { |
||
2396 | RTMPContext *rt = s->priv_data; |
||
2397 | int ret; |
||
2398 | |||
2399 | if (rt->state == STATE_STOPPED) |
||
2400 | return AVERROR_EOF; |
||
2401 | |||
2402 | for (;;) { |
||
2403 | RTMPPacket rpkt = { 0 }; |
||
2404 | if ((ret = ff_rtmp_packet_read(rt->stream, &rpkt, |
||
2405 | rt->in_chunk_size, &rt->prev_pkt[0], |
||
2406 | &rt->nb_prev_pkt[0])) <= 0) { |
||
2407 | if (ret == 0) { |
||
2408 | return AVERROR(EAGAIN); |
||
2409 | } else { |
||
2410 | return AVERROR(EIO); |
||
2411 | } |
||
2412 | } |
||
2413 | |||
2414 | // Track timestamp for later use |
||
2415 | rt->last_timestamp = rpkt.timestamp; |
||
2416 | |||
2417 | rt->bytes_read += ret; |
||
2418 | if (rt->bytes_read - rt->last_bytes_read > rt->client_report_size) { |
||
2419 | av_log(s, AV_LOG_DEBUG, "Sending bytes read report\n"); |
||
2420 | if ((ret = gen_bytes_read(s, rt, rpkt.timestamp + 1)) < 0) |
||
2421 | return ret; |
||
2422 | rt->last_bytes_read = rt->bytes_read; |
||
2423 | } |
||
2424 | |||
2425 | ret = rtmp_parse_result(s, rt, &rpkt); |
||
2426 | |||
2427 | // At this point we must check if we are in the seek state and continue |
||
2428 | // with the next packet. handle_invoke will get us out of this state |
||
2429 | // when the right message is encountered |
||
2430 | if (rt->state == STATE_SEEKING) { |
||
2431 | ff_rtmp_packet_destroy(&rpkt); |
||
2432 | // We continue, let the natural flow of things happen: |
||
2433 | // AVERROR(EAGAIN) or handle_invoke gets us out of here |
||
2434 | continue; |
||
2435 | } |
||
2436 | |||
2437 | if (ret < 0) {//serious error in current packet |
||
2438 | ff_rtmp_packet_destroy(&rpkt); |
||
2439 | return ret; |
||
2440 | } |
||
2441 | if (rt->do_reconnect && for_header) { |
||
2442 | ff_rtmp_packet_destroy(&rpkt); |
||
2443 | return 0; |
||
2444 | } |
||
2445 | if (rt->state == STATE_STOPPED) { |
||
2446 | ff_rtmp_packet_destroy(&rpkt); |
||
2447 | return AVERROR_EOF; |
||
2448 | } |
||
2449 | if (for_header && (rt->state == STATE_PLAYING || |
||
2450 | rt->state == STATE_PUBLISHING || |
||
2451 | rt->state == STATE_SENDING || |
||
2452 | rt->state == STATE_RECEIVING)) { |
||
2453 | ff_rtmp_packet_destroy(&rpkt); |
||
2454 | return 0; |
||
2455 | } |
||
2456 | if (!rpkt.size || !rt->is_input) { |
||
2457 | ff_rtmp_packet_destroy(&rpkt); |
||
2458 | continue; |
||
2459 | } |
||
2460 | if (rpkt.type == RTMP_PT_VIDEO || rpkt.type == RTMP_PT_AUDIO) { |
||
2461 | ret = append_flv_data(rt, &rpkt, 0); |
||
2462 | ff_rtmp_packet_destroy(&rpkt); |
||
2463 | return ret; |
||
2464 | } else if (rpkt.type == RTMP_PT_NOTIFY) { |
||
2465 | ret = handle_notify(s, &rpkt); |
||
2466 | ff_rtmp_packet_destroy(&rpkt); |
||
2467 | return ret; |
||
2468 | } else if (rpkt.type == RTMP_PT_METADATA) { |
||
2469 | ret = handle_metadata(rt, &rpkt); |
||
2470 | ff_rtmp_packet_destroy(&rpkt); |
||
2471 | return 0; |
||
2472 | } |
||
2473 | ff_rtmp_packet_destroy(&rpkt); |
||
2474 | } |
||
2475 | } |
||
2476 | |||
2477 | static int rtmp_close(URLContext *h) |
||
2478 | { |
||
2479 | RTMPContext *rt = h->priv_data; |
||
2480 | int ret = 0, i, j; |
||
2481 | |||
2482 | if (!rt->is_input) { |
||
2483 | rt->flv_data = NULL; |
||
2484 | if (rt->out_pkt.size) |
||
2485 | ff_rtmp_packet_destroy(&rt->out_pkt); |
||
2486 | if (rt->state > STATE_FCPUBLISH) |
||
2487 | ret = gen_fcunpublish_stream(h, rt); |
||
2488 | } |
||
2489 | if (rt->state > STATE_HANDSHAKED) |
||
2490 | ret = gen_delete_stream(h, rt); |
||
2491 | for (i = 0; i < 2; i++) { |
||
2492 | for (j = 0; j < rt->nb_prev_pkt[i]; j++) |
||
2493 | ff_rtmp_packet_destroy(&rt->prev_pkt[i][j]); |
||
2494 | av_freep(&rt->prev_pkt[i]); |
||
2495 | } |
||
2496 | |||
2497 | free_tracked_methods(rt); |
||
2498 | av_freep(&rt->flv_data); |
||
2499 | ffurl_close(rt->stream); |
||
2500 | return ret; |
||
2501 | } |
||
2502 | |||
2503 | /** |
||
2504 | * Insert a fake onMetadata packet into the FLV stream to notify the FLV |
||
2505 | * demuxer about the duration of the stream. |
||
2506 | * |
||
2507 | * This should only be done if there was no real onMetadata packet sent by the |
||
2508 | * server at the start of the stream and if we were able to retrieve a valid |
||
2509 | * duration via a getStreamLength call. |
||
2510 | * |
||
2511 | * @return 0 for successful operation, negative value in case of error |
||
2512 | */ |
||
2513 | static int inject_fake_duration_metadata(RTMPContext *rt) |
||
2514 | { |
||
2515 | // We need to insert the metdata packet directly after the FLV |
||
2516 | // header, i.e. we need to move all other already read data by the |
||
2517 | // size of our fake metadata packet. |
||
2518 | |||
2519 | uint8_t* p; |
||
2520 | // Keep old flv_data pointer |
||
2521 | uint8_t* old_flv_data = rt->flv_data; |
||
2522 | // Allocate a new flv_data pointer with enough space for the additional package |
||
2523 | if (!(rt->flv_data = av_malloc(rt->flv_size + 55))) { |
||
2524 | rt->flv_data = old_flv_data; |
||
2525 | return AVERROR(ENOMEM); |
||
2526 | } |
||
2527 | |||
2528 | // Copy FLV header |
||
2529 | memcpy(rt->flv_data, old_flv_data, 13); |
||
2530 | // Copy remaining packets |
||
2531 | memcpy(rt->flv_data + 13 + 55, old_flv_data + 13, rt->flv_size - 13); |
||
2532 | // Increase the size by the injected packet |
||
2533 | rt->flv_size += 55; |
||
2534 | // Delete the old FLV data |
||
2535 | av_freep(&old_flv_data); |
||
2536 | |||
2537 | p = rt->flv_data + 13; |
||
2538 | bytestream_put_byte(&p, FLV_TAG_TYPE_META); |
||
2539 | bytestream_put_be24(&p, 40); // size of data part (sum of all parts below) |
||
2540 | bytestream_put_be24(&p, 0); // timestamp |
||
2541 | bytestream_put_be32(&p, 0); // reserved |
||
2542 | |||
2543 | // first event name as a string |
||
2544 | bytestream_put_byte(&p, AMF_DATA_TYPE_STRING); |
||
2545 | // "onMetaData" as AMF string |
||
2546 | bytestream_put_be16(&p, 10); |
||
2547 | bytestream_put_buffer(&p, "onMetaData", 10); |
||
2548 | |||
2549 | // mixed array (hash) with size and string/type/data tuples |
||
2550 | bytestream_put_byte(&p, AMF_DATA_TYPE_MIXEDARRAY); |
||
2551 | bytestream_put_be32(&p, 1); // metadata_count |
||
2552 | |||
2553 | // "duration" as AMF string |
||
2554 | bytestream_put_be16(&p, 8); |
||
2555 | bytestream_put_buffer(&p, "duration", 8); |
||
2556 | bytestream_put_byte(&p, AMF_DATA_TYPE_NUMBER); |
||
2557 | bytestream_put_be64(&p, av_double2int(rt->duration)); |
||
2558 | |||
2559 | // Finalise object |
||
2560 | bytestream_put_be16(&p, 0); // Empty string |
||
2561 | bytestream_put_byte(&p, AMF_END_OF_OBJECT); |
||
2562 | bytestream_put_be32(&p, 40); // size of data part (sum of all parts below) |
||
2563 | |||
2564 | return 0; |
||
2565 | } |
||
2566 | |||
2567 | /** |
||
2568 | * Open RTMP connection and verify that the stream can be played. |
||
2569 | * |
||
2570 | * URL syntax: rtmp://server[:port][/app][/playpath] |
||
2571 | * where 'app' is first one or two directories in the path |
||
2572 | * (e.g. /ondemand/, /flash/live/, etc.) |
||
2573 | * and 'playpath' is a file name (the rest of the path, |
||
2574 | * may be prefixed with "mp4:") |
||
2575 | */ |
||
2576 | static int rtmp_open(URLContext *s, const char *uri, int flags) |
||
2577 | { |
||
2578 | RTMPContext *rt = s->priv_data; |
||
2579 | char proto[8], hostname[256], path[1024], auth[100], *fname; |
||
2580 | char *old_app, *qmark, *n, fname_buffer[1024]; |
||
2581 | uint8_t buf[2048]; |
||
2582 | int port; |
||
2583 | AVDictionary *opts = NULL; |
||
2584 | int ret; |
||
2585 | |||
2586 | if (rt->listen_timeout > 0) |
||
2587 | rt->listen = 1; |
||
2588 | |||
2589 | rt->is_input = !(flags & AVIO_FLAG_WRITE); |
||
2590 | |||
2591 | av_url_split(proto, sizeof(proto), auth, sizeof(auth), |
||
2592 | hostname, sizeof(hostname), &port, |
||
2593 | path, sizeof(path), s->filename); |
||
2594 | |||
2595 | n = strchr(path, ' '); |
||
2596 | if (n) { |
||
2597 | av_log(s, AV_LOG_WARNING, |
||
2598 | "Detected librtmp style URL parameters, these aren't supported " |
||
2599 | "by the libavformat internal RTMP handler currently enabled. " |
||
2600 | "See the documentation for the correct way to pass parameters.\n"); |
||
2601 | *n = '\0'; // Trim not supported part |
||
2602 | } |
||
2603 | |||
2604 | if (auth[0]) { |
||
2605 | char *ptr = strchr(auth, ':'); |
||
2606 | if (ptr) { |
||
2607 | *ptr = '\0'; |
||
2608 | av_strlcpy(rt->username, auth, sizeof(rt->username)); |
||
2609 | av_strlcpy(rt->password, ptr + 1, sizeof(rt->password)); |
||
2610 | } |
||
2611 | } |
||
2612 | |||
2613 | if (rt->listen && strcmp(proto, "rtmp")) { |
||
2614 | av_log(s, AV_LOG_ERROR, "rtmp_listen not available for %s\n", |
||
2615 | proto); |
||
2616 | return AVERROR(EINVAL); |
||
2617 | } |
||
2618 | if (!strcmp(proto, "rtmpt") || !strcmp(proto, "rtmpts")) { |
||
2619 | if (!strcmp(proto, "rtmpts")) |
||
2620 | av_dict_set(&opts, "ffrtmphttp_tls", "1", 1); |
||
2621 | |||
2622 | /* open the http tunneling connection */ |
||
2623 | ff_url_join(buf, sizeof(buf), "ffrtmphttp", NULL, hostname, port, NULL); |
||
2624 | } else if (!strcmp(proto, "rtmps")) { |
||
2625 | /* open the tls connection */ |
||
2626 | if (port < 0) |
||
2627 | port = RTMPS_DEFAULT_PORT; |
||
2628 | ff_url_join(buf, sizeof(buf), "tls", NULL, hostname, port, NULL); |
||
2629 | } else if (!strcmp(proto, "rtmpe") || (!strcmp(proto, "rtmpte"))) { |
||
2630 | if (!strcmp(proto, "rtmpte")) |
||
2631 | av_dict_set(&opts, "ffrtmpcrypt_tunneling", "1", 1); |
||
2632 | |||
2633 | /* open the encrypted connection */ |
||
2634 | ff_url_join(buf, sizeof(buf), "ffrtmpcrypt", NULL, hostname, port, NULL); |
||
2635 | rt->encrypted = 1; |
||
2636 | } else { |
||
2637 | /* open the tcp connection */ |
||
2638 | if (port < 0) |
||
2639 | port = RTMP_DEFAULT_PORT; |
||
2640 | if (rt->listen) |
||
2641 | ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, |
||
2642 | "?listen&listen_timeout=%d", |
||
2643 | rt->listen_timeout * 1000); |
||
2644 | else |
||
2645 | ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, NULL); |
||
2646 | } |
||
2647 | |||
2648 | reconnect: |
||
2649 | if ((ret = ffurl_open(&rt->stream, buf, AVIO_FLAG_READ_WRITE, |
||
2650 | &s->interrupt_callback, &opts)) < 0) { |
||
2651 | av_log(s , AV_LOG_ERROR, "Cannot open connection %s\n", buf); |
||
2652 | goto fail; |
||
2653 | } |
||
2654 | |||
2655 | if (rt->swfverify) { |
||
2656 | if ((ret = rtmp_calc_swfhash(s)) < 0) |
||
2657 | goto fail; |
||
2658 | } |
||
2659 | |||
2660 | rt->state = STATE_START; |
||
2661 | if (!rt->listen && (ret = rtmp_handshake(s, rt)) < 0) |
||
2662 | goto fail; |
||
2663 | if (rt->listen && (ret = rtmp_server_handshake(s, rt)) < 0) |
||
2664 | goto fail; |
||
2665 | |||
2666 | rt->out_chunk_size = 128; |
||
2667 | rt->in_chunk_size = 128; // Probably overwritten later |
||
2668 | rt->state = STATE_HANDSHAKED; |
||
2669 | |||
2670 | // Keep the application name when it has been defined by the user. |
||
2671 | old_app = rt->app; |
||
2672 | |||
2673 | rt->app = av_malloc(APP_MAX_LENGTH); |
||
2674 | if (!rt->app) { |
||
2675 | ret = AVERROR(ENOMEM); |
||
2676 | goto fail; |
||
2677 | } |
||
2678 | |||
2679 | //extract "app" part from path |
||
2680 | qmark = strchr(path, '?'); |
||
2681 | if (qmark && strstr(qmark, "slist=")) { |
||
2682 | char* amp; |
||
2683 | // After slist we have the playpath, before the params, the app |
||
2684 | av_strlcpy(rt->app, path + 1, FFMIN(qmark - path, APP_MAX_LENGTH)); |
||
2685 | fname = strstr(path, "slist=") + 6; |
||
2686 | // Strip any further query parameters from fname |
||
2687 | amp = strchr(fname, '&'); |
||
2688 | if (amp) { |
||
2689 | av_strlcpy(fname_buffer, fname, FFMIN(amp - fname + 1, |
||
2690 | sizeof(fname_buffer))); |
||
2691 | fname = fname_buffer; |
||
2692 | } |
||
2693 | } else if (!strncmp(path, "/ondemand/", 10)) { |
||
2694 | fname = path + 10; |
||
2695 | memcpy(rt->app, "ondemand", 9); |
||
2696 | } else { |
||
2697 | char *next = *path ? path + 1 : path; |
||
2698 | char *p = strchr(next, '/'); |
||
2699 | if (!p) { |
||
2700 | if (old_app) { |
||
2701 | // If name of application has been defined by the user, assume that |
||
2702 | // playpath is provided in the URL |
||
2703 | fname = next; |
||
2704 | } else { |
||
2705 | fname = NULL; |
||
2706 | av_strlcpy(rt->app, next, APP_MAX_LENGTH); |
||
2707 | } |
||
2708 | } else { |
||
2709 | // make sure we do not mismatch a playpath for an application instance |
||
2710 | char *c = strchr(p + 1, ':'); |
||
2711 | fname = strchr(p + 1, '/'); |
||
2712 | if (!fname || (c && c < fname)) { |
||
2713 | fname = p + 1; |
||
2714 | av_strlcpy(rt->app, path + 1, FFMIN(p - path, APP_MAX_LENGTH)); |
||
2715 | } else { |
||
2716 | fname++; |
||
2717 | av_strlcpy(rt->app, path + 1, FFMIN(fname - path - 1, APP_MAX_LENGTH)); |
||
2718 | } |
||
2719 | } |
||
2720 | } |
||
2721 | |||
2722 | if (old_app) { |
||
2723 | // The name of application has been defined by the user, override it. |
||
2724 | if (strlen(old_app) >= APP_MAX_LENGTH) { |
||
2725 | ret = AVERROR(EINVAL); |
||
2726 | goto fail; |
||
2727 | } |
||
2728 | av_free(rt->app); |
||
2729 | rt->app = old_app; |
||
2730 | } |
||
2731 | |||
2732 | if (!rt->playpath) { |
||
2733 | rt->playpath = av_malloc(PLAYPATH_MAX_LENGTH); |
||
2734 | if (!rt->playpath) { |
||
2735 | ret = AVERROR(ENOMEM); |
||
2736 | goto fail; |
||
2737 | } |
||
2738 | |||
2739 | if (fname) { |
||
2740 | int len = strlen(fname); |
||
2741 | if (!strchr(fname, ':') && len >= 4 && |
||
2742 | (!strcmp(fname + len - 4, ".f4v") || |
||
2743 | !strcmp(fname + len - 4, ".mp4"))) { |
||
2744 | memcpy(rt->playpath, "mp4:", 5); |
||
2745 | } else { |
||
2746 | if (len >= 4 && !strcmp(fname + len - 4, ".flv")) |
||
2747 | fname[len - 4] = '\0'; |
||
2748 | rt->playpath[0] = 0; |
||
2749 | } |
||
2750 | av_strlcat(rt->playpath, fname, PLAYPATH_MAX_LENGTH); |
||
2751 | } else { |
||
2752 | rt->playpath[0] = '\0'; |
||
2753 | } |
||
2754 | } |
||
2755 | |||
2756 | if (!rt->tcurl) { |
||
2757 | rt->tcurl = av_malloc(TCURL_MAX_LENGTH); |
||
2758 | if (!rt->tcurl) { |
||
2759 | ret = AVERROR(ENOMEM); |
||
2760 | goto fail; |
||
2761 | } |
||
2762 | ff_url_join(rt->tcurl, TCURL_MAX_LENGTH, proto, NULL, hostname, |
||
2763 | port, "/%s", rt->app); |
||
2764 | } |
||
2765 | |||
2766 | if (!rt->flashver) { |
||
2767 | rt->flashver = av_malloc(FLASHVER_MAX_LENGTH); |
||
2768 | if (!rt->flashver) { |
||
2769 | ret = AVERROR(ENOMEM); |
||
2770 | goto fail; |
||
2771 | } |
||
2772 | if (rt->is_input) { |
||
2773 | snprintf(rt->flashver, FLASHVER_MAX_LENGTH, "%s %d,%d,%d,%d", |
||
2774 | RTMP_CLIENT_PLATFORM, RTMP_CLIENT_VER1, RTMP_CLIENT_VER2, |
||
2775 | RTMP_CLIENT_VER3, RTMP_CLIENT_VER4); |
||
2776 | } else { |
||
2777 | snprintf(rt->flashver, FLASHVER_MAX_LENGTH, |
||
2778 | "FMLE/3.0 (compatible; %s)", LIBAVFORMAT_IDENT); |
||
2779 | } |
||
2780 | } |
||
2781 | |||
2782 | rt->client_report_size = 1048576; |
||
2783 | rt->bytes_read = 0; |
||
2784 | rt->has_audio = 0; |
||
2785 | rt->has_video = 0; |
||
2786 | rt->received_metadata = 0; |
||
2787 | rt->last_bytes_read = 0; |
||
2788 | rt->server_bw = 2500000; |
||
2789 | rt->duration = 0; |
||
2790 | |||
2791 | av_log(s, AV_LOG_DEBUG, "Proto = %s, path = %s, app = %s, fname = %s\n", |
||
2792 | proto, path, rt->app, rt->playpath); |
||
2793 | if (!rt->listen) { |
||
2794 | if ((ret = gen_connect(s, rt)) < 0) |
||
2795 | goto fail; |
||
2796 | } else { |
||
2797 | if ((ret = read_connect(s, s->priv_data)) < 0) |
||
2798 | goto fail; |
||
2799 | } |
||
2800 | |||
2801 | do { |
||
2802 | ret = get_packet(s, 1); |
||
2803 | } while (ret == AVERROR(EAGAIN)); |
||
2804 | if (ret < 0) |
||
2805 | goto fail; |
||
2806 | |||
2807 | if (rt->do_reconnect) { |
||
2808 | int i; |
||
2809 | ffurl_close(rt->stream); |
||
2810 | rt->stream = NULL; |
||
2811 | rt->do_reconnect = 0; |
||
2812 | rt->nb_invokes = 0; |
||
2813 | for (i = 0; i < 2; i++) |
||
2814 | memset(rt->prev_pkt[i], 0, |
||
2815 | sizeof(**rt->prev_pkt) * rt->nb_prev_pkt[i]); |
||
2816 | free_tracked_methods(rt); |
||
2817 | goto reconnect; |
||
2818 | } |
||
2819 | |||
2820 | if (rt->is_input) { |
||
2821 | // generate FLV header for demuxer |
||
2822 | rt->flv_size = 13; |
||
2823 | if ((ret = av_reallocp(&rt->flv_data, rt->flv_size)) < 0) |
||
2824 | goto fail; |
||
2825 | rt->flv_off = 0; |
||
2826 | memcpy(rt->flv_data, "FLV\1\0\0\0\0\011\0\0\0\0", rt->flv_size); |
||
2827 | |||
2828 | // Read packets until we reach the first A/V packet or read metadata. |
||
2829 | // If there was a metadata package in front of the A/V packets, we can |
||
2830 | // build the FLV header from this. If we do not receive any metadata, |
||
2831 | // the FLV decoder will allocate the needed streams when their first |
||
2832 | // audio or video packet arrives. |
||
2833 | while (!rt->has_audio && !rt->has_video && !rt->received_metadata) { |
||
2834 | if ((ret = get_packet(s, 0)) < 0) |
||
2835 | goto fail; |
||
2836 | } |
||
2837 | |||
2838 | // Either after we have read the metadata or (if there is none) the |
||
2839 | // first packet of an A/V stream, we have a better knowledge about the |
||
2840 | // streams, so set the FLV header accordingly. |
||
2841 | if (rt->has_audio) { |
||
2842 | rt->flv_data[4] |= FLV_HEADER_FLAG_HASAUDIO; |
||
2843 | } |
||
2844 | if (rt->has_video) { |
||
2845 | rt->flv_data[4] |= FLV_HEADER_FLAG_HASVIDEO; |
||
2846 | } |
||
2847 | |||
2848 | // If we received the first packet of an A/V stream and no metadata but |
||
2849 | // the server returned a valid duration, create a fake metadata packet |
||
2850 | // to inform the FLV decoder about the duration. |
||
2851 | if (!rt->received_metadata && rt->duration > 0) { |
||
2852 | if ((ret = inject_fake_duration_metadata(rt)) < 0) |
||
2853 | goto fail; |
||
2854 | } |
||
2855 | } else { |
||
2856 | rt->flv_size = 0; |
||
2857 | rt->flv_data = NULL; |
||
2858 | rt->flv_off = 0; |
||
2859 | rt->skip_bytes = 13; |
||
2860 | } |
||
2861 | |||
2862 | s->max_packet_size = rt->stream->max_packet_size; |
||
2863 | s->is_streamed = 1; |
||
2864 | return 0; |
||
2865 | |||
2866 | fail: |
||
2867 | av_dict_free(&opts); |
||
2868 | rtmp_close(s); |
||
2869 | return ret; |
||
2870 | } |
||
2871 | |||
2872 | static int rtmp_read(URLContext *s, uint8_t *buf, int size) |
||
2873 | { |
||
2874 | RTMPContext *rt = s->priv_data; |
||
2875 | int orig_size = size; |
||
2876 | int ret; |
||
2877 | |||
2878 | while (size > 0) { |
||
2879 | int data_left = rt->flv_size - rt->flv_off; |
||
2880 | |||
2881 | if (data_left >= size) { |
||
2882 | memcpy(buf, rt->flv_data + rt->flv_off, size); |
||
2883 | rt->flv_off += size; |
||
2884 | return orig_size; |
||
2885 | } |
||
2886 | if (data_left > 0) { |
||
2887 | memcpy(buf, rt->flv_data + rt->flv_off, data_left); |
||
2888 | buf += data_left; |
||
2889 | size -= data_left; |
||
2890 | rt->flv_off = rt->flv_size; |
||
2891 | return data_left; |
||
2892 | } |
||
2893 | if ((ret = get_packet(s, 0)) < 0) |
||
2894 | return ret; |
||
2895 | } |
||
2896 | return orig_size; |
||
2897 | } |
||
2898 | |||
2899 | static int64_t rtmp_seek(URLContext *s, int stream_index, int64_t timestamp, |
||
2900 | int flags) |
||
2901 | { |
||
2902 | RTMPContext *rt = s->priv_data; |
||
2903 | int ret; |
||
2904 | av_log(s, AV_LOG_DEBUG, |
||
2905 | "Seek on stream index %d at timestamp %"PRId64" with flags %08x\n", |
||
2906 | stream_index, timestamp, flags); |
||
2907 | if ((ret = gen_seek(s, rt, timestamp)) < 0) { |
||
2908 | av_log(s, AV_LOG_ERROR, |
||
2909 | "Unable to send seek command on stream index %d at timestamp " |
||
2910 | "%"PRId64" with flags %08x\n", |
||
2911 | stream_index, timestamp, flags); |
||
2912 | return ret; |
||
2913 | } |
||
2914 | rt->flv_off = rt->flv_size; |
||
2915 | rt->state = STATE_SEEKING; |
||
2916 | return timestamp; |
||
2917 | } |
||
2918 | |||
2919 | static int rtmp_pause(URLContext *s, int pause) |
||
2920 | { |
||
2921 | RTMPContext *rt = s->priv_data; |
||
2922 | int ret; |
||
2923 | av_log(s, AV_LOG_DEBUG, "Pause at timestamp %d\n", |
||
2924 | rt->last_timestamp); |
||
2925 | if ((ret = gen_pause(s, rt, pause, rt->last_timestamp)) < 0) { |
||
2926 | av_log(s, AV_LOG_ERROR, "Unable to send pause command at timestamp %d\n", |
||
2927 | rt->last_timestamp); |
||
2928 | return ret; |
||
2929 | } |
||
2930 | return 0; |
||
2931 | } |
||
2932 | |||
2933 | static int rtmp_write(URLContext *s, const uint8_t *buf, int size) |
||
2934 | { |
||
2935 | RTMPContext *rt = s->priv_data; |
||
2936 | int size_temp = size; |
||
2937 | int pktsize, pkttype, copy; |
||
2938 | uint32_t ts; |
||
2939 | const uint8_t *buf_temp = buf; |
||
2940 | uint8_t c; |
||
2941 | int ret; |
||
2942 | |||
2943 | do { |
||
2944 | if (rt->skip_bytes) { |
||
2945 | int skip = FFMIN(rt->skip_bytes, size_temp); |
||
2946 | buf_temp += skip; |
||
2947 | size_temp -= skip; |
||
2948 | rt->skip_bytes -= skip; |
||
2949 | continue; |
||
2950 | } |
||
2951 | |||
2952 | if (rt->flv_header_bytes < RTMP_HEADER) { |
||
2953 | const uint8_t *header = rt->flv_header; |
||
2954 | int channel = RTMP_AUDIO_CHANNEL; |
||
2955 | |||
2956 | copy = FFMIN(RTMP_HEADER - rt->flv_header_bytes, size_temp); |
||
2957 | bytestream_get_buffer(&buf_temp, rt->flv_header + rt->flv_header_bytes, copy); |
||
2958 | rt->flv_header_bytes += copy; |
||
2959 | size_temp -= copy; |
||
2960 | if (rt->flv_header_bytes < RTMP_HEADER) |
||
2961 | break; |
||
2962 | |||
2963 | pkttype = bytestream_get_byte(&header); |
||
2964 | pktsize = bytestream_get_be24(&header); |
||
2965 | ts = bytestream_get_be24(&header); |
||
2966 | ts |= bytestream_get_byte(&header) << 24; |
||
2967 | bytestream_get_be24(&header); |
||
2968 | rt->flv_size = pktsize; |
||
2969 | |||
2970 | if (pkttype == RTMP_PT_VIDEO) |
||
2971 | channel = RTMP_VIDEO_CHANNEL; |
||
2972 | |||
2973 | if (((pkttype == RTMP_PT_VIDEO || pkttype == RTMP_PT_AUDIO) && ts == 0) || |
||
2974 | pkttype == RTMP_PT_NOTIFY) { |
||
2975 | if ((ret = ff_rtmp_check_alloc_array(&rt->prev_pkt[1], |
||
2976 | &rt->nb_prev_pkt[1], |
||
2977 | channel)) < 0) |
||
2978 | return ret; |
||
2979 | // Force sending a full 12 bytes header by clearing the |
||
2980 | // channel id, to make it not match a potential earlier |
||
2981 | // packet in the same channel. |
||
2982 | rt->prev_pkt[1][channel].channel_id = 0; |
||
2983 | } |
||
2984 | |||
2985 | //this can be a big packet, it's better to send it right here |
||
2986 | if ((ret = ff_rtmp_packet_create(&rt->out_pkt, channel, |
||
2987 | pkttype, ts, pktsize)) < 0) |
||
2988 | return ret; |
||
2989 | |||
2990 | rt->out_pkt.extra = rt->stream_id; |
||
2991 | rt->flv_data = rt->out_pkt.data; |
||
2992 | } |
||
2993 | |||
2994 | copy = FFMIN(rt->flv_size - rt->flv_off, size_temp); |
||
2995 | bytestream_get_buffer(&buf_temp, rt->flv_data + rt->flv_off, copy); |
||
2996 | rt->flv_off += copy; |
||
2997 | size_temp -= copy; |
||
2998 | |||
2999 | if (rt->flv_off == rt->flv_size) { |
||
3000 | rt->skip_bytes = 4; |
||
3001 | |||
3002 | if (rt->out_pkt.type == RTMP_PT_NOTIFY) { |
||
3003 | // For onMetaData and |RtmpSampleAccess packets, we want |
||
3004 | // @setDataFrame prepended to the packet before it gets sent. |
||
3005 | // However, not all RTMP_PT_NOTIFY packets (e.g., onTextData |
||
3006 | // and onCuePoint). |
||
3007 | uint8_t commandbuffer[64]; |
||
3008 | int stringlen = 0; |
||
3009 | GetByteContext gbc; |
||
3010 | |||
3011 | bytestream2_init(&gbc, rt->flv_data, rt->flv_size); |
||
3012 | if (!ff_amf_read_string(&gbc, commandbuffer, sizeof(commandbuffer), |
||
3013 | &stringlen)) { |
||
3014 | if (!strcmp(commandbuffer, "onMetaData") || |
||
3015 | !strcmp(commandbuffer, "|RtmpSampleAccess")) { |
||
3016 | uint8_t *ptr; |
||
3017 | if ((ret = av_reallocp(&rt->out_pkt.data, rt->out_pkt.size + 16)) < 0) { |
||
3018 | rt->flv_size = rt->flv_off = rt->flv_header_bytes = 0; |
||
3019 | return ret; |
||
3020 | } |
||
3021 | memmove(rt->out_pkt.data + 16, rt->out_pkt.data, rt->out_pkt.size); |
||
3022 | rt->out_pkt.size += 16; |
||
3023 | ptr = rt->out_pkt.data; |
||
3024 | ff_amf_write_string(&ptr, "@setDataFrame"); |
||
3025 | } |
||
3026 | } |
||
3027 | } |
||
3028 | |||
3029 | if ((ret = rtmp_send_packet(rt, &rt->out_pkt, 0)) < 0) |
||
3030 | return ret; |
||
3031 | rt->flv_size = 0; |
||
3032 | rt->flv_off = 0; |
||
3033 | rt->flv_header_bytes = 0; |
||
3034 | rt->flv_nb_packets++; |
||
3035 | } |
||
3036 | } while (buf_temp - buf < size); |
||
3037 | |||
3038 | if (rt->flv_nb_packets < rt->flush_interval) |
||
3039 | return size; |
||
3040 | rt->flv_nb_packets = 0; |
||
3041 | |||
3042 | /* set stream into nonblocking mode */ |
||
3043 | rt->stream->flags |= AVIO_FLAG_NONBLOCK; |
||
3044 | |||
3045 | /* try to read one byte from the stream */ |
||
3046 | ret = ffurl_read(rt->stream, &c, 1); |
||
3047 | |||
3048 | /* switch the stream back into blocking mode */ |
||
3049 | rt->stream->flags &= ~AVIO_FLAG_NONBLOCK; |
||
3050 | |||
3051 | if (ret == AVERROR(EAGAIN)) { |
||
3052 | /* no incoming data to handle */ |
||
3053 | return size; |
||
3054 | } else if (ret < 0) { |
||
3055 | return ret; |
||
3056 | } else if (ret == 1) { |
||
3057 | RTMPPacket rpkt = { 0 }; |
||
3058 | |||
3059 | if ((ret = ff_rtmp_packet_read_internal(rt->stream, &rpkt, |
||
3060 | rt->in_chunk_size, |
||
3061 | &rt->prev_pkt[0], |
||
3062 | &rt->nb_prev_pkt[0], c)) <= 0) |
||
3063 | return ret; |
||
3064 | |||
3065 | if ((ret = rtmp_parse_result(s, rt, &rpkt)) < 0) |
||
3066 | return ret; |
||
3067 | |||
3068 | ff_rtmp_packet_destroy(&rpkt); |
||
3069 | } |
||
3070 | |||
3071 | return size; |
||
3072 | } |
||
3073 | |||
3074 | #define OFFSET(x) offsetof(RTMPContext, x) |
||
3075 | #define DEC AV_OPT_FLAG_DECODING_PARAM |
||
3076 | #define ENC AV_OPT_FLAG_ENCODING_PARAM |
||
3077 | |||
3078 | static const AVOption rtmp_options[] = { |
||
3079 | {"rtmp_app", "Name of application to connect to on the RTMP server", OFFSET(app), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, |
||
3080 | {"rtmp_buffer", "Set buffer time in milliseconds. The default is 3000.", OFFSET(client_buffer_time), AV_OPT_TYPE_INT, {.i64 = 3000}, 0, INT_MAX, DEC|ENC}, |
||
3081 | {"rtmp_conn", "Append arbitrary AMF data to the Connect message", OFFSET(conn), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, |
||
3082 | {"rtmp_flashver", "Version of the Flash plugin used to run the SWF player.", OFFSET(flashver), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, |
||
3083 | {"rtmp_flush_interval", "Number of packets flushed in the same request (RTMPT only).", OFFSET(flush_interval), AV_OPT_TYPE_INT, {.i64 = 10}, 0, INT_MAX, ENC}, |
||
3084 | {"rtmp_live", "Specify that the media is a live stream.", OFFSET(live), AV_OPT_TYPE_INT, {.i64 = -2}, INT_MIN, INT_MAX, DEC, "rtmp_live"}, |
||
3085 | {"any", "both", 0, AV_OPT_TYPE_CONST, {.i64 = -2}, 0, 0, DEC, "rtmp_live"}, |
||
3086 | {"live", "live stream", 0, AV_OPT_TYPE_CONST, {.i64 = -1}, 0, 0, DEC, "rtmp_live"}, |
||
3087 | {"recorded", "recorded stream", 0, AV_OPT_TYPE_CONST, {.i64 = 0}, 0, 0, DEC, "rtmp_live"}, |
||
3088 | {"rtmp_pageurl", "URL of the web page in which the media was embedded. By default no value will be sent.", OFFSET(pageurl), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC}, |
||
3089 | {"rtmp_playpath", "Stream identifier to play or to publish", OFFSET(playpath), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, |
||
3090 | {"rtmp_subscribe", "Name of live stream to subscribe to. Defaults to rtmp_playpath.", OFFSET(subscribe), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC}, |
||
3091 | {"rtmp_swfhash", "SHA256 hash of the decompressed SWF file (32 bytes).", OFFSET(swfhash), AV_OPT_TYPE_BINARY, .flags = DEC}, |
||
3092 | {"rtmp_swfsize", "Size of the decompressed SWF file, required for SWFVerification.", OFFSET(swfsize), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, DEC}, |
||
3093 | {"rtmp_swfurl", "URL of the SWF player. By default no value will be sent", OFFSET(swfurl), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, |
||
3094 | {"rtmp_swfverify", "URL to player swf file, compute hash/size automatically.", OFFSET(swfverify), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC}, |
||
3095 | {"rtmp_tcurl", "URL of the target stream. Defaults to proto://host[:port]/app.", OFFSET(tcurl), AV_OPT_TYPE_STRING, {.str = NULL }, 0, 0, DEC|ENC}, |
||
3096 | {"rtmp_listen", "Listen for incoming rtmp connections", OFFSET(listen), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, DEC, "rtmp_listen" }, |
||
3097 | {"listen", "Listen for incoming rtmp connections", OFFSET(listen), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, DEC, "rtmp_listen" }, |
||
3098 | {"timeout", "Maximum timeout (in seconds) to wait for incoming connections. -1 is infinite. Implies -rtmp_listen 1", OFFSET(listen_timeout), AV_OPT_TYPE_INT, {.i64 = -1}, INT_MIN, INT_MAX, DEC, "rtmp_listen" }, |
||
3099 | { NULL }, |
||
3100 | }; |
||
3101 | |||
3102 | #define RTMP_PROTOCOL(flavor) \ |
||
3103 | static const AVClass flavor##_class = { \ |
||
3104 | .class_name = #flavor, \ |
||
3105 | .item_name = av_default_item_name, \ |
||
3106 | .option = rtmp_options, \ |
||
3107 | .version = LIBAVUTIL_VERSION_INT, \ |
||
3108 | }; \ |
||
3109 | \ |
||
3110 | URLProtocol ff_##flavor##_protocol = { \ |
||
3111 | .name = #flavor, \ |
||
3112 | .url_open = rtmp_open, \ |
||
3113 | .url_read = rtmp_read, \ |
||
3114 | .url_read_seek = rtmp_seek, \ |
||
3115 | .url_read_pause = rtmp_pause, \ |
||
3116 | .url_write = rtmp_write, \ |
||
3117 | .url_close = rtmp_close, \ |
||
3118 | .priv_data_size = sizeof(RTMPContext), \ |
||
3119 | .flags = URL_PROTOCOL_FLAG_NETWORK, \ |
||
3120 | .priv_data_class= &flavor##_class, \ |
||
3121 | }; |
||
3122 | |||
3123 | |||
3124 | RTMP_PROTOCOL(rtmp) |
||
3125 | RTMP_PROTOCOL(rtmpe) |
||
3126 | RTMP_PROTOCOL(rtmps) |
||
3127 | RTMP_PROTOCOL(rtmpt) |
||
3128 | RTMP_PROTOCOL(rtmpte) |
||
3129 | RTMP_PROTOCOL(rtmpts)>=>>>>>>>>><>>>>>>>>>>>>>>>>>>>>>>>>=>><>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>=>>=>>>>>=>>>>=>=>=>>>>>>>>>>>>>>>>>>>>=>>>>>>>>>>>=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> |