OLD | NEW |
1 /* | 1 /* |
2 * Multiple format streaming server | 2 * Multiple format streaming server |
3 * Copyright (c) 2000, 2001, 2002 Fabrice Bellard | 3 * Copyright (c) 2000, 2001, 2002 Fabrice Bellard |
4 * | 4 * |
5 * This file is part of FFmpeg. | 5 * This file is part of FFmpeg. |
6 * | 6 * |
7 * FFmpeg is free software; you can redistribute it and/or | 7 * FFmpeg is free software; you can redistribute it and/or |
8 * modify it under the terms of the GNU Lesser General Public | 8 * modify it under the terms of the GNU Lesser General Public |
9 * License as published by the Free Software Foundation; either | 9 * License as published by the Free Software Foundation; either |
10 * version 2.1 of the License, or (at your option) any later version. | 10 * version 2.1 of the License, or (at your option) any later version. |
(...skipping 10 matching lines...) Expand all Loading... |
21 | 21 |
22 #define _XOPEN_SOURCE 600 | 22 #define _XOPEN_SOURCE 600 |
23 | 23 |
24 #include "config.h" | 24 #include "config.h" |
25 #if !HAVE_CLOSESOCKET | 25 #if !HAVE_CLOSESOCKET |
26 #define closesocket close | 26 #define closesocket close |
27 #endif | 27 #endif |
28 #include <string.h> | 28 #include <string.h> |
29 #include <strings.h> | 29 #include <strings.h> |
30 #include <stdlib.h> | 30 #include <stdlib.h> |
31 /* avformat.h defines LIBAVFORMAT_BUILD, include it before all the other libav*
headers which use it */ | |
32 #include "libavformat/avformat.h" | 31 #include "libavformat/avformat.h" |
33 #include "libavformat/network.h" | 32 #include "libavformat/network.h" |
34 #include "libavformat/os_support.h" | 33 #include "libavformat/os_support.h" |
35 #include "libavformat/rtpdec.h" | 34 #include "libavformat/rtpdec.h" |
36 #include "libavformat/rtsp.h" | 35 #include "libavformat/rtsp.h" |
37 #include "libavutil/avstring.h" | 36 #include "libavutil/avstring.h" |
38 #include "libavutil/lfg.h" | 37 #include "libavutil/lfg.h" |
39 #include "libavutil/random_seed.h" | 38 #include "libavutil/random_seed.h" |
40 #include "libavutil/intreadwrite.h" | |
41 #include "libavcodec/opt.h" | 39 #include "libavcodec/opt.h" |
42 #include <stdarg.h> | 40 #include <stdarg.h> |
43 #include <unistd.h> | 41 #include <unistd.h> |
44 #include <fcntl.h> | 42 #include <fcntl.h> |
45 #include <sys/ioctl.h> | 43 #include <sys/ioctl.h> |
46 #if HAVE_POLL_H | 44 #if HAVE_POLL_H |
47 #include <poll.h> | 45 #include <poll.h> |
48 #endif | 46 #endif |
49 #include <errno.h> | 47 #include <errno.h> |
50 #include <sys/time.h> | 48 #include <sys/time.h> |
51 #undef time //needed because HAVE_AV_CONFIG_H is defined on top | |
52 #include <time.h> | 49 #include <time.h> |
53 #include <sys/wait.h> | 50 #include <sys/wait.h> |
54 #include <signal.h> | 51 #include <signal.h> |
55 #if HAVE_DLFCN_H | 52 #if HAVE_DLFCN_H |
56 #include <dlfcn.h> | 53 #include <dlfcn.h> |
57 #endif | 54 #endif |
58 | 55 |
59 #include "cmdutils.h" | 56 #include "cmdutils.h" |
60 | 57 |
61 #undef exit | |
62 | |
63 const char program_name[] = "FFserver"; | 58 const char program_name[] = "FFserver"; |
64 const int program_birth_year = 2000; | 59 const int program_birth_year = 2000; |
65 | 60 |
66 static const OptionDef options[]; | 61 static const OptionDef options[]; |
67 | 62 |
68 enum HTTPState { | 63 enum HTTPState { |
69 HTTPSTATE_WAIT_REQUEST, | 64 HTTPSTATE_WAIT_REQUEST, |
70 HTTPSTATE_SEND_HEADER, | 65 HTTPSTATE_SEND_HEADER, |
71 HTTPSTATE_SEND_DATA_HEADER, | 66 HTTPSTATE_SEND_DATA_HEADER, |
72 HTTPSTATE_SEND_DATA, /* sending TCP or UDP data */ | 67 HTTPSTATE_SEND_DATA, /* sending TCP or UDP data */ |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
117 /* context associated with one connection */ | 112 /* context associated with one connection */ |
118 typedef struct HTTPContext { | 113 typedef struct HTTPContext { |
119 enum HTTPState state; | 114 enum HTTPState state; |
120 int fd; /* socket file descriptor */ | 115 int fd; /* socket file descriptor */ |
121 struct sockaddr_in from_addr; /* origin */ | 116 struct sockaddr_in from_addr; /* origin */ |
122 struct pollfd *poll_entry; /* used when polling */ | 117 struct pollfd *poll_entry; /* used when polling */ |
123 int64_t timeout; | 118 int64_t timeout; |
124 uint8_t *buffer_ptr, *buffer_end; | 119 uint8_t *buffer_ptr, *buffer_end; |
125 int http_error; | 120 int http_error; |
126 int post; | 121 int post; |
| 122 int chunked_encoding; |
| 123 int chunk_size; /* 0 if it needs to be read */ |
127 struct HTTPContext *next; | 124 struct HTTPContext *next; |
128 int got_key_frame; /* stream 0 => 1, stream 1 => 2, stream 2=> 4 */ | 125 int got_key_frame; /* stream 0 => 1, stream 1 => 2, stream 2=> 4 */ |
129 int64_t data_count; | 126 int64_t data_count; |
130 /* feed input */ | 127 /* feed input */ |
131 int feed_fd; | 128 int feed_fd; |
132 /* input format handling */ | 129 /* input format handling */ |
133 AVFormatContext *fmt_in; | 130 AVFormatContext *fmt_in; |
134 int64_t start_time; /* In milliseconds - this wraps fairly often
*/ | 131 int64_t start_time; /* In milliseconds - this wraps fairly often
*/ |
135 int64_t first_pts; /* initial pts value */ | 132 int64_t first_pts; /* initial pts value */ |
136 int64_t cur_pts; /* current pts value from the stream in us */ | 133 int64_t cur_pts; /* current pts value from the stream in us */ |
(...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
308 | 305 |
309 static uint64_t max_bandwidth = 1000; | 306 static uint64_t max_bandwidth = 1000; |
310 static uint64_t current_bandwidth; | 307 static uint64_t current_bandwidth; |
311 | 308 |
312 static int64_t cur_time; // Making this global saves on passing it aro
und everywhere | 309 static int64_t cur_time; // Making this global saves on passing it aro
und everywhere |
313 | 310 |
314 static AVLFG random_state; | 311 static AVLFG random_state; |
315 | 312 |
316 static FILE *logfile = NULL; | 313 static FILE *logfile = NULL; |
317 | 314 |
| 315 /* FIXME: make ffserver work with IPv6 */ |
| 316 /* resolve host with also IP address parsing */ |
| 317 static int resolve_host(struct in_addr *sin_addr, const char *hostname) |
| 318 { |
| 319 |
| 320 if (!ff_inet_aton(hostname, sin_addr)) { |
| 321 #if HAVE_GETADDRINFO |
| 322 struct addrinfo *ai, *cur; |
| 323 struct addrinfo hints; |
| 324 memset(&hints, 0, sizeof(hints)); |
| 325 hints.ai_family = AF_INET; |
| 326 if (getaddrinfo(hostname, NULL, &hints, &ai)) |
| 327 return -1; |
| 328 /* getaddrinfo returns a linked list of addrinfo structs. |
| 329 * Even if we set ai_family = AF_INET above, make sure |
| 330 * that the returned one actually is of the correct type. */ |
| 331 for (cur = ai; cur; cur = cur->ai_next) { |
| 332 if (cur->ai_family == AF_INET) { |
| 333 *sin_addr = ((struct sockaddr_in *)cur->ai_addr)->sin_addr; |
| 334 freeaddrinfo(ai); |
| 335 return 0; |
| 336 } |
| 337 } |
| 338 freeaddrinfo(ai); |
| 339 return -1; |
| 340 #else |
| 341 struct hostent *hp; |
| 342 hp = gethostbyname(hostname); |
| 343 if (!hp) |
| 344 return -1; |
| 345 memcpy(sin_addr, hp->h_addr_list[0], sizeof(struct in_addr)); |
| 346 #endif |
| 347 } |
| 348 return 0; |
| 349 } |
| 350 |
318 static char *ctime1(char *buf2) | 351 static char *ctime1(char *buf2) |
319 { | 352 { |
320 time_t ti; | 353 time_t ti; |
321 char *p; | 354 char *p; |
322 | 355 |
323 ti = time(NULL); | 356 ti = time(NULL); |
324 p = ctime(&ti); | 357 p = ctime(&ti); |
325 strcpy(buf2, p); | 358 strcpy(buf2, p); |
326 p = buf2 + strlen(p) - 1; | 359 p = buf2 + strlen(p) - 1; |
327 if (*p == '\n') | 360 if (*p == '\n') |
(...skipping 2105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2433 } | 2466 } |
2434 | 2467 |
2435 c->stream->feed_write_index = FFMAX(ffm_read_write_index(fd), FFM_PACKET_SIZ
E); | 2468 c->stream->feed_write_index = FFMAX(ffm_read_write_index(fd), FFM_PACKET_SIZ
E); |
2436 c->stream->feed_size = lseek(fd, 0, SEEK_END); | 2469 c->stream->feed_size = lseek(fd, 0, SEEK_END); |
2437 lseek(fd, 0, SEEK_SET); | 2470 lseek(fd, 0, SEEK_SET); |
2438 | 2471 |
2439 /* init buffer input */ | 2472 /* init buffer input */ |
2440 c->buffer_ptr = c->buffer; | 2473 c->buffer_ptr = c->buffer; |
2441 c->buffer_end = c->buffer + FFM_PACKET_SIZE; | 2474 c->buffer_end = c->buffer + FFM_PACKET_SIZE; |
2442 c->stream->feed_opened = 1; | 2475 c->stream->feed_opened = 1; |
| 2476 c->chunked_encoding = !!av_stristr(c->buffer, "Transfer-Encoding: chunked"); |
2443 return 0; | 2477 return 0; |
2444 } | 2478 } |
2445 | 2479 |
2446 static int http_receive_data(HTTPContext *c) | 2480 static int http_receive_data(HTTPContext *c) |
2447 { | 2481 { |
2448 HTTPContext *c1; | 2482 HTTPContext *c1; |
| 2483 int len, loop_run = 0; |
| 2484 |
| 2485 while (c->chunked_encoding && !c->chunk_size && |
| 2486 c->buffer_end > c->buffer_ptr) { |
| 2487 /* read chunk header, if present */ |
| 2488 len = recv(c->fd, c->buffer_ptr, 1, 0); |
| 2489 |
| 2490 if (len < 0) { |
| 2491 if (ff_neterrno() != FF_NETERROR(EAGAIN) && |
| 2492 ff_neterrno() != FF_NETERROR(EINTR)) |
| 2493 /* error : close connection */ |
| 2494 goto fail; |
| 2495 } else if (len == 0) { |
| 2496 /* end of connection : close it */ |
| 2497 goto fail; |
| 2498 } else if (c->buffer_ptr - c->buffer >= 2 && |
| 2499 !memcmp(c->buffer_ptr - 1, "\r\n", 2)) { |
| 2500 c->chunk_size = strtol(c->buffer, 0, 16); |
| 2501 if (c->chunk_size == 0) // end of stream |
| 2502 goto fail; |
| 2503 c->buffer_ptr = c->buffer; |
| 2504 break; |
| 2505 } else if (++loop_run > 10) { |
| 2506 /* no chunk header, abort */ |
| 2507 goto fail; |
| 2508 } else { |
| 2509 c->buffer_ptr++; |
| 2510 } |
| 2511 } |
2449 | 2512 |
2450 if (c->buffer_end > c->buffer_ptr) { | 2513 if (c->buffer_end > c->buffer_ptr) { |
2451 int len; | 2514 len = recv(c->fd, c->buffer_ptr, |
2452 | 2515 FFMIN(c->chunk_size, c->buffer_end - c->buffer_ptr), 0); |
2453 len = recv(c->fd, c->buffer_ptr, c->buffer_end - c->buffer_ptr, 0); | |
2454 if (len < 0) { | 2516 if (len < 0) { |
2455 if (ff_neterrno() != FF_NETERROR(EAGAIN) && | 2517 if (ff_neterrno() != FF_NETERROR(EAGAIN) && |
2456 ff_neterrno() != FF_NETERROR(EINTR)) | 2518 ff_neterrno() != FF_NETERROR(EINTR)) |
2457 /* error : close connection */ | 2519 /* error : close connection */ |
2458 goto fail; | 2520 goto fail; |
2459 } else if (len == 0) | 2521 } else if (len == 0) |
2460 /* end of connection : close it */ | 2522 /* end of connection : close it */ |
2461 goto fail; | 2523 goto fail; |
2462 else { | 2524 else { |
| 2525 c->chunk_size -= len; |
2463 c->buffer_ptr += len; | 2526 c->buffer_ptr += len; |
2464 c->data_count += len; | 2527 c->data_count += len; |
2465 update_datarate(&c->datarate, c->data_count); | 2528 update_datarate(&c->datarate, c->data_count); |
2466 } | 2529 } |
2467 } | 2530 } |
2468 | 2531 |
2469 if (c->buffer_ptr - c->buffer >= 2 && c->data_count > FFM_PACKET_SIZE) { | 2532 if (c->buffer_ptr - c->buffer >= 2 && c->data_count > FFM_PACKET_SIZE) { |
2470 if (c->buffer[0] != 'f' || | 2533 if (c->buffer[0] != 'f' || |
2471 c->buffer[1] != 'm') { | 2534 c->buffer[1] != 'm') { |
2472 http_log("Feed stream has become desynchronized -- disconnecting\n")
; | 2535 http_log("Feed stream has become desynchronized -- disconnecting\n")
; |
(...skipping 213 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2686 if (p2 > p && p2[-1] == '\r') | 2749 if (p2 > p && p2[-1] == '\r') |
2687 p2--; | 2750 p2--; |
2688 /* skip empty line */ | 2751 /* skip empty line */ |
2689 if (p2 == p) | 2752 if (p2 == p) |
2690 break; | 2753 break; |
2691 len = p2 - p; | 2754 len = p2 - p; |
2692 if (len > sizeof(line) - 1) | 2755 if (len > sizeof(line) - 1) |
2693 len = sizeof(line) - 1; | 2756 len = sizeof(line) - 1; |
2694 memcpy(line, p, len); | 2757 memcpy(line, p, len); |
2695 line[len] = '\0'; | 2758 line[len] = '\0'; |
2696 rtsp_parse_line(header, line); | 2759 ff_rtsp_parse_line(header, line); |
2697 p = p1 + 1; | 2760 p = p1 + 1; |
2698 } | 2761 } |
2699 | 2762 |
2700 /* handle sequence number */ | 2763 /* handle sequence number */ |
2701 c->seq = header->seq; | 2764 c->seq = header->seq; |
2702 | 2765 |
2703 if (!strcmp(cmd, "DESCRIBE")) | 2766 if (!strcmp(cmd, "DESCRIBE")) |
2704 rtsp_cmd_describe(c, url); | 2767 rtsp_cmd_describe(c, url); |
2705 else if (!strcmp(cmd, "OPTIONS")) | 2768 else if (!strcmp(cmd, "OPTIONS")) |
2706 rtsp_cmd_options(c, url); | 2769 rtsp_cmd_options(c, url); |
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2771 static void rtsp_cmd_describe(HTTPContext *c, const char *url) | 2834 static void rtsp_cmd_describe(HTTPContext *c, const char *url) |
2772 { | 2835 { |
2773 FFStream *stream; | 2836 FFStream *stream; |
2774 char path1[1024]; | 2837 char path1[1024]; |
2775 const char *path; | 2838 const char *path; |
2776 uint8_t *content; | 2839 uint8_t *content; |
2777 int content_length, len; | 2840 int content_length, len; |
2778 struct sockaddr_in my_addr; | 2841 struct sockaddr_in my_addr; |
2779 | 2842 |
2780 /* find which url is asked */ | 2843 /* find which url is asked */ |
2781 url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url); | 2844 ff_url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url); |
2782 path = path1; | 2845 path = path1; |
2783 if (*path == '/') | 2846 if (*path == '/') |
2784 path++; | 2847 path++; |
2785 | 2848 |
2786 for(stream = first_stream; stream != NULL; stream = stream->next) { | 2849 for(stream = first_stream; stream != NULL; stream = stream->next) { |
2787 if (!stream->is_feed && | 2850 if (!stream->is_feed && |
2788 stream->fmt && !strcmp(stream->fmt->name, "rtp") && | 2851 stream->fmt && !strcmp(stream->fmt->name, "rtp") && |
2789 !strcmp(path, stream->filename)) { | 2852 !strcmp(path, stream->filename)) { |
2790 goto found; | 2853 goto found; |
2791 } | 2854 } |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2846 int stream_index, port; | 2909 int stream_index, port; |
2847 char buf[1024]; | 2910 char buf[1024]; |
2848 char path1[1024]; | 2911 char path1[1024]; |
2849 const char *path; | 2912 const char *path; |
2850 HTTPContext *rtp_c; | 2913 HTTPContext *rtp_c; |
2851 RTSPTransportField *th; | 2914 RTSPTransportField *th; |
2852 struct sockaddr_in dest_addr; | 2915 struct sockaddr_in dest_addr; |
2853 RTSPActionServerSetup setup; | 2916 RTSPActionServerSetup setup; |
2854 | 2917 |
2855 /* find which url is asked */ | 2918 /* find which url is asked */ |
2856 url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url); | 2919 ff_url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url); |
2857 path = path1; | 2920 path = path1; |
2858 if (*path == '/') | 2921 if (*path == '/') |
2859 path++; | 2922 path++; |
2860 | 2923 |
2861 /* now check each stream */ | 2924 /* now check each stream */ |
2862 for(stream = first_stream; stream != NULL; stream = stream->next) { | 2925 for(stream = first_stream; stream != NULL; stream = stream->next) { |
2863 if (!stream->is_feed && | 2926 if (!stream->is_feed && |
2864 stream->fmt && !strcmp(stream->fmt->name, "rtp")) { | 2927 stream->fmt && !strcmp(stream->fmt->name, "rtp")) { |
2865 /* accept aggregate filenames only if single stream */ | 2928 /* accept aggregate filenames only if single stream */ |
2866 if (!strcmp(path, stream->filename)) { | 2929 if (!strcmp(path, stream->filename)) { |
(...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2988 char path1[1024]; | 3051 char path1[1024]; |
2989 const char *path; | 3052 const char *path; |
2990 char buf[1024]; | 3053 char buf[1024]; |
2991 int s; | 3054 int s; |
2992 | 3055 |
2993 rtp_c = find_rtp_session(session_id); | 3056 rtp_c = find_rtp_session(session_id); |
2994 if (!rtp_c) | 3057 if (!rtp_c) |
2995 return NULL; | 3058 return NULL; |
2996 | 3059 |
2997 /* find which url is asked */ | 3060 /* find which url is asked */ |
2998 url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url); | 3061 ff_url_split(NULL, 0, NULL, 0, NULL, 0, NULL, path1, sizeof(path1), url); |
2999 path = path1; | 3062 path = path1; |
3000 if (*path == '/') | 3063 if (*path == '/') |
3001 path++; | 3064 path++; |
3002 if(!strcmp(path, rtp_c->stream->filename)) return rtp_c; | 3065 if(!strcmp(path, rtp_c->stream->filename)) return rtp_c; |
3003 for(s=0; s<rtp_c->stream->nb_streams; ++s) { | 3066 for(s=0; s<rtp_c->stream->nb_streams; ++s) { |
3004 snprintf(buf, sizeof(buf), "%s/streamid=%d", | 3067 snprintf(buf, sizeof(buf), "%s/streamid=%d", |
3005 rtp_c->stream->filename, s); | 3068 rtp_c->stream->filename, s); |
3006 if(!strncmp(path, buf, sizeof(buf))) { | 3069 if(!strncmp(path, buf, sizeof(buf))) { |
3007 // XXX: Should we reply with RTSP_STATUS_ONLY_AGGREGATE if nb_streams>1? | 3070 // XXX: Should we reply with RTSP_STATUS_ONLY_AGGREGATE if nb_streams>1? |
3008 return rtp_c; | 3071 return rtp_c; |
(...skipping 1207 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4216 avctx = &audio_enc; | 4279 avctx = &audio_enc; |
4217 type = AV_OPT_FLAG_AUDIO_PARAM; | 4280 type = AV_OPT_FLAG_AUDIO_PARAM; |
4218 } | 4281 } |
4219 if (ffserver_opt_default(arg, arg2, avctx, type|AV_OPT_FLAG_ENCODING
_PARAM)) { | 4282 if (ffserver_opt_default(arg, arg2, avctx, type|AV_OPT_FLAG_ENCODING
_PARAM)) { |
4220 fprintf(stderr, "AVOption error: %s %s\n", arg, arg2); | 4283 fprintf(stderr, "AVOption error: %s %s\n", arg, arg2); |
4221 errors++; | 4284 errors++; |
4222 } | 4285 } |
4223 } else if (!strcasecmp(cmd, "VideoTag")) { | 4286 } else if (!strcasecmp(cmd, "VideoTag")) { |
4224 get_arg(arg, sizeof(arg), &p); | 4287 get_arg(arg, sizeof(arg), &p); |
4225 if ((strlen(arg) == 4) && stream) | 4288 if ((strlen(arg) == 4) && stream) |
4226 video_enc.codec_tag = AV_RL32(arg); | 4289 video_enc.codec_tag = MKTAG(arg[0], arg[1], arg[2], arg[3]); |
4227 } else if (!strcasecmp(cmd, "BitExact")) { | 4290 } else if (!strcasecmp(cmd, "BitExact")) { |
4228 if (stream) | 4291 if (stream) |
4229 video_enc.flags |= CODEC_FLAG_BITEXACT; | 4292 video_enc.flags |= CODEC_FLAG_BITEXACT; |
4230 } else if (!strcasecmp(cmd, "DctFastint")) { | 4293 } else if (!strcasecmp(cmd, "DctFastint")) { |
4231 if (stream) | 4294 if (stream) |
4232 video_enc.dct_algo = FF_DCT_FASTINT; | 4295 video_enc.dct_algo = FF_DCT_FASTINT; |
4233 } else if (!strcasecmp(cmd, "IdctSimple")) { | 4296 } else if (!strcasecmp(cmd, "IdctSimple")) { |
4234 if (stream) | 4297 if (stream) |
4235 video_enc.idct_algo = FF_IDCT_SIMPLE; | 4298 video_enc.idct_algo = FF_IDCT_SIMPLE; |
4236 } else if (!strcasecmp(cmd, "Qscale")) { | 4299 } else if (!strcasecmp(cmd, "Qscale")) { |
(...skipping 337 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4574 if (ffserver_daemon) | 4637 if (ffserver_daemon) |
4575 chdir("/"); | 4638 chdir("/"); |
4576 | 4639 |
4577 if (http_server() < 0) { | 4640 if (http_server() < 0) { |
4578 http_log("Could not start server\n"); | 4641 http_log("Could not start server\n"); |
4579 exit(1); | 4642 exit(1); |
4580 } | 4643 } |
4581 | 4644 |
4582 return 0; | 4645 return 0; |
4583 } | 4646 } |
OLD | NEW |