24564
|
1 /*
|
26742
|
2 * Copyright (C) 2007 Alessandro Molina <amol.wrk@gmail.com>
|
24564
|
3 *
|
26742
|
4 * This file is part of MPlayer.
|
24564
|
5 *
|
26742
|
6 * MPlayer is free software; you can redistribute it and/or modify
|
|
7 * it under the terms of the GNU General Public License as published by
|
|
8 * the Free Software Foundation; either version 2 of the License, or
|
|
9 * (at your option) any later version.
|
24564
|
10 *
|
26742
|
11 * MPlayer is distributed in the hope that it will be useful,
|
|
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14 * GNU General Public License for more details.
|
|
15 *
|
|
16 * You should have received a copy of the GNU General Public License along
|
|
17 * with MPlayer; if not, write to the Free Software Foundation, Inc.,
|
|
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
24564
|
19 */
|
34174
|
20
|
25267
|
21 #include <stdlib.h>
|
|
22 #include <stdio.h>
|
34174
|
23
|
25267
|
24 #include "stream/stream.h"
|
32059
|
25 #include "mpcommon.h"
|
34174
|
26 #include "mp_msg.h"
|
25267
|
27 #include "demuxer.h"
|
24564
|
28 #include "stheader.h"
|
|
29 #include "nemesi/rtsp.h"
|
|
30 #include "nemesi/rtp.h"
|
24856
|
31 #include <sched.h>
|
24564
|
32
|
31282
|
33 int rtsp_transport_http = 0;
|
24564
|
34 int rtsp_transport_tcp = 0;
|
|
35 int rtsp_transport_sctp = 0;
|
25156
|
36 int rtsp_port = 0;
|
24564
|
37
|
25103
|
38 typedef struct {
|
|
39 char * mime;
|
|
40 unsigned int fourcc;
|
|
41 } MIMEto4CC;
|
|
42
|
|
43 #define NMS_MAX_FORMATS 16
|
|
44
|
|
45 MIMEto4CC supported_audio[NMS_MAX_FORMATS] = {
|
29263
|
46 {"MPA", 0x55},
|
25103
|
47 {"vorbis", mmioFOURCC('v','r','b','s')},
|
25104
|
48 {"mpeg4-generic", mmioFOURCC('M','P','4','A')},
|
25103
|
49 {NULL, 0},
|
|
50 };
|
|
51
|
|
52 MIMEto4CC supported_video[NMS_MAX_FORMATS] = {
|
|
53 {"MPV", mmioFOURCC('M','P','E','G')},
|
25116
|
54 {"theora",mmioFOURCC('t','h','e','o')},
|
25103
|
55 {"H264", mmioFOURCC('H','2','6','4')},
|
|
56 {"H263-1998", mmioFOURCC('H','2','6','3')},
|
|
57 {"MP4V-ES", mmioFOURCC('M','P','4','V')},
|
|
58 {NULL, 0},
|
|
59 };
|
|
60
|
24564
|
61 typedef enum { NEMESI_SESSION_VIDEO,
|
|
62 NEMESI_SESSION_AUDIO } Nemesi_SessionType;
|
|
63
|
|
64 typedef struct {
|
|
65 rtsp_ctrl * rtsp;
|
|
66 rtp_session * session[2];
|
|
67 rtp_frame first_pkt[2];
|
|
68 double time[2];
|
|
69 double seek;
|
|
70 } Nemesi_DemuxerStreamData;
|
|
71
|
24997
|
72
|
|
73 #define STYPE_TO_DS(demuxer, stype) \
|
|
74 ((stype) == NEMESI_SESSION_VIDEO ? (demuxer)->video : (demuxer)->audio)
|
|
75
|
|
76 #define DS_TO_STYPE(demuxer, ds) \
|
|
77 ((ds) == (demuxer)->video ? NEMESI_SESSION_VIDEO : NEMESI_SESSION_AUDIO)
|
|
78
|
|
79 #define INVERT_STYPE(stype) ((stype + 1) % 2)
|
|
80
|
25103
|
81 static unsigned int get4CC(MIMEto4CC * supported_formats, char const * format)
|
|
82 {
|
|
83 unsigned i;
|
|
84
|
|
85 for(i = 0; i < NMS_MAX_FORMATS; ++i) {
|
|
86 if (!supported_formats[i].mime)
|
|
87 return 0;
|
|
88 else if ( strcmp(supported_formats[i].mime, format) == 0 )
|
|
89 return supported_formats[i].fourcc;
|
|
90 }
|
|
91
|
|
92 return 0;
|
|
93 }
|
24997
|
94
|
|
95 static rtp_ssrc *wait_for_packets(Nemesi_DemuxerStreamData * ndsd, Nemesi_SessionType stype)
|
|
96 {
|
|
97 rtp_ssrc *ssrc = NULL;
|
|
98
|
|
99 /* Wait for prebuffering (prebuffering must be enabled in nemesi) */
|
|
100 int terminated = rtp_fill_buffers(rtsp_get_rtp_th(ndsd->rtsp));
|
|
101
|
|
102 /* Wait for the ssrc to be registered, if prebuffering is on in nemesi
|
|
103 this will just get immediatly the correct ssrc */
|
|
104 if (!terminated) {
|
|
105 while ( !(ssrc = rtp_session_get_ssrc(ndsd->session[stype], ndsd->rtsp)) )
|
|
106 sched_yield();
|
|
107 }
|
|
108
|
|
109 return ssrc;
|
|
110 }
|
|
111
|
24564
|
112 static void link_session_and_fetch_conf(Nemesi_DemuxerStreamData * ndsd,
|
|
113 Nemesi_SessionType stype,
|
|
114 rtp_session * sess,
|
|
115 rtp_buff * buff, unsigned int * fps)
|
|
116 {
|
24855
|
117 rtp_ssrc *ssrc = NULL;
|
24564
|
118 rtp_frame * fr = &ndsd->first_pkt[stype];
|
|
119 rtp_buff trash_buff;
|
25010
|
120 int must_prefetch = ((fps != NULL) || (buff != NULL)) ? 1 : 0;
|
24564
|
121
|
|
122 ndsd->session[stype] = sess;
|
|
123
|
24997
|
124 ssrc = wait_for_packets(ndsd, stype);
|
24564
|
125
|
25010
|
126 if ( ((ssrc) && (must_prefetch)) ) {
|
24997
|
127 if (buff == NULL)
|
|
128 buff = &trash_buff;
|
24856
|
129
|
|
130 rtp_fill_buffer(ssrc, fr, buff); //Prefetch the first packet
|
24564
|
131
|
24998
|
132 /* Packet prefecthing must be done anyway or we won't be
|
|
133 able to get the metadata, but fps calculation happens
|
|
134 only if the user didn't specify the FPS */
|
25010
|
135 if ( ((!force_fps) && (fps != NULL)) ) {
|
24998
|
136 while ( *fps <= 0 ) {
|
|
137 //Wait more pkts to calculate FPS and try again
|
|
138 sched_yield();
|
|
139 *fps = rtp_get_fps(ssrc);
|
|
140 }
|
|
141 }
|
24564
|
142 }
|
|
143 }
|
|
144
|
25267
|
145 static demuxer_t* demux_open_rtp(demuxer_t* demuxer)
|
24564
|
146 {
|
|
147 nms_rtsp_hints hints;
|
|
148 char * url = demuxer->stream->streaming_ctrl->url->url;
|
|
149 rtsp_ctrl * ctl;
|
|
150 RTSP_Error reply;
|
|
151 rtsp_medium * media;
|
|
152 Nemesi_DemuxerStreamData * ndsd = calloc(1, sizeof(Nemesi_DemuxerStreamData));
|
|
153
|
|
154 memset(&hints,0,sizeof(hints));
|
25156
|
155 if (rtsp_port) hints.first_rtp_port = rtsp_port;
|
24564
|
156 if (rtsp_transport_tcp) {
|
|
157 hints.pref_rtsp_proto = TCP;
|
|
158 hints.pref_rtp_proto = TCP;
|
|
159 }
|
|
160 if (rtsp_transport_sctp) {
|
|
161 hints.pref_rtsp_proto = SCTP;
|
|
162 hints.pref_rtp_proto = SCTP;
|
|
163 }
|
|
164
|
|
165 mp_msg(MSGT_DEMUX, MSGL_INFO, "Initializing libNemesi\n");
|
|
166 if ((ctl = rtsp_init(&hints)) == NULL) {
|
|
167 free(ndsd);
|
|
168 return STREAM_ERROR;
|
|
169 }
|
|
170
|
|
171 ndsd->rtsp = ctl;
|
|
172 demuxer->priv = ndsd;
|
|
173 //nms_verbosity_set(1);
|
|
174
|
|
175 mp_msg(MSGT_DEMUX, MSGL_INFO, "Opening: %s\n", url);
|
|
176 if (rtsp_open(ctl, url)) {
|
|
177 mp_msg(MSGT_DEMUX, MSGL_ERR, "rtsp_open failed.\n");
|
|
178 return demuxer;
|
|
179 }
|
|
180
|
|
181 reply = rtsp_wait(ctl);
|
|
182 if (reply.got_error) {
|
|
183 mp_msg(MSGT_DEMUX, MSGL_ERR,
|
|
184 "OPEN Error from the server: %s\n",
|
|
185 reply.message.reply_str);
|
|
186 return demuxer;
|
|
187 }
|
|
188
|
|
189 rtsp_play(ctl, 0, 0);
|
|
190 reply = rtsp_wait(ctl);
|
|
191 if (reply.got_error) {
|
|
192 mp_msg(MSGT_DEMUX, MSGL_ERR,
|
|
193 "PLAY Error from the server: %s\n",
|
|
194 reply.message.reply_str);
|
|
195 return demuxer;
|
|
196 }
|
|
197
|
25490
|
198 if (!ctl->rtsp_queue)
|
|
199 return demuxer;
|
|
200
|
24564
|
201 media = ctl->rtsp_queue->media_queue;
|
|
202 for (; media; media=media->next) {
|
|
203 sdp_medium_info * info = media->medium_info;
|
|
204 rtp_session * sess = media->rtp_sess;
|
25011
|
205 rtp_buff buff;
|
24564
|
206
|
|
207 int media_format = atoi(info->fmts);
|
|
208 rtp_pt * ptinfo = rtp_get_pt_info(sess, media_format);
|
|
209 char const * format_name = ptinfo ? ptinfo->name : NULL;
|
|
210
|
25011
|
211 memset(&buff, 0, sizeof(rtp_buff));
|
|
212
|
24564
|
213 if (sess->parsers[media_format] == NULL) {
|
|
214 mp_msg(MSGT_DEMUX, MSGL_ERR,
|
|
215 "libNemesi unsupported media format: %s\n",
|
|
216 format_name ? format_name : info->fmts);
|
|
217 continue;
|
|
218 }
|
|
219 else {
|
|
220 mp_msg(MSGT_DEMUX, MSGL_INFO,
|
|
221 "libNemesi supported media: %s\n",
|
|
222 format_name);
|
|
223 }
|
|
224
|
|
225 if (ptinfo->type == AU) {
|
|
226 if (ndsd->session[NEMESI_SESSION_AUDIO] == NULL) {
|
31609
|
227 sh_audio_t* sh_audio = new_sh_audio(demuxer,0, NULL);
|
25011
|
228 WAVEFORMATEX* wf;
|
24564
|
229 demux_stream_t* d_audio = demuxer->audio;
|
27023
|
230 demuxer->audio->id = 0;
|
24564
|
231
|
|
232 mp_msg(MSGT_DEMUX, MSGL_INFO, "Detected as AUDIO stream...\n");
|
|
233
|
|
234 link_session_and_fetch_conf(ndsd, NEMESI_SESSION_AUDIO,
|
25011
|
235 sess, &buff, NULL);
|
|
236
|
32125
|
237 wf = calloc(1,sizeof(*wf)+buff.len);
|
|
238 wf->cbSize = buff.len;
|
|
239 memcpy(wf+1, buff.data, buff.len);
|
24564
|
240
|
|
241 sh_audio->wf = wf;
|
|
242 d_audio->sh = sh_audio;
|
|
243 sh_audio->ds = d_audio;
|
|
244 wf->nSamplesPerSec = 0;
|
|
245
|
25103
|
246 wf->wFormatTag =
|
|
247 sh_audio->format = get4CC(supported_audio, format_name);
|
|
248 if ( !(wf->wFormatTag) )
|
24564
|
249 mp_msg(MSGT_DEMUX, MSGL_WARN,
|
|
250 "Unknown MPlayer format code for MIME"
|
|
251 " type \"audio/%s\"\n", format_name);
|
|
252 } else {
|
|
253 mp_msg(MSGT_DEMUX, MSGL_ERR,
|
|
254 "There is already an audio session registered,"
|
|
255 " ignoring...\n");
|
|
256 }
|
|
257 } else if (ptinfo->type == VI) {
|
25012
|
258 if (ndsd->session[NEMESI_SESSION_VIDEO] == NULL) {
|
24564
|
259 sh_video_t* sh_video;
|
|
260 BITMAPINFOHEADER* bih;
|
|
261 demux_stream_t* d_video;
|
|
262 int fps = 0;
|
|
263
|
|
264 mp_msg(MSGT_DEMUX, MSGL_INFO, "Detected as VIDEO stream...\n");
|
|
265
|
|
266 link_session_and_fetch_conf(ndsd, NEMESI_SESSION_VIDEO,
|
|
267 sess, &buff, &fps);
|
|
268
|
32125
|
269 bih = calloc(1,sizeof(*bih)+buff.len);
|
|
270 bih->biSize = sizeof(*bih)+buff.len;
|
|
271 memcpy(bih+1, buff.data, buff.len);
|
24564
|
272
|
|
273 sh_video = new_sh_video(demuxer,0);
|
|
274 sh_video->bih = bih;
|
|
275 d_video = demuxer->video;
|
|
276 d_video->sh = sh_video;
|
|
277 sh_video->ds = d_video;
|
|
278
|
24856
|
279 if (fps) {
|
24564
|
280 sh_video->fps = fps;
|
24856
|
281 sh_video->frametime = 1.0/fps;
|
|
282 }
|
24564
|
283
|
25103
|
284 bih->biCompression =
|
|
285 sh_video->format = get4CC(supported_video, format_name);
|
|
286 if ( !(bih->biCompression) ) {
|
24564
|
287 mp_msg(MSGT_DEMUX, MSGL_WARN,
|
|
288 "Unknown MPlayer format code for MIME"
|
|
289 " type \"video/%s\"\n", format_name);
|
|
290 }
|
|
291 } else {
|
|
292 mp_msg(MSGT_DEMUX, MSGL_ERR,
|
|
293 "There is already a video session registered,"
|
|
294 " ignoring...\n");
|
|
295 }
|
|
296 } else {
|
|
297 mp_msg(MSGT_DEMUX, MSGL_ERR, "Unsupported media type\n");
|
|
298 }
|
|
299 }
|
|
300
|
|
301 demuxer->stream->eof = 0;
|
|
302
|
|
303 return demuxer;
|
|
304 }
|
|
305
|
|
306 static int get_data_for_session(Nemesi_DemuxerStreamData * ndsd,
|
24997
|
307 Nemesi_SessionType stype, rtp_ssrc * ssrc,
|
|
308 rtp_frame * fr)
|
24564
|
309 {
|
24997
|
310 if (ndsd->first_pkt[stype].len != 0) {
|
|
311 fr->data = ndsd->first_pkt[stype].data;
|
|
312 fr->time_sec = ndsd->first_pkt[stype].time_sec;
|
|
313 fr->len = ndsd->first_pkt[stype].len;
|
|
314 ndsd->first_pkt[stype].len = 0;
|
|
315 return RTP_FILL_OK;
|
|
316 } else {
|
|
317 rtp_buff buff;
|
|
318 return rtp_fill_buffer(ssrc, fr, &buff);
|
|
319 }
|
|
320 }
|
24564
|
321
|
29263
|
322 static void stream_add_packet(Nemesi_DemuxerStreamData * ndsd,
|
24997
|
323 Nemesi_SessionType stype,
|
|
324 demux_stream_t* ds, rtp_frame * fr)
|
|
325 {
|
|
326 demux_packet_t* dp = new_demux_packet(fr->len);
|
|
327 memcpy(dp->buffer, fr->data, fr->len);
|
24564
|
328
|
24997
|
329 fr->time_sec += ndsd->seek;
|
|
330 ndsd->time[stype] = dp->pts = fr->time_sec;
|
|
331
|
|
332 ds_add_packet(ds, dp);
|
24564
|
333 }
|
|
334
|
25267
|
335 static int demux_rtp_fill_buffer(demuxer_t* demuxer, demux_stream_t* ds)
|
24564
|
336 {
|
|
337 Nemesi_DemuxerStreamData * ndsd = demuxer->priv;
|
|
338 Nemesi_SessionType stype;
|
24997
|
339 rtp_ssrc * ssrc;
|
24564
|
340 rtp_frame fr;
|
|
341
|
24997
|
342 if ( (!ndsd->rtsp->rtsp_queue) || (demuxer->stream->eof) ) {
|
24564
|
343 mp_msg(MSGT_DEMUX, MSGL_INFO, "End of Stream...\n");
|
|
344 demuxer->stream->eof = 1;
|
|
345 return 0;
|
|
346 }
|
|
347
|
25103
|
348 memset(&fr, 0, sizeof(fr));
|
|
349
|
24997
|
350 stype = DS_TO_STYPE(demuxer, ds);
|
|
351 if ( (ssrc = wait_for_packets(ndsd, stype)) == NULL ) {
|
|
352 mp_msg(MSGT_DEMUX, MSGL_INFO, "Bye...\n");
|
|
353 demuxer->stream->eof = 1;
|
24564
|
354 return 0;
|
|
355 }
|
|
356
|
24997
|
357 if(!get_data_for_session(ndsd, stype, ssrc, &fr))
|
|
358 stream_add_packet(ndsd, stype, ds, &fr);
|
|
359 else {
|
|
360 stype = INVERT_STYPE(stype);
|
25013
|
361
|
|
362 //Must check if we actually have a stream of the other type
|
|
363 if (!ndsd->session[stype])
|
|
364 return 1;
|
|
365
|
24997
|
366 ds = STYPE_TO_DS(demuxer, stype);
|
|
367 ssrc = wait_for_packets(ndsd, stype);
|
|
368
|
|
369 if(!get_data_for_session(ndsd, stype, ssrc, &fr))
|
|
370 stream_add_packet(ndsd, stype, ds, &fr);
|
24564
|
371 }
|
|
372
|
|
373 return 1;
|
|
374 }
|
|
375
|
|
376
|
25267
|
377 static void demux_close_rtp(demuxer_t* demuxer)
|
24564
|
378 {
|
|
379 Nemesi_DemuxerStreamData * ndsd = demuxer->priv;
|
|
380 rtsp_ctrl * ctl = ndsd->rtsp;
|
|
381 RTSP_Error err;
|
|
382
|
|
383 mp_msg(MSGT_DEMUX, MSGL_INFO, "Closing libNemesi RTSP Stream...\n");
|
|
384
|
|
385 if (ndsd == NULL)
|
|
386 return;
|
|
387
|
|
388 free(ndsd);
|
|
389
|
|
390 if (rtsp_close(ctl)) {
|
|
391 err = rtsp_wait(ctl);
|
|
392 if (err.got_error)
|
|
393 mp_msg(MSGT_DEMUX, MSGL_ERR,
|
|
394 "Error Closing Stream: %s\n",
|
|
395 err.message.reply_str);
|
|
396 }
|
|
397
|
|
398 rtsp_uninit(ctl);
|
|
399 }
|
|
400
|
|
401 static void demux_seek_rtp(demuxer_t *demuxer, float rel_seek_secs,
|
|
402 float audio_delay, int flags)
|
|
403 {
|
|
404 Nemesi_DemuxerStreamData * ndsd = demuxer->priv;
|
|
405 rtsp_ctrl * ctl = ndsd->rtsp;
|
|
406 sdp_attr * r_attr = NULL;
|
|
407 sdp_range r = {0, 0};
|
|
408 double time = ndsd->time[NEMESI_SESSION_VIDEO] ?
|
|
409 ndsd->time[NEMESI_SESSION_VIDEO] :
|
|
410 ndsd->time[NEMESI_SESSION_AUDIO];
|
|
411
|
|
412 if (!ctl->rtsp_queue)
|
|
413 return;
|
|
414
|
|
415 r_attr = sdp_get_attr(ctl->rtsp_queue->info->attr_list, "range");
|
|
416 if (r_attr)
|
|
417 r = sdp_parse_range(r_attr->value);
|
|
418
|
|
419 //flags & 1 -> absolute seek
|
|
420 //flags & 2 -> percent seek
|
|
421 if (flags == 0) {
|
|
422 time += rel_seek_secs;
|
|
423 if (time < r.begin)
|
|
424 time = r.begin;
|
|
425 else if (time > r.end)
|
|
426 time = r.end;
|
|
427 ndsd->seek = time;
|
|
428
|
|
429 mp_msg(MSGT_DEMUX,MSGL_WARN,"libNemesi SEEK %f on %f - %f)\n",
|
|
430 time, r.begin, r.end);
|
|
431
|
|
432 if (!rtsp_seek(ctl, time, 0)) {
|
|
433 RTSP_Error err = rtsp_wait(ctl);
|
|
434 if (err.got_error) {
|
|
435 mp_msg(MSGT_DEMUX, MSGL_ERR,
|
|
436 "Error Performing Seek: %s\n",
|
|
437 err.message.reply_str);
|
|
438 demuxer->stream->eof = 1;
|
|
439 }
|
|
440 else
|
|
441 mp_msg(MSGT_DEMUX, MSGL_INFO, "Seek, performed\n");
|
|
442 }
|
|
443 else {
|
|
444 mp_msg(MSGT_DEMUX, MSGL_ERR, "Unable to pause stream to perform seek\n");
|
|
445 demuxer->stream->eof = 1;
|
|
446 }
|
|
447 }
|
|
448 else
|
|
449 mp_msg(MSGT_DEMUX, MSGL_ERR, "Unsupported seek type\n");
|
|
450 }
|
|
451
|
30644
|
452 static int demux_rtp_control(struct demuxer *demuxer, int cmd, void *arg)
|
24564
|
453 {
|
|
454 Nemesi_DemuxerStreamData * ndsd = demuxer->priv;
|
|
455 rtsp_ctrl * ctl = ndsd->rtsp;
|
|
456 sdp_attr * r_attr = NULL;
|
|
457 sdp_range r = {0, 0};
|
|
458 double time = ndsd->time[NEMESI_SESSION_VIDEO] ?
|
|
459 ndsd->time[NEMESI_SESSION_VIDEO] :
|
|
460 ndsd->time[NEMESI_SESSION_AUDIO];
|
|
461
|
|
462 if (!ctl->rtsp_queue)
|
|
463 return DEMUXER_CTRL_DONTKNOW;
|
|
464
|
|
465 r_attr = sdp_get_attr(ctl->rtsp_queue->info->attr_list, "range");
|
|
466 if (r_attr)
|
|
467 r = sdp_parse_range(r_attr->value);
|
|
468
|
|
469 switch (cmd) {
|
|
470 case DEMUXER_CTRL_GET_TIME_LENGTH:
|
|
471 if (r.end == 0)
|
|
472 return DEMUXER_CTRL_DONTKNOW;
|
|
473
|
|
474 *((double *)arg) = ((double)r.end) - ((double)r.begin);
|
|
475 return DEMUXER_CTRL_OK;
|
|
476
|
|
477 case DEMUXER_CTRL_GET_PERCENT_POS:
|
|
478 if (r.end == 0)
|
|
479 return DEMUXER_CTRL_DONTKNOW;
|
|
480
|
|
481 *((int *)arg) = (int)( time * 100 / (r.end - r.begin) );
|
|
482 return DEMUXER_CTRL_OK;
|
|
483 default:
|
|
484 return DEMUXER_CTRL_DONTKNOW;
|
|
485 }
|
|
486 }
|
|
487
|
25707
|
488 const demuxer_desc_t demuxer_desc_rtp_nemesi = {
|
25270
|
489 "libnemesi RTP demuxer",
|
|
490 "nemesi",
|
24564
|
491 "",
|
|
492 "Alessandro Molina",
|
25270
|
493 "requires libnemesi",
|
25266
|
494 DEMUXER_TYPE_RTP_NEMESI,
|
24564
|
495 0, // no autodetect
|
|
496 NULL,
|
|
497 demux_rtp_fill_buffer,
|
|
498 demux_open_rtp,
|
|
499 demux_close_rtp,
|
|
500 demux_seek_rtp,
|
|
501 demux_rtp_control
|
|
502 };
|