Mercurial > libavformat.hg
annotate rtmpproto.c @ 5339:942ca859a587 libavformat
Set the sample format for Smacker audio in the decoder rather than in the
demuxer.
author | jbr |
---|---|
date | Sun, 01 Nov 2009 15:08:49 +0000 |
parents | 08ec48911f20 |
children | f0711d97bff4 |
rev | line source |
---|---|
5123 | 1 /* |
2 * RTMP network protocol | |
3 * Copyright (c) 2009 Kostya 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 libavformat/rtmpproto.c | |
24 * RTMP protocol | |
25 */ | |
26 | |
27 #include "libavcodec/bytestream.h" | |
28 #include "libavutil/avstring.h" | |
29 #include "libavutil/lfg.h" | |
30 #include "libavutil/sha.h" | |
31 #include "avformat.h" | |
32 | |
33 #include "network.h" | |
34 | |
35 #include "flv.h" | |
36 #include "rtmp.h" | |
37 #include "rtmppkt.h" | |
38 | |
39 /* we can't use av_log() with URLContext yet... */ | |
40 #if LIBAVFORMAT_VERSION_MAJOR < 53 | |
41 #define LOG_CONTEXT NULL | |
42 #else | |
43 #define LOG_CONTEXT s | |
44 #endif | |
45 | |
46 /** RTMP protocol handler state */ | |
47 typedef enum { | |
48 STATE_START, ///< client has not done anything yet | |
49 STATE_HANDSHAKED, ///< client has performed handshake | |
50 STATE_CONNECTING, ///< client connected to server successfully | |
51 STATE_READY, ///< client has sent all needed commands and waits for server reply | |
52 STATE_PLAYING, ///< client has started receiving multimedia data from server | |
53 } ClientState; | |
54 | |
55 /** protocol handler context */ | |
56 typedef struct RTMPContext { | |
57 URLContext* stream; ///< TCP stream used in interactions with RTMP server | |
58 RTMPPacket prev_pkt[2][RTMP_CHANNELS]; ///< packet history used when reading and sending packets | |
59 int chunk_size; ///< size of the chunks RTMP packets are divided into | |
60 char playpath[256]; ///< path to filename to play (with possible "mp4:" prefix) | |
61 ClientState state; ///< current state | |
62 int main_channel_id; ///< an additional channel ID which is used for some invocations | |
63 uint8_t* flv_data; ///< buffer with data for demuxer | |
64 int flv_size; ///< current buffer size | |
65 int flv_off; ///< number of bytes read from current buffer | |
66 uint32_t video_ts; ///< current video timestamp in milliseconds | |
67 uint32_t audio_ts; ///< current audio timestamp in milliseconds | |
68 } RTMPContext; | |
69 | |
70 #define PLAYER_KEY_OPEN_PART_LEN 30 ///< length of partial key used for first client digest signing | |
71 /** Client key used for digest signing */ | |
72 static const uint8_t rtmp_player_key[] = { | |
73 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', | |
74 'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ', '0', '0', '1', | |
75 | |
76 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, 0x02, | |
77 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, 0x93, 0xB8, | |
78 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE | |
79 }; | |
80 | |
81 #define SERVER_KEY_OPEN_PART_LEN 36 ///< length of partial key used for first server digest signing | |
82 /** Key used for RTMP server digest signing */ | |
83 static const uint8_t rtmp_server_key[] = { | |
84 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', | |
85 'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ', | |
86 'S', 'e', 'r', 'v', 'e', 'r', ' ', '0', '0', '1', | |
87 | |
88 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, 0x02, | |
89 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, 0x93, 0xB8, | |
90 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE | |
91 }; | |
92 | |
93 /** | |
94 * Generates 'connect' call and sends it to the server. | |
95 */ | |
96 static void gen_connect(URLContext *s, RTMPContext *rt, const char *proto, | |
97 const char *host, int port, const char *app) | |
98 { | |
99 RTMPPacket pkt; | |
100 uint8_t ver[32], *p; | |
101 char tcurl[512]; | |
102 | |
103 ff_rtmp_packet_create(&pkt, RTMP_VIDEO_CHANNEL, RTMP_PT_INVOKE, 0, 4096); | |
104 p = pkt.data; | |
105 | |
106 snprintf(tcurl, sizeof(tcurl), "%s://%s:%d/%s", proto, host, port, app); | |
107 ff_amf_write_string(&p, "connect"); | |
108 ff_amf_write_number(&p, 1.0); | |
109 ff_amf_write_object_start(&p); | |
110 ff_amf_write_field_name(&p, "app"); | |
111 ff_amf_write_string(&p, app); | |
112 | |
113 snprintf(ver, sizeof(ver), "%s %d,%d,%d,%d", RTMP_CLIENT_PLATFORM, RTMP_CLIENT_VER1, | |
114 RTMP_CLIENT_VER2, RTMP_CLIENT_VER3, RTMP_CLIENT_VER4); | |
115 ff_amf_write_field_name(&p, "flashVer"); | |
116 ff_amf_write_string(&p, ver); | |
117 ff_amf_write_field_name(&p, "tcUrl"); | |
118 ff_amf_write_string(&p, tcurl); | |
119 ff_amf_write_field_name(&p, "fpad"); | |
120 ff_amf_write_bool(&p, 0); | |
121 ff_amf_write_field_name(&p, "capabilities"); | |
122 ff_amf_write_number(&p, 15.0); | |
123 ff_amf_write_field_name(&p, "audioCodecs"); | |
124 ff_amf_write_number(&p, 1639.0); | |
125 ff_amf_write_field_name(&p, "videoCodecs"); | |
126 ff_amf_write_number(&p, 252.0); | |
127 ff_amf_write_field_name(&p, "videoFunction"); | |
128 ff_amf_write_number(&p, 1.0); | |
129 ff_amf_write_object_end(&p); | |
130 | |
131 pkt.data_size = p - pkt.data; | |
132 | |
133 ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, rt->prev_pkt[1]); | |
134 } | |
135 | |
136 /** | |
137 * Generates 'createStream' call and sends it to the server. It should make | |
138 * the server allocate some channel for media streams. | |
139 */ | |
140 static void gen_create_stream(URLContext *s, RTMPContext *rt) | |
141 { | |
142 RTMPPacket pkt; | |
143 uint8_t *p; | |
144 | |
145 av_log(LOG_CONTEXT, AV_LOG_DEBUG, "Creating stream...\n"); | |
146 ff_rtmp_packet_create(&pkt, RTMP_VIDEO_CHANNEL, RTMP_PT_INVOKE, 0, 25); | |
147 | |
148 p = pkt.data; | |
149 ff_amf_write_string(&p, "createStream"); | |
150 ff_amf_write_number(&p, 3.0); | |
151 ff_amf_write_null(&p); | |
152 | |
153 ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, rt->prev_pkt[1]); | |
154 ff_rtmp_packet_destroy(&pkt); | |
155 } | |
156 | |
157 /** | |
158 * Generates 'play' call and sends it to the server, then pings the server | |
159 * to start actual playing. | |
160 */ | |
161 static void gen_play(URLContext *s, RTMPContext *rt) | |
162 { | |
163 RTMPPacket pkt; | |
164 uint8_t *p; | |
165 | |
166 av_log(LOG_CONTEXT, AV_LOG_DEBUG, "Sending play command for '%s'\n", rt->playpath); | |
167 ff_rtmp_packet_create(&pkt, RTMP_VIDEO_CHANNEL, RTMP_PT_INVOKE, 0, | |
5295
08ec48911f20
Last parameter in RTMP "play" call was optional and some servers seem not to
kostya
parents:
5214
diff
changeset
|
168 20 + strlen(rt->playpath)); |
5123 | 169 pkt.extra = rt->main_channel_id; |
170 | |
171 p = pkt.data; | |
172 ff_amf_write_string(&p, "play"); | |
173 ff_amf_write_number(&p, 0.0); | |
174 ff_amf_write_null(&p); | |
175 ff_amf_write_string(&p, rt->playpath); | |
176 | |
177 ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, rt->prev_pkt[1]); | |
178 ff_rtmp_packet_destroy(&pkt); | |
179 | |
180 // set client buffer time disguised in ping packet | |
181 ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING, 1, 10); | |
182 | |
183 p = pkt.data; | |
184 bytestream_put_be16(&p, 3); | |
185 bytestream_put_be32(&p, 1); | |
186 bytestream_put_be32(&p, 256); //TODO: what is a good value here? | |
187 | |
188 ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, rt->prev_pkt[1]); | |
189 ff_rtmp_packet_destroy(&pkt); | |
190 } | |
191 | |
192 /** | |
193 * Generates ping reply and sends it to the server. | |
194 */ | |
195 static void gen_pong(URLContext *s, RTMPContext *rt, RTMPPacket *ppkt) | |
196 { | |
197 RTMPPacket pkt; | |
198 uint8_t *p; | |
199 | |
200 ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING, ppkt->timestamp + 1, 6); | |
201 p = pkt.data; | |
202 bytestream_put_be16(&p, 7); | |
203 bytestream_put_be32(&p, AV_RB32(ppkt->data+2) + 1); | |
204 ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, rt->prev_pkt[1]); | |
205 ff_rtmp_packet_destroy(&pkt); | |
206 } | |
207 | |
208 //TODO: Move HMAC code somewhere. Eventually. | |
209 #define HMAC_IPAD_VAL 0x36 | |
210 #define HMAC_OPAD_VAL 0x5C | |
211 | |
212 /** | |
213 * Calculates HMAC-SHA2 digest for RTMP handshake packets. | |
214 * | |
215 * @param src input buffer | |
216 * @param len input buffer length (should be 1536) | |
217 * @param gap offset in buffer where 32 bytes should not be taken into account | |
218 * when calculating digest (since it will be used to store that digest) | |
219 * @param key digest key | |
220 * @param keylen digest key length | |
221 * @param dst buffer where calculated digest will be stored (32 bytes) | |
222 */ | |
223 static void rtmp_calc_digest(const uint8_t *src, int len, int gap, | |
224 const uint8_t *key, int keylen, uint8_t *dst) | |
225 { | |
226 struct AVSHA *sha; | |
227 uint8_t hmac_buf[64+32] = {0}; | |
228 int i; | |
229 | |
230 sha = av_mallocz(av_sha_size); | |
231 | |
232 if (keylen < 64) { | |
233 memcpy(hmac_buf, key, keylen); | |
234 } else { | |
235 av_sha_init(sha, 256); | |
236 av_sha_update(sha,key, keylen); | |
237 av_sha_final(sha, hmac_buf); | |
238 } | |
239 for (i = 0; i < 64; i++) | |
240 hmac_buf[i] ^= HMAC_IPAD_VAL; | |
241 | |
242 av_sha_init(sha, 256); | |
243 av_sha_update(sha, hmac_buf, 64); | |
244 if (gap <= 0) { | |
245 av_sha_update(sha, src, len); | |
246 } else { //skip 32 bytes used for storing digest | |
247 av_sha_update(sha, src, gap); | |
248 av_sha_update(sha, src + gap + 32, len - gap - 32); | |
249 } | |
250 av_sha_final(sha, hmac_buf + 64); | |
251 | |
252 for (i = 0; i < 64; i++) | |
253 hmac_buf[i] ^= HMAC_IPAD_VAL ^ HMAC_OPAD_VAL; //reuse XORed key for opad | |
254 av_sha_init(sha, 256); | |
255 av_sha_update(sha, hmac_buf, 64+32); | |
256 av_sha_final(sha, dst); | |
257 | |
258 av_free(sha); | |
259 } | |
260 | |
261 /** | |
262 * Puts HMAC-SHA2 digest of packet data (except for the bytes where this digest | |
263 * will be stored) into that packet. | |
264 * | |
265 * @param buf handshake data (1536 bytes) | |
266 * @return offset to the digest inside input data | |
267 */ | |
268 static int rtmp_handshake_imprint_with_digest(uint8_t *buf) | |
269 { | |
270 int i, digest_pos = 0; | |
271 | |
272 for (i = 8; i < 12; i++) | |
273 digest_pos += buf[i]; | |
274 digest_pos = (digest_pos % 728) + 12; | |
275 | |
276 rtmp_calc_digest(buf, RTMP_HANDSHAKE_PACKET_SIZE, digest_pos, | |
277 rtmp_player_key, PLAYER_KEY_OPEN_PART_LEN, | |
278 buf + digest_pos); | |
279 return digest_pos; | |
280 } | |
281 | |
282 /** | |
283 * Verifies that the received server response has the expected digest value. | |
284 * | |
285 * @param buf handshake data received from the server (1536 bytes) | |
286 * @param off position to search digest offset from | |
287 * @return 0 if digest is valid, digest position otherwise | |
288 */ | |
289 static int rtmp_validate_digest(uint8_t *buf, int off) | |
290 { | |
291 int i, digest_pos = 0; | |
292 uint8_t digest[32]; | |
293 | |
294 for (i = 0; i < 4; i++) | |
295 digest_pos += buf[i + off]; | |
296 digest_pos = (digest_pos % 728) + off + 4; | |
297 | |
298 rtmp_calc_digest(buf, RTMP_HANDSHAKE_PACKET_SIZE, digest_pos, | |
299 rtmp_server_key, SERVER_KEY_OPEN_PART_LEN, | |
300 digest); | |
301 if (!memcmp(digest, buf + digest_pos, 32)) | |
302 return digest_pos; | |
303 return 0; | |
304 } | |
305 | |
306 /** | |
307 * Performs handshake with the server by means of exchanging pseudorandom data | |
308 * signed with HMAC-SHA2 digest. | |
309 * | |
310 * @return 0 if handshake succeeds, negative value otherwise | |
311 */ | |
312 static int rtmp_handshake(URLContext *s, RTMPContext *rt) | |
313 { | |
314 AVLFG rnd; | |
315 uint8_t tosend [RTMP_HANDSHAKE_PACKET_SIZE+1] = { | |
316 3, // unencrypted data | |
317 0, 0, 0, 0, // client uptime | |
318 RTMP_CLIENT_VER1, | |
319 RTMP_CLIENT_VER2, | |
320 RTMP_CLIENT_VER3, | |
321 RTMP_CLIENT_VER4, | |
322 }; | |
323 uint8_t clientdata[RTMP_HANDSHAKE_PACKET_SIZE]; | |
324 uint8_t serverdata[RTMP_HANDSHAKE_PACKET_SIZE+1]; | |
325 int i; | |
326 int server_pos, client_pos; | |
327 uint8_t digest[32]; | |
328 | |
329 av_log(LOG_CONTEXT, AV_LOG_DEBUG, "Handshaking...\n"); | |
330 | |
331 av_lfg_init(&rnd, 0xDEADC0DE); | |
332 // generate handshake packet - 1536 bytes of pseudorandom data | |
333 for (i = 9; i <= RTMP_HANDSHAKE_PACKET_SIZE; i++) | |
334 tosend[i] = av_lfg_get(&rnd) >> 24; | |
335 client_pos = rtmp_handshake_imprint_with_digest(tosend + 1); | |
336 | |
337 url_write(rt->stream, tosend, RTMP_HANDSHAKE_PACKET_SIZE + 1); | |
338 i = url_read_complete(rt->stream, serverdata, RTMP_HANDSHAKE_PACKET_SIZE + 1); | |
339 if (i != RTMP_HANDSHAKE_PACKET_SIZE + 1) { | |
340 av_log(LOG_CONTEXT, AV_LOG_ERROR, "Cannot read RTMP handshake response\n"); | |
341 return -1; | |
342 } | |
343 i = url_read_complete(rt->stream, clientdata, RTMP_HANDSHAKE_PACKET_SIZE); | |
344 if (i != RTMP_HANDSHAKE_PACKET_SIZE) { | |
345 av_log(LOG_CONTEXT, AV_LOG_ERROR, "Cannot read RTMP handshake response\n"); | |
346 return -1; | |
347 } | |
348 | |
349 av_log(LOG_CONTEXT, AV_LOG_DEBUG, "Server version %d.%d.%d.%d\n", | |
350 serverdata[5], serverdata[6], serverdata[7], serverdata[8]); | |
351 | |
352 server_pos = rtmp_validate_digest(serverdata + 1, 772); | |
353 if (!server_pos) { | |
354 server_pos = rtmp_validate_digest(serverdata + 1, 8); | |
355 if (!server_pos) { | |
356 av_log(LOG_CONTEXT, AV_LOG_ERROR, "Server response validating failed\n"); | |
357 return -1; | |
358 } | |
359 } | |
360 | |
361 rtmp_calc_digest(tosend + 1 + client_pos, 32, 0, | |
362 rtmp_server_key, sizeof(rtmp_server_key), | |
363 digest); | |
364 rtmp_calc_digest(clientdata, RTMP_HANDSHAKE_PACKET_SIZE-32, 0, | |
365 digest, 32, | |
366 digest); | |
367 if (memcmp(digest, clientdata + RTMP_HANDSHAKE_PACKET_SIZE - 32, 32)) { | |
368 av_log(LOG_CONTEXT, AV_LOG_ERROR, "Signature mismatch\n"); | |
369 return -1; | |
370 } | |
371 | |
372 for (i = 0; i < RTMP_HANDSHAKE_PACKET_SIZE; i++) | |
373 tosend[i] = av_lfg_get(&rnd) >> 24; | |
374 rtmp_calc_digest(serverdata + 1 + server_pos, 32, 0, | |
375 rtmp_player_key, sizeof(rtmp_player_key), | |
376 digest); | |
377 rtmp_calc_digest(tosend, RTMP_HANDSHAKE_PACKET_SIZE - 32, 0, | |
378 digest, 32, | |
379 tosend + RTMP_HANDSHAKE_PACKET_SIZE - 32); | |
380 | |
381 // write reply back to the server | |
382 url_write(rt->stream, tosend, RTMP_HANDSHAKE_PACKET_SIZE); | |
383 return 0; | |
384 } | |
385 | |
386 /** | |
387 * Parses received packet and may perform some action depending on | |
388 * the packet contents. | |
389 * @return 0 for no errors, negative values for serious errors which prevent | |
390 * further communications, positive values for uncritical errors | |
391 */ | |
392 static int rtmp_parse_result(URLContext *s, RTMPContext *rt, RTMPPacket *pkt) | |
393 { | |
394 int i, t; | |
395 const uint8_t *data_end = pkt->data + pkt->data_size; | |
396 | |
397 switch (pkt->type) { | |
398 case RTMP_PT_CHUNK_SIZE: | |
399 if (pkt->data_size != 4) { | |
400 av_log(LOG_CONTEXT, AV_LOG_ERROR, | |
401 "Chunk size change packet is not 4 bytes long (%d)\n", pkt->data_size); | |
402 return -1; | |
403 } | |
404 rt->chunk_size = AV_RB32(pkt->data); | |
405 if (rt->chunk_size <= 0) { | |
406 av_log(LOG_CONTEXT, AV_LOG_ERROR, "Incorrect chunk size %d\n", rt->chunk_size); | |
407 return -1; | |
408 } | |
409 av_log(LOG_CONTEXT, AV_LOG_DEBUG, "New chunk size = %d\n", rt->chunk_size); | |
410 break; | |
411 case RTMP_PT_PING: | |
412 t = AV_RB16(pkt->data); | |
413 if (t == 6) | |
414 gen_pong(s, rt, pkt); | |
415 break; | |
416 case RTMP_PT_INVOKE: | |
417 //TODO: check for the messages sent for wrong state? | |
418 if (!memcmp(pkt->data, "\002\000\006_error", 9)) { | |
419 uint8_t tmpstr[256]; | |
420 | |
421 if (!ff_amf_get_field_value(pkt->data + 9, data_end, | |
422 "description", tmpstr, sizeof(tmpstr))) | |
423 av_log(LOG_CONTEXT, AV_LOG_ERROR, "Server error: %s\n",tmpstr); | |
424 return -1; | |
425 } else if (!memcmp(pkt->data, "\002\000\007_result", 10)) { | |
426 switch (rt->state) { | |
427 case STATE_HANDSHAKED: | |
428 gen_create_stream(s, rt); | |
429 rt->state = STATE_CONNECTING; | |
430 break; | |
431 case STATE_CONNECTING: | |
432 //extract a number from the result | |
433 if (pkt->data[10] || pkt->data[19] != 5 || pkt->data[20]) { | |
434 av_log(LOG_CONTEXT, AV_LOG_WARNING, "Unexpected reply on connect()\n"); | |
435 } else { | |
436 rt->main_channel_id = (int) av_int2dbl(AV_RB64(pkt->data + 21)); | |
437 } | |
438 gen_play(s, rt); | |
439 rt->state = STATE_READY; | |
440 break; | |
441 } | |
442 } else if (!memcmp(pkt->data, "\002\000\010onStatus", 11)) { | |
443 const uint8_t* ptr = pkt->data + 11; | |
444 uint8_t tmpstr[256]; | |
445 int t; | |
446 | |
447 for (i = 0; i < 2; i++) { | |
448 t = ff_amf_tag_size(ptr, data_end); | |
449 if (t < 0) | |
450 return 1; | |
451 ptr += t; | |
452 } | |
453 t = ff_amf_get_field_value(ptr, data_end, | |
454 "level", tmpstr, sizeof(tmpstr)); | |
455 if (!t && !strcmp(tmpstr, "error")) { | |
456 if (!ff_amf_get_field_value(ptr, data_end, | |
457 "description", tmpstr, sizeof(tmpstr))) | |
458 av_log(LOG_CONTEXT, AV_LOG_ERROR, "Server error: %s\n",tmpstr); | |
459 return -1; | |
460 } | |
461 t = ff_amf_get_field_value(ptr, data_end, | |
462 "code", tmpstr, sizeof(tmpstr)); | |
463 if (!t && !strcmp(tmpstr, "NetStream.Play.Start")) { | |
464 rt->state = STATE_PLAYING; | |
465 return 0; | |
466 } | |
467 } | |
468 break; | |
469 } | |
470 return 0; | |
471 } | |
472 | |
473 /** | |
474 * Interacts with the server by receiving and sending RTMP packets until | |
475 * there is some significant data (media data or expected status notification). | |
476 * | |
477 * @param s reading context | |
478 * @param for_header non-zero value tells function to work until it gets notification from the server that playing has been started, otherwise function will work until some media data is received (or an error happens) | |
479 * @return 0 for successful operation, negative value in case of error | |
480 */ | |
481 static int get_packet(URLContext *s, int for_header) | |
482 { | |
483 RTMPContext *rt = s->priv_data; | |
484 int ret; | |
485 | |
486 for(;;) { | |
487 RTMPPacket rpkt; | |
488 if ((ret = ff_rtmp_packet_read(rt->stream, &rpkt, | |
489 rt->chunk_size, rt->prev_pkt[0])) != 0) { | |
490 if (ret > 0) { | |
491 return AVERROR(EAGAIN); | |
492 } else { | |
493 return AVERROR(EIO); | |
494 } | |
495 } | |
496 | |
497 ret = rtmp_parse_result(s, rt, &rpkt); | |
498 if (ret < 0) {//serious error in current packet | |
499 ff_rtmp_packet_destroy(&rpkt); | |
500 return -1; | |
501 } | |
502 if (for_header && rt->state == STATE_PLAYING) { | |
503 ff_rtmp_packet_destroy(&rpkt); | |
504 return 0; | |
505 } | |
506 if (!rpkt.data_size) { | |
507 ff_rtmp_packet_destroy(&rpkt); | |
508 continue; | |
509 } | |
510 if (rpkt.type == RTMP_PT_VIDEO || rpkt.type == RTMP_PT_AUDIO || | |
511 rpkt.type == RTMP_PT_NOTIFY) { | |
512 uint8_t *p; | |
513 uint32_t ts = rpkt.timestamp; | |
514 | |
515 if (rpkt.type == RTMP_PT_VIDEO) { | |
516 rt->video_ts += rpkt.timestamp; | |
517 ts = rt->video_ts; | |
518 } else if (rpkt.type == RTMP_PT_AUDIO) { | |
519 rt->audio_ts += rpkt.timestamp; | |
520 ts = rt->audio_ts; | |
521 } | |
522 // generate packet header and put data into buffer for FLV demuxer | |
523 rt->flv_off = 0; | |
524 rt->flv_size = rpkt.data_size + 15; | |
525 rt->flv_data = p = av_realloc(rt->flv_data, rt->flv_size); | |
526 bytestream_put_byte(&p, rpkt.type); | |
527 bytestream_put_be24(&p, rpkt.data_size); | |
528 bytestream_put_be24(&p, ts); | |
529 bytestream_put_byte(&p, ts >> 24); | |
530 bytestream_put_be24(&p, 0); | |
531 bytestream_put_buffer(&p, rpkt.data, rpkt.data_size); | |
532 bytestream_put_be32(&p, 0); | |
533 ff_rtmp_packet_destroy(&rpkt); | |
534 return 0; | |
535 } else if (rpkt.type == RTMP_PT_METADATA) { | |
536 // we got raw FLV data, make it available for FLV demuxer | |
537 rt->flv_off = 0; | |
538 rt->flv_size = rpkt.data_size; | |
539 rt->flv_data = av_realloc(rt->flv_data, rt->flv_size); | |
540 memcpy(rt->flv_data, rpkt.data, rpkt.data_size); | |
541 ff_rtmp_packet_destroy(&rpkt); | |
542 return 0; | |
543 } | |
544 ff_rtmp_packet_destroy(&rpkt); | |
545 } | |
546 return 0; | |
547 } | |
548 | |
549 static int rtmp_close(URLContext *h) | |
550 { | |
551 RTMPContext *rt = h->priv_data; | |
552 | |
553 av_freep(&rt->flv_data); | |
554 url_close(rt->stream); | |
555 av_free(rt); | |
556 return 0; | |
557 } | |
558 | |
559 /** | |
560 * Opens RTMP connection and verifies that the stream can be played. | |
561 * | |
562 * URL syntax: rtmp://server[:port][/app][/playpath] | |
563 * where 'app' is first one or two directories in the path | |
564 * (e.g. /ondemand/, /flash/live/, etc.) | |
565 * and 'playpath' is a file name (the rest of the path, | |
566 * may be prefixed with "mp4:") | |
567 */ | |
568 static int rtmp_open(URLContext *s, const char *uri, int flags) | |
569 { | |
570 RTMPContext *rt; | |
571 char proto[8], hostname[256], path[1024], app[128], *fname; | |
572 uint8_t buf[2048]; | |
573 int port, is_input; | |
574 int ret; | |
575 | |
576 is_input = !(flags & URL_WRONLY); | |
577 | |
578 rt = av_mallocz(sizeof(RTMPContext)); | |
579 if (!rt) | |
580 return AVERROR(ENOMEM); | |
581 s->priv_data = rt; | |
582 | |
583 url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname), &port, | |
584 path, sizeof(path), s->filename); | |
585 | |
586 if (port < 0) | |
587 port = RTMP_DEFAULT_PORT; | |
588 snprintf(buf, sizeof(buf), "tcp://%s:%d", hostname, port); | |
589 | |
590 if (url_open(&rt->stream, buf, URL_RDWR) < 0) | |
591 goto fail; | |
592 | |
593 if (!is_input) { | |
594 av_log(LOG_CONTEXT, AV_LOG_ERROR, "RTMP output is not supported yet.\n"); | |
595 goto fail; | |
596 } else { | |
597 rt->state = STATE_START; | |
598 if (rtmp_handshake(s, rt)) | |
599 return -1; | |
600 | |
601 rt->chunk_size = 128; | |
602 rt->state = STATE_HANDSHAKED; | |
603 //extract "app" part from path | |
604 if (!strncmp(path, "/ondemand/", 10)) { | |
605 fname = path + 10; | |
606 memcpy(app, "ondemand", 9); | |
607 } else { | |
608 char *p = strchr(path + 1, '/'); | |
609 if (!p) { | |
610 fname = path + 1; | |
611 app[0] = '\0'; | |
612 } else { | |
5214
dd04eacd063b
Do not include "mp4:" prefix from RTMP URL into "app" path or second time
kostya
parents:
5123
diff
changeset
|
613 char *c = strchr(p + 1, ':'); |
5123 | 614 fname = strchr(p + 1, '/'); |
5214
dd04eacd063b
Do not include "mp4:" prefix from RTMP URL into "app" path or second time
kostya
parents:
5123
diff
changeset
|
615 if (!fname || c < fname) { |
5123 | 616 fname = p + 1; |
617 av_strlcpy(app, path + 1, p - path); | |
618 } else { | |
619 fname++; | |
620 av_strlcpy(app, path + 1, fname - path - 1); | |
621 } | |
622 } | |
623 } | |
5214
dd04eacd063b
Do not include "mp4:" prefix from RTMP URL into "app" path or second time
kostya
parents:
5123
diff
changeset
|
624 if (!strchr(fname, ':') && |
dd04eacd063b
Do not include "mp4:" prefix from RTMP URL into "app" path or second time
kostya
parents:
5123
diff
changeset
|
625 (!strcmp(fname + strlen(fname) - 4, ".f4v") || |
dd04eacd063b
Do not include "mp4:" prefix from RTMP URL into "app" path or second time
kostya
parents:
5123
diff
changeset
|
626 !strcmp(fname + strlen(fname) - 4, ".mp4"))) { |
5123 | 627 memcpy(rt->playpath, "mp4:", 5); |
628 } else { | |
629 rt->playpath[0] = 0; | |
630 } | |
631 strncat(rt->playpath, fname, sizeof(rt->playpath) - 5); | |
632 | |
633 av_log(LOG_CONTEXT, AV_LOG_DEBUG, "Proto = %s, path = %s, app = %s, fname = %s\n", | |
634 proto, path, app, rt->playpath); | |
635 gen_connect(s, rt, proto, hostname, port, app); | |
636 | |
637 do { | |
638 ret = get_packet(s, 1); | |
639 } while (ret == EAGAIN); | |
640 if (ret < 0) | |
641 goto fail; | |
642 // generate FLV header for demuxer | |
643 rt->flv_size = 13; | |
644 rt->flv_data = av_realloc(rt->flv_data, rt->flv_size); | |
645 rt->flv_off = 0; | |
646 memcpy(rt->flv_data, "FLV\1\5\0\0\0\011\0\0\0\0", rt->flv_size); | |
647 } | |
648 | |
649 s->max_packet_size = url_get_max_packet_size(rt->stream); | |
650 s->is_streamed = 1; | |
651 return 0; | |
652 | |
653 fail: | |
654 rtmp_close(s); | |
655 return AVERROR(EIO); | |
656 } | |
657 | |
658 static int rtmp_read(URLContext *s, uint8_t *buf, int size) | |
659 { | |
660 RTMPContext *rt = s->priv_data; | |
661 int orig_size = size; | |
662 int ret; | |
663 | |
664 while (size > 0) { | |
665 int data_left = rt->flv_size - rt->flv_off; | |
666 | |
667 if (data_left >= size) { | |
668 memcpy(buf, rt->flv_data + rt->flv_off, size); | |
669 rt->flv_off += size; | |
670 return orig_size; | |
671 } | |
672 if (data_left > 0) { | |
673 memcpy(buf, rt->flv_data + rt->flv_off, data_left); | |
674 buf += data_left; | |
675 size -= data_left; | |
676 rt->flv_off = rt->flv_size; | |
677 } | |
678 if ((ret = get_packet(s, 0)) < 0) | |
679 return ret; | |
680 } | |
681 return orig_size; | |
682 } | |
683 | |
684 static int rtmp_write(URLContext *h, uint8_t *buf, int size) | |
685 { | |
686 return 0; | |
687 } | |
688 | |
689 URLProtocol rtmp_protocol = { | |
690 "rtmp", | |
691 rtmp_open, | |
692 rtmp_read, | |
693 rtmp_write, | |
694 NULL, /* seek */ | |
695 rtmp_close, | |
696 }; |