Mercurial > libavformat.hg
comparison rtmpproto.c @ 5123:cc34279f0fab libavformat
RTMP protocol support (as a client)
author | kostya |
---|---|
date | Fri, 31 Jul 2009 06:49:36 +0000 |
parents | |
children | dd04eacd063b |
comparison
equal
deleted
inserted
replaced
5122:6ea4f08de89a | 5123:cc34279f0fab |
---|---|
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, | |
168 29 + strlen(rt->playpath)); | |
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 ff_amf_write_number(&p, 0.0); | |
177 | |
178 ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, rt->prev_pkt[1]); | |
179 ff_rtmp_packet_destroy(&pkt); | |
180 | |
181 // set client buffer time disguised in ping packet | |
182 ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING, 1, 10); | |
183 | |
184 p = pkt.data; | |
185 bytestream_put_be16(&p, 3); | |
186 bytestream_put_be32(&p, 1); | |
187 bytestream_put_be32(&p, 256); //TODO: what is a good value here? | |
188 | |
189 ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, rt->prev_pkt[1]); | |
190 ff_rtmp_packet_destroy(&pkt); | |
191 } | |
192 | |
193 /** | |
194 * Generates ping reply and sends it to the server. | |
195 */ | |
196 static void gen_pong(URLContext *s, RTMPContext *rt, RTMPPacket *ppkt) | |
197 { | |
198 RTMPPacket pkt; | |
199 uint8_t *p; | |
200 | |
201 ff_rtmp_packet_create(&pkt, RTMP_NETWORK_CHANNEL, RTMP_PT_PING, ppkt->timestamp + 1, 6); | |
202 p = pkt.data; | |
203 bytestream_put_be16(&p, 7); | |
204 bytestream_put_be32(&p, AV_RB32(ppkt->data+2) + 1); | |
205 ff_rtmp_packet_write(rt->stream, &pkt, rt->chunk_size, rt->prev_pkt[1]); | |
206 ff_rtmp_packet_destroy(&pkt); | |
207 } | |
208 | |
209 //TODO: Move HMAC code somewhere. Eventually. | |
210 #define HMAC_IPAD_VAL 0x36 | |
211 #define HMAC_OPAD_VAL 0x5C | |
212 | |
213 /** | |
214 * Calculates HMAC-SHA2 digest for RTMP handshake packets. | |
215 * | |
216 * @param src input buffer | |
217 * @param len input buffer length (should be 1536) | |
218 * @param gap offset in buffer where 32 bytes should not be taken into account | |
219 * when calculating digest (since it will be used to store that digest) | |
220 * @param key digest key | |
221 * @param keylen digest key length | |
222 * @param dst buffer where calculated digest will be stored (32 bytes) | |
223 */ | |
224 static void rtmp_calc_digest(const uint8_t *src, int len, int gap, | |
225 const uint8_t *key, int keylen, uint8_t *dst) | |
226 { | |
227 struct AVSHA *sha; | |
228 uint8_t hmac_buf[64+32] = {0}; | |
229 int i; | |
230 | |
231 sha = av_mallocz(av_sha_size); | |
232 | |
233 if (keylen < 64) { | |
234 memcpy(hmac_buf, key, keylen); | |
235 } else { | |
236 av_sha_init(sha, 256); | |
237 av_sha_update(sha,key, keylen); | |
238 av_sha_final(sha, hmac_buf); | |
239 } | |
240 for (i = 0; i < 64; i++) | |
241 hmac_buf[i] ^= HMAC_IPAD_VAL; | |
242 | |
243 av_sha_init(sha, 256); | |
244 av_sha_update(sha, hmac_buf, 64); | |
245 if (gap <= 0) { | |
246 av_sha_update(sha, src, len); | |
247 } else { //skip 32 bytes used for storing digest | |
248 av_sha_update(sha, src, gap); | |
249 av_sha_update(sha, src + gap + 32, len - gap - 32); | |
250 } | |
251 av_sha_final(sha, hmac_buf + 64); | |
252 | |
253 for (i = 0; i < 64; i++) | |
254 hmac_buf[i] ^= HMAC_IPAD_VAL ^ HMAC_OPAD_VAL; //reuse XORed key for opad | |
255 av_sha_init(sha, 256); | |
256 av_sha_update(sha, hmac_buf, 64+32); | |
257 av_sha_final(sha, dst); | |
258 | |
259 av_free(sha); | |
260 } | |
261 | |
262 /** | |
263 * Puts HMAC-SHA2 digest of packet data (except for the bytes where this digest | |
264 * will be stored) into that packet. | |
265 * | |
266 * @param buf handshake data (1536 bytes) | |
267 * @return offset to the digest inside input data | |
268 */ | |
269 static int rtmp_handshake_imprint_with_digest(uint8_t *buf) | |
270 { | |
271 int i, digest_pos = 0; | |
272 | |
273 for (i = 8; i < 12; i++) | |
274 digest_pos += buf[i]; | |
275 digest_pos = (digest_pos % 728) + 12; | |
276 | |
277 rtmp_calc_digest(buf, RTMP_HANDSHAKE_PACKET_SIZE, digest_pos, | |
278 rtmp_player_key, PLAYER_KEY_OPEN_PART_LEN, | |
279 buf + digest_pos); | |
280 return digest_pos; | |
281 } | |
282 | |
283 /** | |
284 * Verifies that the received server response has the expected digest value. | |
285 * | |
286 * @param buf handshake data received from the server (1536 bytes) | |
287 * @param off position to search digest offset from | |
288 * @return 0 if digest is valid, digest position otherwise | |
289 */ | |
290 static int rtmp_validate_digest(uint8_t *buf, int off) | |
291 { | |
292 int i, digest_pos = 0; | |
293 uint8_t digest[32]; | |
294 | |
295 for (i = 0; i < 4; i++) | |
296 digest_pos += buf[i + off]; | |
297 digest_pos = (digest_pos % 728) + off + 4; | |
298 | |
299 rtmp_calc_digest(buf, RTMP_HANDSHAKE_PACKET_SIZE, digest_pos, | |
300 rtmp_server_key, SERVER_KEY_OPEN_PART_LEN, | |
301 digest); | |
302 if (!memcmp(digest, buf + digest_pos, 32)) | |
303 return digest_pos; | |
304 return 0; | |
305 } | |
306 | |
307 /** | |
308 * Performs handshake with the server by means of exchanging pseudorandom data | |
309 * signed with HMAC-SHA2 digest. | |
310 * | |
311 * @return 0 if handshake succeeds, negative value otherwise | |
312 */ | |
313 static int rtmp_handshake(URLContext *s, RTMPContext *rt) | |
314 { | |
315 AVLFG rnd; | |
316 uint8_t tosend [RTMP_HANDSHAKE_PACKET_SIZE+1] = { | |
317 3, // unencrypted data | |
318 0, 0, 0, 0, // client uptime | |
319 RTMP_CLIENT_VER1, | |
320 RTMP_CLIENT_VER2, | |
321 RTMP_CLIENT_VER3, | |
322 RTMP_CLIENT_VER4, | |
323 }; | |
324 uint8_t clientdata[RTMP_HANDSHAKE_PACKET_SIZE]; | |
325 uint8_t serverdata[RTMP_HANDSHAKE_PACKET_SIZE+1]; | |
326 int i; | |
327 int server_pos, client_pos; | |
328 uint8_t digest[32]; | |
329 | |
330 av_log(LOG_CONTEXT, AV_LOG_DEBUG, "Handshaking...\n"); | |
331 | |
332 av_lfg_init(&rnd, 0xDEADC0DE); | |
333 // generate handshake packet - 1536 bytes of pseudorandom data | |
334 for (i = 9; i <= RTMP_HANDSHAKE_PACKET_SIZE; i++) | |
335 tosend[i] = av_lfg_get(&rnd) >> 24; | |
336 client_pos = rtmp_handshake_imprint_with_digest(tosend + 1); | |
337 | |
338 url_write(rt->stream, tosend, RTMP_HANDSHAKE_PACKET_SIZE + 1); | |
339 i = url_read_complete(rt->stream, serverdata, RTMP_HANDSHAKE_PACKET_SIZE + 1); | |
340 if (i != RTMP_HANDSHAKE_PACKET_SIZE + 1) { | |
341 av_log(LOG_CONTEXT, AV_LOG_ERROR, "Cannot read RTMP handshake response\n"); | |
342 return -1; | |
343 } | |
344 i = url_read_complete(rt->stream, clientdata, RTMP_HANDSHAKE_PACKET_SIZE); | |
345 if (i != RTMP_HANDSHAKE_PACKET_SIZE) { | |
346 av_log(LOG_CONTEXT, AV_LOG_ERROR, "Cannot read RTMP handshake response\n"); | |
347 return -1; | |
348 } | |
349 | |
350 av_log(LOG_CONTEXT, AV_LOG_DEBUG, "Server version %d.%d.%d.%d\n", | |
351 serverdata[5], serverdata[6], serverdata[7], serverdata[8]); | |
352 | |
353 server_pos = rtmp_validate_digest(serverdata + 1, 772); | |
354 if (!server_pos) { | |
355 server_pos = rtmp_validate_digest(serverdata + 1, 8); | |
356 if (!server_pos) { | |
357 av_log(LOG_CONTEXT, AV_LOG_ERROR, "Server response validating failed\n"); | |
358 return -1; | |
359 } | |
360 } | |
361 | |
362 rtmp_calc_digest(tosend + 1 + client_pos, 32, 0, | |
363 rtmp_server_key, sizeof(rtmp_server_key), | |
364 digest); | |
365 rtmp_calc_digest(clientdata, RTMP_HANDSHAKE_PACKET_SIZE-32, 0, | |
366 digest, 32, | |
367 digest); | |
368 if (memcmp(digest, clientdata + RTMP_HANDSHAKE_PACKET_SIZE - 32, 32)) { | |
369 av_log(LOG_CONTEXT, AV_LOG_ERROR, "Signature mismatch\n"); | |
370 return -1; | |
371 } | |
372 | |
373 for (i = 0; i < RTMP_HANDSHAKE_PACKET_SIZE; i++) | |
374 tosend[i] = av_lfg_get(&rnd) >> 24; | |
375 rtmp_calc_digest(serverdata + 1 + server_pos, 32, 0, | |
376 rtmp_player_key, sizeof(rtmp_player_key), | |
377 digest); | |
378 rtmp_calc_digest(tosend, RTMP_HANDSHAKE_PACKET_SIZE - 32, 0, | |
379 digest, 32, | |
380 tosend + RTMP_HANDSHAKE_PACKET_SIZE - 32); | |
381 | |
382 // write reply back to the server | |
383 url_write(rt->stream, tosend, RTMP_HANDSHAKE_PACKET_SIZE); | |
384 return 0; | |
385 } | |
386 | |
387 /** | |
388 * Parses received packet and may perform some action depending on | |
389 * the packet contents. | |
390 * @return 0 for no errors, negative values for serious errors which prevent | |
391 * further communications, positive values for uncritical errors | |
392 */ | |
393 static int rtmp_parse_result(URLContext *s, RTMPContext *rt, RTMPPacket *pkt) | |
394 { | |
395 int i, t; | |
396 const uint8_t *data_end = pkt->data + pkt->data_size; | |
397 | |
398 switch (pkt->type) { | |
399 case RTMP_PT_CHUNK_SIZE: | |
400 if (pkt->data_size != 4) { | |
401 av_log(LOG_CONTEXT, AV_LOG_ERROR, | |
402 "Chunk size change packet is not 4 bytes long (%d)\n", pkt->data_size); | |
403 return -1; | |
404 } | |
405 rt->chunk_size = AV_RB32(pkt->data); | |
406 if (rt->chunk_size <= 0) { | |
407 av_log(LOG_CONTEXT, AV_LOG_ERROR, "Incorrect chunk size %d\n", rt->chunk_size); | |
408 return -1; | |
409 } | |
410 av_log(LOG_CONTEXT, AV_LOG_DEBUG, "New chunk size = %d\n", rt->chunk_size); | |
411 break; | |
412 case RTMP_PT_PING: | |
413 t = AV_RB16(pkt->data); | |
414 if (t == 6) | |
415 gen_pong(s, rt, pkt); | |
416 break; | |
417 case RTMP_PT_INVOKE: | |
418 //TODO: check for the messages sent for wrong state? | |
419 if (!memcmp(pkt->data, "\002\000\006_error", 9)) { | |
420 uint8_t tmpstr[256]; | |
421 | |
422 if (!ff_amf_get_field_value(pkt->data + 9, data_end, | |
423 "description", tmpstr, sizeof(tmpstr))) | |
424 av_log(LOG_CONTEXT, AV_LOG_ERROR, "Server error: %s\n",tmpstr); | |
425 return -1; | |
426 } else if (!memcmp(pkt->data, "\002\000\007_result", 10)) { | |
427 switch (rt->state) { | |
428 case STATE_HANDSHAKED: | |
429 gen_create_stream(s, rt); | |
430 rt->state = STATE_CONNECTING; | |
431 break; | |
432 case STATE_CONNECTING: | |
433 //extract a number from the result | |
434 if (pkt->data[10] || pkt->data[19] != 5 || pkt->data[20]) { | |
435 av_log(LOG_CONTEXT, AV_LOG_WARNING, "Unexpected reply on connect()\n"); | |
436 } else { | |
437 rt->main_channel_id = (int) av_int2dbl(AV_RB64(pkt->data + 21)); | |
438 } | |
439 gen_play(s, rt); | |
440 rt->state = STATE_READY; | |
441 break; | |
442 } | |
443 } else if (!memcmp(pkt->data, "\002\000\010onStatus", 11)) { | |
444 const uint8_t* ptr = pkt->data + 11; | |
445 uint8_t tmpstr[256]; | |
446 int t; | |
447 | |
448 for (i = 0; i < 2; i++) { | |
449 t = ff_amf_tag_size(ptr, data_end); | |
450 if (t < 0) | |
451 return 1; | |
452 ptr += t; | |
453 } | |
454 t = ff_amf_get_field_value(ptr, data_end, | |
455 "level", tmpstr, sizeof(tmpstr)); | |
456 if (!t && !strcmp(tmpstr, "error")) { | |
457 if (!ff_amf_get_field_value(ptr, data_end, | |
458 "description", tmpstr, sizeof(tmpstr))) | |
459 av_log(LOG_CONTEXT, AV_LOG_ERROR, "Server error: %s\n",tmpstr); | |
460 return -1; | |
461 } | |
462 t = ff_amf_get_field_value(ptr, data_end, | |
463 "code", tmpstr, sizeof(tmpstr)); | |
464 if (!t && !strcmp(tmpstr, "NetStream.Play.Start")) { | |
465 rt->state = STATE_PLAYING; | |
466 return 0; | |
467 } | |
468 } | |
469 break; | |
470 } | |
471 return 0; | |
472 } | |
473 | |
474 /** | |
475 * Interacts with the server by receiving and sending RTMP packets until | |
476 * there is some significant data (media data or expected status notification). | |
477 * | |
478 * @param s reading context | |
479 * @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) | |
480 * @return 0 for successful operation, negative value in case of error | |
481 */ | |
482 static int get_packet(URLContext *s, int for_header) | |
483 { | |
484 RTMPContext *rt = s->priv_data; | |
485 int ret; | |
486 | |
487 for(;;) { | |
488 RTMPPacket rpkt; | |
489 if ((ret = ff_rtmp_packet_read(rt->stream, &rpkt, | |
490 rt->chunk_size, rt->prev_pkt[0])) != 0) { | |
491 if (ret > 0) { | |
492 return AVERROR(EAGAIN); | |
493 } else { | |
494 return AVERROR(EIO); | |
495 } | |
496 } | |
497 | |
498 ret = rtmp_parse_result(s, rt, &rpkt); | |
499 if (ret < 0) {//serious error in current packet | |
500 ff_rtmp_packet_destroy(&rpkt); | |
501 return -1; | |
502 } | |
503 if (for_header && rt->state == STATE_PLAYING) { | |
504 ff_rtmp_packet_destroy(&rpkt); | |
505 return 0; | |
506 } | |
507 if (!rpkt.data_size) { | |
508 ff_rtmp_packet_destroy(&rpkt); | |
509 continue; | |
510 } | |
511 if (rpkt.type == RTMP_PT_VIDEO || rpkt.type == RTMP_PT_AUDIO || | |
512 rpkt.type == RTMP_PT_NOTIFY) { | |
513 uint8_t *p; | |
514 uint32_t ts = rpkt.timestamp; | |
515 | |
516 if (rpkt.type == RTMP_PT_VIDEO) { | |
517 rt->video_ts += rpkt.timestamp; | |
518 ts = rt->video_ts; | |
519 } else if (rpkt.type == RTMP_PT_AUDIO) { | |
520 rt->audio_ts += rpkt.timestamp; | |
521 ts = rt->audio_ts; | |
522 } | |
523 // generate packet header and put data into buffer for FLV demuxer | |
524 rt->flv_off = 0; | |
525 rt->flv_size = rpkt.data_size + 15; | |
526 rt->flv_data = p = av_realloc(rt->flv_data, rt->flv_size); | |
527 bytestream_put_byte(&p, rpkt.type); | |
528 bytestream_put_be24(&p, rpkt.data_size); | |
529 bytestream_put_be24(&p, ts); | |
530 bytestream_put_byte(&p, ts >> 24); | |
531 bytestream_put_be24(&p, 0); | |
532 bytestream_put_buffer(&p, rpkt.data, rpkt.data_size); | |
533 bytestream_put_be32(&p, 0); | |
534 ff_rtmp_packet_destroy(&rpkt); | |
535 return 0; | |
536 } else if (rpkt.type == RTMP_PT_METADATA) { | |
537 // we got raw FLV data, make it available for FLV demuxer | |
538 rt->flv_off = 0; | |
539 rt->flv_size = rpkt.data_size; | |
540 rt->flv_data = av_realloc(rt->flv_data, rt->flv_size); | |
541 memcpy(rt->flv_data, rpkt.data, rpkt.data_size); | |
542 ff_rtmp_packet_destroy(&rpkt); | |
543 return 0; | |
544 } | |
545 ff_rtmp_packet_destroy(&rpkt); | |
546 } | |
547 return 0; | |
548 } | |
549 | |
550 static int rtmp_close(URLContext *h) | |
551 { | |
552 RTMPContext *rt = h->priv_data; | |
553 | |
554 av_freep(&rt->flv_data); | |
555 url_close(rt->stream); | |
556 av_free(rt); | |
557 return 0; | |
558 } | |
559 | |
560 /** | |
561 * Opens RTMP connection and verifies that the stream can be played. | |
562 * | |
563 * URL syntax: rtmp://server[:port][/app][/playpath] | |
564 * where 'app' is first one or two directories in the path | |
565 * (e.g. /ondemand/, /flash/live/, etc.) | |
566 * and 'playpath' is a file name (the rest of the path, | |
567 * may be prefixed with "mp4:") | |
568 */ | |
569 static int rtmp_open(URLContext *s, const char *uri, int flags) | |
570 { | |
571 RTMPContext *rt; | |
572 char proto[8], hostname[256], path[1024], app[128], *fname; | |
573 uint8_t buf[2048]; | |
574 int port, is_input; | |
575 int ret; | |
576 | |
577 is_input = !(flags & URL_WRONLY); | |
578 | |
579 rt = av_mallocz(sizeof(RTMPContext)); | |
580 if (!rt) | |
581 return AVERROR(ENOMEM); | |
582 s->priv_data = rt; | |
583 | |
584 url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname), &port, | |
585 path, sizeof(path), s->filename); | |
586 | |
587 if (port < 0) | |
588 port = RTMP_DEFAULT_PORT; | |
589 snprintf(buf, sizeof(buf), "tcp://%s:%d", hostname, port); | |
590 | |
591 if (url_open(&rt->stream, buf, URL_RDWR) < 0) | |
592 goto fail; | |
593 | |
594 if (!is_input) { | |
595 av_log(LOG_CONTEXT, AV_LOG_ERROR, "RTMP output is not supported yet.\n"); | |
596 goto fail; | |
597 } else { | |
598 rt->state = STATE_START; | |
599 if (rtmp_handshake(s, rt)) | |
600 return -1; | |
601 | |
602 rt->chunk_size = 128; | |
603 rt->state = STATE_HANDSHAKED; | |
604 //extract "app" part from path | |
605 if (!strncmp(path, "/ondemand/", 10)) { | |
606 fname = path + 10; | |
607 memcpy(app, "ondemand", 9); | |
608 } else { | |
609 char *p = strchr(path + 1, '/'); | |
610 if (!p) { | |
611 fname = path + 1; | |
612 app[0] = '\0'; | |
613 } else { | |
614 fname = strchr(p + 1, '/'); | |
615 if (!fname) { | |
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 } | |
624 if (!strcmp(fname + strlen(fname) - 4, ".f4v") || | |
625 !strcmp(fname + strlen(fname) - 4, ".mp4")) { | |
626 memcpy(rt->playpath, "mp4:", 5); | |
627 } else { | |
628 rt->playpath[0] = 0; | |
629 } | |
630 strncat(rt->playpath, fname, sizeof(rt->playpath) - 5); | |
631 | |
632 av_log(LOG_CONTEXT, AV_LOG_DEBUG, "Proto = %s, path = %s, app = %s, fname = %s\n", | |
633 proto, path, app, rt->playpath); | |
634 gen_connect(s, rt, proto, hostname, port, app); | |
635 | |
636 do { | |
637 ret = get_packet(s, 1); | |
638 } while (ret == EAGAIN); | |
639 if (ret < 0) | |
640 goto fail; | |
641 // generate FLV header for demuxer | |
642 rt->flv_size = 13; | |
643 rt->flv_data = av_realloc(rt->flv_data, rt->flv_size); | |
644 rt->flv_off = 0; | |
645 memcpy(rt->flv_data, "FLV\1\5\0\0\0\011\0\0\0\0", rt->flv_size); | |
646 } | |
647 | |
648 s->max_packet_size = url_get_max_packet_size(rt->stream); | |
649 s->is_streamed = 1; | |
650 return 0; | |
651 | |
652 fail: | |
653 rtmp_close(s); | |
654 return AVERROR(EIO); | |
655 } | |
656 | |
657 static int rtmp_read(URLContext *s, uint8_t *buf, int size) | |
658 { | |
659 RTMPContext *rt = s->priv_data; | |
660 int orig_size = size; | |
661 int ret; | |
662 | |
663 while (size > 0) { | |
664 int data_left = rt->flv_size - rt->flv_off; | |
665 | |
666 if (data_left >= size) { | |
667 memcpy(buf, rt->flv_data + rt->flv_off, size); | |
668 rt->flv_off += size; | |
669 return orig_size; | |
670 } | |
671 if (data_left > 0) { | |
672 memcpy(buf, rt->flv_data + rt->flv_off, data_left); | |
673 buf += data_left; | |
674 size -= data_left; | |
675 rt->flv_off = rt->flv_size; | |
676 } | |
677 if ((ret = get_packet(s, 0)) < 0) | |
678 return ret; | |
679 } | |
680 return orig_size; | |
681 } | |
682 | |
683 static int rtmp_write(URLContext *h, uint8_t *buf, int size) | |
684 { | |
685 return 0; | |
686 } | |
687 | |
688 URLProtocol rtmp_protocol = { | |
689 "rtmp", | |
690 rtmp_open, | |
691 rtmp_read, | |
692 rtmp_write, | |
693 NULL, /* seek */ | |
694 rtmp_close, | |
695 }; |