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