|
1 /* |
|
2 * Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
3 * All rights reserved. |
|
4 * This component and the accompanying materials are made available |
|
5 * under the terms of "Eclipse Public License v1.0" |
|
6 * which accompanies this distribution, and is available |
|
7 * at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 * |
|
9 * Initial Contributors: |
|
10 * Nokia Corporation - initial contribution. |
|
11 * |
|
12 * Contributors: |
|
13 * |
|
14 * Description: |
|
15 * |
|
16 */ |
|
17 |
|
18 #include "xaframeworkmgr.h" |
|
19 #include <stdio.h> |
|
20 #include <string.h> |
|
21 |
|
22 /* Default line width permitted in the cfg file + 2 to hold "\r\n"*/ |
|
23 #define LINEWIDTH 82 |
|
24 |
|
25 typedef enum |
|
26 { |
|
27 FWMgrTagNone, |
|
28 FWMgrTagURIScheme, |
|
29 FWMgrTagFileExt |
|
30 } FWMgrTagType; |
|
31 |
|
32 |
|
33 /* Config file location */ |
|
34 const char configFileLocation[] = "c:/openmaxal/openmaxal.cfg"; |
|
35 |
|
36 /* Tags used for parsing */ |
|
37 const char mediaPlayerBeginTag[] = "<mediaplayer>"; |
|
38 const char mediaPlayerEndTag[] = "</mediaplayer>"; |
|
39 const char mediaRecorderBeginTag[] = "<mediarecorder>"; |
|
40 const char mediaRecorderEndTag[] = "</mediarecorder>"; |
|
41 const char mediaFrameworkMmfBeginTag[] = "<mmf>"; |
|
42 const char mediaFrameworkMmfEndTag[] = "</mmf>"; |
|
43 const char mediaFrameworkGstBeginTag[] = "<gst>"; |
|
44 const char uriSchemeBeginTag[] = "<urischeme>"; |
|
45 const char uriSchemeEndTag[] = "</urischeme>"; |
|
46 const char mediaFrameworkGstEndTag[] = "</gst>"; |
|
47 const char fileExtBeginTag[] = "<fileext>"; |
|
48 const char fileExtEndTag[] = "</fileext>"; |
|
49 |
|
50 /* Local function definitions */ |
|
51 /* returns FWMgrTrue if processed successfully */ |
|
52 static FWMgrBool processConfigEntry( |
|
53 const char* buffer, |
|
54 FWMgrMOType *mediaType, |
|
55 FWMgrFwType *frameworkType, |
|
56 FWMgrTagType *tagType, |
|
57 FWMgrBool *newNode, |
|
58 FrameworkMap **node); |
|
59 |
|
60 /* returns FWMgrTrue if processed successfully */ |
|
61 static FWMgrBool processTagType( |
|
62 const char* buffer, |
|
63 FWMgrFwType *frameworkType, |
|
64 FWMgrTagType *tagType, |
|
65 FrameworkMap **node); |
|
66 |
|
67 /* returns FWMgrTrue if processed successfully */ |
|
68 static FWMgrBool tokenizeTag(FWMgrTagType tagType, const char* buffer, FrameworkMap **node); |
|
69 |
|
70 /* Crates a default rules config file */ |
|
71 static int createDefaultRules(const char * filename); |
|
72 |
|
73 /* Global functions from header file */ |
|
74 |
|
75 /* FrameworkMap* XAFrameworkMgr_CreateFrameworkMap |
|
76 * Description: Creates a list of framework and use-case map. |
|
77 */ |
|
78 FrameworkMap* XAFrameworkMgr_CreateFrameworkMap() |
|
79 { |
|
80 char buffer[LINEWIDTH]; |
|
81 int readSize; |
|
82 int lineNumber = 0; |
|
83 FWMgrBool processedEntry = FWMgrTrue; |
|
84 FWMgrMOType currentMediaType = FWMgrMOUnknown; |
|
85 FWMgrFwType currentFrameworkType = FWMgrFWUknown; |
|
86 FWMgrTagType currentTagType = FWMgrTagNone; |
|
87 FrameworkMap *curNode = NULL; |
|
88 FWMgrBool newNode; |
|
89 FrameworkMap *frameworkMap = NULL; |
|
90 FILE* fp = fopen(configFileLocation, "r"); |
|
91 |
|
92 if (fp == NULL) |
|
93 { |
|
94 createDefaultRules(configFileLocation); |
|
95 } |
|
96 |
|
97 fp = fopen(configFileLocation, "r"); |
|
98 if (fp != NULL) |
|
99 { |
|
100 while((fgets(buffer, LINEWIDTH, fp) != NULL) && processedEntry) |
|
101 { |
|
102 /* keep looping until NULL pointer OR error... */ |
|
103 lineNumber++; |
|
104 readSize = strlen(buffer); |
|
105 /* Ignore comments line */ |
|
106 if (buffer[0] == '#') |
|
107 continue; |
|
108 |
|
109 /* Ignore replace "\r\n" with '\0' */ |
|
110 if ((readSize >= 2) && (buffer[readSize-2]=='\r') && (buffer[readSize-1]=='\n')) |
|
111 buffer[readSize-2]='\0'; |
|
112 |
|
113 /* Ignore new line... */ |
|
114 if (readSize == 2) |
|
115 continue; |
|
116 |
|
117 processedEntry = processConfigEntry( |
|
118 buffer, |
|
119 ¤tMediaType, |
|
120 ¤tFrameworkType, |
|
121 ¤tTagType, |
|
122 &newNode, |
|
123 &curNode); |
|
124 if (newNode) |
|
125 { |
|
126 /*Just link to the last element in the chain*/ |
|
127 if (!frameworkMap) |
|
128 { |
|
129 frameworkMap = curNode; |
|
130 } |
|
131 else |
|
132 { |
|
133 FrameworkMap *lastNode = frameworkMap; |
|
134 while (lastNode->next) |
|
135 { |
|
136 lastNode = lastNode->next; |
|
137 } |
|
138 lastNode->next = curNode; |
|
139 } |
|
140 } |
|
141 } |
|
142 fclose(fp); |
|
143 } |
|
144 else |
|
145 { |
|
146 printf("unable to open config file!\n"); |
|
147 } |
|
148 return frameworkMap; |
|
149 } |
|
150 |
|
151 #ifdef _DEBUG |
|
152 /* void XAFrameworkMgr_DumpFrameworkMap |
|
153 * Description: Prints map to std console. |
|
154 */ |
|
155 void XAFrameworkMgr_DumpFrameworkMap(FrameworkMap *map) |
|
156 { |
|
157 FrameworkMap *node = map; |
|
158 int i; |
|
159 int loopIndex = 0; |
|
160 while (node) |
|
161 { |
|
162 loopIndex++; |
|
163 printf("%d>", loopIndex); |
|
164 if (node->moType == FWMgrMOPlayer) |
|
165 printf("MediaPlayer-"); |
|
166 else if (node->moType == FWMgrMORecorder) |
|
167 printf("MediaRecrdr-"); |
|
168 else |
|
169 printf("UKNOWN-"); |
|
170 if (node->fwType == FWMgrFWMMF) |
|
171 printf("MMF-"); |
|
172 else if (node->fwType == FWMgrFWGST) |
|
173 printf("GST-"); |
|
174 else |
|
175 printf("UKNOWN-"); |
|
176 printf("Scheme["); |
|
177 for(i=0;i<node->uriSchemeCount;i++) |
|
178 printf(" %s", node->uriSchemes[i]); |
|
179 printf("]FileExt["); |
|
180 for(i=0;i<node->fileExtCount;i++) |
|
181 printf(" %s", node->fileExts[i]); |
|
182 printf("]\n"); |
|
183 node = node->next; |
|
184 } |
|
185 } |
|
186 #endif |
|
187 |
|
188 /* void XAFrameworkMgr_DeleteFrameworkMap |
|
189 * Description: Deletes the list of framework and use-case map. |
|
190 */ |
|
191 void XAFrameworkMgr_DeleteFrameworkMap(FrameworkMap **map) |
|
192 { |
|
193 FrameworkMap *node = *map; |
|
194 FrameworkMap *nextNode = NULL; |
|
195 int i; |
|
196 while (node) |
|
197 { |
|
198 for(i=0;i<node->uriSchemeCount;i++) |
|
199 free (node->uriSchemes[i]); |
|
200 free (node->uriSchemes); |
|
201 |
|
202 for(i=0;i<node->fileExtCount;i++) |
|
203 free (node->fileExts[i]); |
|
204 free (node->fileExts); |
|
205 |
|
206 nextNode = node->next; |
|
207 free (node); |
|
208 node = nextNode; |
|
209 } |
|
210 *map = NULL; |
|
211 } |
|
212 |
|
213 /* FWMgrFwType XAFrameworkMgr_GetFramework |
|
214 * Description: Returns the framework enum that handles uri. |
|
215 */ |
|
216 FWMgrFwType XAFrameworkMgr_GetFramework( |
|
217 FrameworkMap *map, |
|
218 const char *uri, |
|
219 FWMgrMOType mediaObject) |
|
220 { |
|
221 FWMgrFwType retVal = FWMgrFWUknown; |
|
222 char fileScheme[] = "file"; |
|
223 char *uriScheme = NULL; |
|
224 char *fileExt = NULL; |
|
225 FrameworkMap *node = map; |
|
226 FWMgrBool uriMatchFound = FWMgrFalse; |
|
227 FWMgrBool fileExtMatchFound = FWMgrFalse; |
|
228 int i = 0; |
|
229 int copyLen = 0; |
|
230 |
|
231 if (!map || !uri) |
|
232 { |
|
233 /* TODO Log invalid uri */ |
|
234 return retVal; |
|
235 } |
|
236 |
|
237 /* Get uri scheme */ |
|
238 uriScheme = strchr(uri, ':'); |
|
239 if ( uriScheme == NULL) |
|
240 { |
|
241 /* TODO Log invalid uri */ |
|
242 return retVal; |
|
243 } |
|
244 |
|
245 copyLen = (uriScheme - uri); |
|
246 uriScheme = (char*)calloc(copyLen + 1, sizeof(char)); |
|
247 strncpy(uriScheme, uri, copyLen); |
|
248 uriScheme[copyLen] = '\0'; /*Null terminate it*/ |
|
249 |
|
250 if (strcasecmp(uriScheme, fileScheme) == 0) |
|
251 { |
|
252 /* Get uri extension */ |
|
253 char* dotLoc = strrchr(uri, '.'); |
|
254 if ( dotLoc == NULL) |
|
255 { |
|
256 /* TODO Log invalid uri */ |
|
257 free(uriScheme); |
|
258 return retVal; |
|
259 } |
|
260 /* We need to add 1 to exclude '.'*/ |
|
261 copyLen = strlen(uri) - (dotLoc + 1 - uri); |
|
262 fileExt = (char*)calloc(copyLen + 1, sizeof(char)); |
|
263 strncpy(fileExt, dotLoc + 1, copyLen); |
|
264 fileExt[copyLen] = '\0'; /*Null terminate it*/ |
|
265 } |
|
266 |
|
267 while (node) |
|
268 { |
|
269 if (mediaObject == node->moType) |
|
270 { |
|
271 uriMatchFound = FWMgrFalse; |
|
272 fileExtMatchFound = FWMgrFalse; |
|
273 /* Match for uri*/ |
|
274 for(i = 0; i < node->uriSchemeCount; i++) |
|
275 { |
|
276 if (strcasecmp(uriScheme, node->uriSchemes[i]) == 0) |
|
277 { |
|
278 uriMatchFound = FWMgrTrue; |
|
279 break; |
|
280 } |
|
281 } |
|
282 /* if uri scheme is not file, we only need to check for uri */ |
|
283 if (!fileExt) |
|
284 { |
|
285 fileExtMatchFound = FWMgrTrue; |
|
286 } |
|
287 else |
|
288 { |
|
289 for(i = 0; i < node->fileExtCount; i++) |
|
290 { |
|
291 if (strcasecmp(fileExt, node->fileExts[i]) == 0) |
|
292 { |
|
293 fileExtMatchFound = FWMgrTrue; |
|
294 break; |
|
295 } |
|
296 } |
|
297 } |
|
298 |
|
299 if ((uriMatchFound == FWMgrTrue) && |
|
300 (fileExtMatchFound == FWMgrTrue)) |
|
301 { |
|
302 retVal = node->fwType; |
|
303 break; |
|
304 } |
|
305 } |
|
306 node = node->next; |
|
307 } |
|
308 free(uriScheme); |
|
309 free(fileExt); |
|
310 return retVal; |
|
311 } |
|
312 |
|
313 /* Local functions */ |
|
314 |
|
315 /* FWMgrBool processConfigEntry |
|
316 * Description: Processes a single line entry from the config file. |
|
317 */ |
|
318 FWMgrBool processConfigEntry( |
|
319 const char* buffer, |
|
320 FWMgrMOType *mediaType, |
|
321 FWMgrFwType *frameworkType, |
|
322 FWMgrTagType *tagType, |
|
323 FWMgrBool *newNode, |
|
324 FrameworkMap **node) |
|
325 { |
|
326 FWMgrBool processedSuccessfully = FWMgrTrue; |
|
327 *newNode = FWMgrFalse; |
|
328 switch (*mediaType) |
|
329 { |
|
330 case FWMgrMOUnknown: |
|
331 { |
|
332 if (strcmp(buffer, mediaPlayerBeginTag) == 0) |
|
333 { |
|
334 *mediaType = FWMgrMOPlayer; |
|
335 *frameworkType = FWMgrFWUknown; |
|
336 *tagType = FWMgrTagNone; |
|
337 *node = NULL; |
|
338 } |
|
339 else if (strcmp(buffer, mediaRecorderBeginTag) == 0) |
|
340 { |
|
341 *mediaType = FWMgrMORecorder; |
|
342 *frameworkType = FWMgrFWUknown; |
|
343 *tagType = FWMgrTagNone; |
|
344 *node = NULL; |
|
345 } |
|
346 } |
|
347 break; |
|
348 case FWMgrMOPlayer: |
|
349 case FWMgrMORecorder: |
|
350 { |
|
351 switch (*frameworkType) |
|
352 { |
|
353 case FWMgrFWUknown: |
|
354 { |
|
355 if ((*mediaType == FWMgrMOPlayer) && (strcmp(buffer, mediaPlayerEndTag) == 0)) |
|
356 *mediaType = FWMgrMOUnknown; |
|
357 else if ((*mediaType == FWMgrMORecorder) && (strcmp(buffer, mediaRecorderEndTag) == 0)) |
|
358 *mediaType = FWMgrMOUnknown; |
|
359 else if ( (strcmp(buffer, mediaFrameworkMmfBeginTag) == 0) || |
|
360 (strcmp(buffer, mediaFrameworkGstBeginTag) == 0) ) |
|
361 { |
|
362 *frameworkType = FWMgrFWMMF; |
|
363 if (strcmp(buffer, mediaFrameworkGstBeginTag) == 0) |
|
364 *frameworkType = FWMgrFWGST; |
|
365 if (*node) |
|
366 { |
|
367 printf("Fatal error error. Entry already exists and creating another one!!!"); |
|
368 return FWMgrFalse; |
|
369 } |
|
370 *node = (FrameworkMap*)calloc(1, sizeof(FrameworkMap)); |
|
371 if (!(*node)) |
|
372 { |
|
373 printf("Fatal error. No memory to create an Entry!!!"); |
|
374 return FWMgrFalse; |
|
375 } |
|
376 *newNode = FWMgrTrue; |
|
377 (*node)->moType = *mediaType; |
|
378 (*node)->fwType = *frameworkType; |
|
379 } |
|
380 } |
|
381 break; |
|
382 case FWMgrFWMMF: |
|
383 { |
|
384 processedSuccessfully = processTagType( |
|
385 buffer, |
|
386 frameworkType, |
|
387 tagType, |
|
388 node); |
|
389 } |
|
390 break; |
|
391 case FWMgrFWGST: |
|
392 { |
|
393 processedSuccessfully = processTagType( |
|
394 buffer, |
|
395 frameworkType, |
|
396 tagType, |
|
397 node); |
|
398 } |
|
399 break; |
|
400 default: |
|
401 processedSuccessfully = FWMgrFalse; |
|
402 break; |
|
403 }; |
|
404 } |
|
405 break; |
|
406 default: |
|
407 processedSuccessfully = FWMgrFalse; |
|
408 break; |
|
409 }; |
|
410 return processedSuccessfully; |
|
411 } |
|
412 |
|
413 /* FWMgrBool processTagType |
|
414 * Description: Processes a framework type, uri, file tags entry from the config file. |
|
415 */ |
|
416 FWMgrBool processTagType(const char* buffer, |
|
417 FWMgrFwType *frameworkType, |
|
418 FWMgrTagType *tagType, |
|
419 FrameworkMap **node) |
|
420 { |
|
421 FWMgrBool processedSuccessfully = FWMgrTrue; |
|
422 switch (*tagType) |
|
423 { |
|
424 case FWMgrTagNone: |
|
425 { |
|
426 if (((*frameworkType == FWMgrFWMMF) && (strcmp(buffer, mediaFrameworkMmfEndTag) == 0)) || |
|
427 ((*frameworkType == FWMgrFWGST) && (strcmp(buffer, mediaFrameworkGstEndTag) == 0))) |
|
428 { |
|
429 *node = NULL; |
|
430 *frameworkType = FWMgrFWUknown; |
|
431 } |
|
432 else if (strcmp(buffer, uriSchemeBeginTag) == 0) |
|
433 *tagType = FWMgrTagURIScheme; |
|
434 else if (strcmp(buffer, fileExtBeginTag) == 0) |
|
435 *tagType = FWMgrTagFileExt; |
|
436 } |
|
437 break; |
|
438 case FWMgrTagURIScheme: |
|
439 { |
|
440 if (strcmp(buffer, uriSchemeEndTag) == 0) |
|
441 *tagType = FWMgrTagNone; |
|
442 else |
|
443 { |
|
444 processedSuccessfully = FWMgrFalse; |
|
445 if (*node) |
|
446 processedSuccessfully = tokenizeTag(FWMgrTagURIScheme, buffer, node); |
|
447 } |
|
448 } |
|
449 break; |
|
450 case FWMgrTagFileExt: |
|
451 { |
|
452 if (strcmp(buffer, fileExtEndTag) == 0) |
|
453 *tagType = FWMgrTagNone; |
|
454 else |
|
455 { |
|
456 processedSuccessfully = FWMgrFalse; |
|
457 if (*node) |
|
458 processedSuccessfully = tokenizeTag(FWMgrTagFileExt, buffer, node); |
|
459 } |
|
460 } |
|
461 break; |
|
462 default: |
|
463 break; |
|
464 }; |
|
465 return processedSuccessfully; |
|
466 } |
|
467 |
|
468 /* FWMgrBool processTagType |
|
469 * Description: Processes a framework type, uri, file tags entry from the config file. |
|
470 */ |
|
471 FWMgrBool tokenizeTag(FWMgrTagType tagType, const char* buffer, FrameworkMap **node) |
|
472 { |
|
473 char* tempStartPtr = /*const_cast<char*>*/(char*)(buffer); |
|
474 char* tempEndPtr = /*const_cast<char*>*/(char*)(buffer); |
|
475 int index = 0; |
|
476 int strLen = 0; |
|
477 |
|
478 if (tagType == FWMgrTagURIScheme) |
|
479 { |
|
480 (*node)->uriSchemeCount = atoi(buffer); |
|
481 (*node)->uriSchemes = (char**)calloc((*node)->uriSchemeCount, sizeof (*((*node)->uriSchemes))); |
|
482 if (!(*node)->uriSchemes) |
|
483 { |
|
484 printf("Fatal error. No memory to create an Entry!!!"); |
|
485 return FWMgrFalse; |
|
486 } |
|
487 } |
|
488 else if (tagType == FWMgrTagFileExt) |
|
489 { |
|
490 (*node)->fileExtCount = atoi(buffer); |
|
491 (*node)->fileExts = (char**)calloc((*node)->fileExtCount, sizeof (*((*node)->fileExts))); |
|
492 if (!(*node)->fileExts) |
|
493 { |
|
494 printf("Fatal error. No memory to create an Entry!!!"); |
|
495 return FWMgrFalse; |
|
496 } |
|
497 } |
|
498 else |
|
499 return FWMgrFalse; |
|
500 |
|
501 /*Find the index of :*/ |
|
502 tempStartPtr = strchr(tempStartPtr, ','); |
|
503 index = 0; |
|
504 while (tempStartPtr && (strlen(tempStartPtr) > 1)) |
|
505 { |
|
506 tempStartPtr++; /* Ignore separator ','*/ |
|
507 tempEndPtr = strchr(tempStartPtr, ','); |
|
508 strLen = (tempEndPtr - tempStartPtr) + 1; /* To hold null terminator */ |
|
509 if (strLen > 0) |
|
510 { |
|
511 if (tagType == FWMgrTagURIScheme) |
|
512 { |
|
513 (*node)->uriSchemes[index] = (char*)calloc(strLen, sizeof(char)); |
|
514 strncpy((*node)->uriSchemes[index], tempStartPtr, strLen); |
|
515 (*node)->uriSchemes[index][strLen-1] = '\0'; /*Null terminate it*/ |
|
516 index++; |
|
517 } |
|
518 else if (tagType == FWMgrTagFileExt) |
|
519 { |
|
520 (*node)->fileExts[index] = (char*)calloc(strLen, sizeof(char)); |
|
521 strncpy((*node)->fileExts[index], tempStartPtr, strLen); |
|
522 (*node)->fileExts[index][strLen-1] = '\0'; /*Null terminate it*/ |
|
523 index++; |
|
524 } |
|
525 } |
|
526 tempStartPtr = tempEndPtr; |
|
527 } |
|
528 return FWMgrTrue; |
|
529 } |
|
530 |
|
531 int createDefaultRules(const char * filename) |
|
532 { |
|
533 FILE* fp = fopen(filename, "w"); |
|
534 if (fp == NULL) |
|
535 return /*KErrAccessDenied*/-21; |
|
536 fputs("#/*\r\n", fp); |
|
537 fputs("#* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).\r\n", fp); |
|
538 fputs("#* All rights reserved.\r\n", fp); |
|
539 fputs("#* This component and the accompanying materials are made available\r\n", fp); |
|
540 fputs("#* under the terms of \"Eclipse Public License v1.0\"\r\n", fp); |
|
541 fputs("#* which accompanies this distribution, and is available\r\n", fp); |
|
542 fputs("#* at the URL \"http://www.eclipse.org/legal/epl-v10.html\".\r\n", fp); |
|
543 fputs("#*\r\n", fp); |
|
544 fputs("#* Initial Contributors:\r\n", fp); |
|
545 fputs("#* Nokia Corporation - initial contribution.\r\n", fp); |
|
546 fputs("#*\r\n", fp); |
|
547 fputs("#* Contributors:\r\n", fp); |
|
548 fputs("#*\r\n", fp); |
|
549 fputs("#* Description:\r\n", fp); |
|
550 fputs("#*\r\n", fp); |
|
551 fputs("#*/\r\n", fp); |
|
552 fputs("#============================================================================>|\r\n", fp); |
|
553 fputs("# Must not exceed 80 chars each line=========================================>|\r\n", fp); |
|
554 fputs("#============================================================================>|\r\n", fp); |
|
555 fputs("<mediaplayer>\r\n", fp); |
|
556 fputs("<mmf>\r\n", fp); |
|
557 fputs("<urischeme>\r\n", fp); |
|
558 fputs("# Num of entries followed by actual entries all ending with a comma\r\n", fp); |
|
559 fputs("3,file,http,rtsp,\r\n", fp); |
|
560 fputs("</urischeme>\r\n", fp); |
|
561 fputs("<fileext>\r\n", fp); |
|
562 fputs("15,3gp,wma,wmv,wav,amr,mp3,mp4,rm,ra,avi,mkv,aac,mid,awb,3g2,\r\n", fp); |
|
563 fputs("</fileext>\r\n", fp); |
|
564 fputs("</mmf>\r\n", fp); |
|
565 fputs("</mediaplayer>\r\n", fp); |
|
566 fputs("<mediarecorder>\r\n", fp); |
|
567 fputs("<gst>\r\n", fp); |
|
568 fputs("<urischeme>\r\n", fp); |
|
569 fputs("# Num of entries followed by actual entries all ending with a comma\r\n", fp); |
|
570 fputs("1,file,\r\n", fp); |
|
571 fputs("</urischeme>\r\n", fp); |
|
572 fputs("<fileext>\r\n", fp); |
|
573 fputs("3,wav,amr,mp4,\r\n", fp); |
|
574 fputs("</fileext>\r\n", fp); |
|
575 fputs("</gst>\r\n", fp); |
|
576 fputs("</mediarecorder>\r\n", fp); |
|
577 fclose(fp); |
|
578 return 0; |
|
579 } |