ArDrone SDK 1.8 added
[mardrone] / mardrone / ARDrone_SDK_Version_1_8_20110726 / ARDroneLib / Soft / Lib / ardrone_tool / Video / video_stage_ffmpeg_recorder.c
1 /* CODE ADAPATED FOR ARDRONE NAVIGATION FROM :
2  *
3  * Libavformat API example: Output a media file in any supported
4  * libavformat format. The default codecs are used.
5  *
6  * Copyright (c) 2003 Fabrice Bellard
7  *
8  * Permission is hereby granted, free of charge, to any person obtaining a copy
9  * of this software and associated documentation files (the "Software"), to deal
10  * in the Software without restriction, including without limitation the rights
11  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12  * copies of the Software, and to permit persons to whom the Software is
13  * furnished to do so, subject to the following conditions:
14  *
15  * The above copyright notice and this permission notice shall be included in
16  * all copies or substantial portions of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24  * THE SOFTWARE.
25  */
26
27 #include <generated_custom.h>
28
29 /*#ifndef USE_FFMPEG_RECORDER
30 #define USE_FFMPEG_RECORDER
31 #endif*/
32
33 #ifdef USE_FFMPEG_RECORDER
34
35 /* From FFMPEG example */
36 #include <stdlib.h>
37 #include <stdio.h>
38 #include <string.h>
39 #include <math.h>
40
41 #include <libavformat/avformat.h>
42 #include <libswscale/swscale.h>
43 #include <libavfilter/avfilter.h>
44 #include <libavutil/avutil.h>
45 #include <libavcodec/avcodec.h>
46 #include <libavcodec/opt.h>
47
48
49 #include <unistd.h>
50 #include <sys/types.h>
51 #include <sys/stat.h>
52 #include <fcntl.h>
53
54
55
56 /* Should be in avutil.h but Ubuntu 10.04 package is too old
57  * http://cekirdek.pardus.org.tr/~ismail/ffmpeg-docs/libavutil_2avutil_8h-source.html
58  * */
59 #if LIBAVUTIL_VERSION_MAJOR < 50
60 #define AV_PKT_FLAG_KEY   0x0001
61 enum AVMediaType {
62      AVMEDIA_TYPE_UNKNOWN = -1,
63      AVMEDIA_TYPE_VIDEO,
64      AVMEDIA_TYPE_AUDIO,
65      AVMEDIA_TYPE_DATA,
66      AVMEDIA_TYPE_SUBTITLE,
67      AVMEDIA_TYPE_ATTACHMENT,
68      AVMEDIA_TYPE_NB
69  };
70 #endif
71
72
73 #include <time.h>
74 #ifndef _WIN32
75         #include <sys/time.h>
76 #else
77   #include <sys/timeb.h>
78  #include <Winsock2.h>  // for timeval structure
79
80  int gettimeofday (struct timeval *tp, void *tz)
81  {
82          struct _timeb timebuffer;
83          _ftime (&timebuffer);
84          tp->tv_sec = (long)timebuffer.time;
85          tp->tv_usec = (long)timebuffer.millitm * 1000;
86          return 0;
87  }
88 #endif
89
90 #include <VP_Os/vp_os_malloc.h>
91 #include <VP_Api/vp_api_picture.h>
92
93 #include <config.h>
94 #include <ardrone_tool/Video/video_stage_ffmpeg_recorder.h>
95
96 /*#ifdef USE_VIDEO_YUV
97 #define VIDEO_FILE_EXTENSION "yuv"
98 #else
99 #define VIDEO_FILE_EXTENSION "y"
100 #endif
101 #define  VIDEO_TIMESTAMPS_FILE_EXTENSION "time.txt"
102
103 #ifndef VIDEO_FILE_DEFAULT_PATH
104 #ifdef USE_ELINUX
105 #define VIDEO_FILE_DEFAULT_PATH "/data/video"
106 #else
107 #define VIDEO_FILE_DEFAULT_PATH "."
108 #endif
109 #endif
110 */
111 #define VIDEO_FILE_DEFAULT_PATH "."
112
113  /*- LibAVFormat variables */
114         const char *filename;
115         AVOutputFormat *fmt;
116         AVFormatContext *oc;
117         AVStream *video_st;
118         double video_pts;
119         int i;
120
121 #define STREAM_FRAME_RATE (60)
122 #define STREAM_BIT_RATE_KBITS 1600
123 #define STREAM_PIX_FMT PIX_FMT_YUV420P /* default pix_fmt */
124
125 static int sws_flags = SWS_BICUBIC;
126
127 AVFrame *picture_to_encode=NULL, *tmp_picture=NULL;
128 uint8_t *video_outbuf=NULL;
129 int frame_count=0, video_outbuf_size=0;
130
131
132 const vp_api_stage_funcs_t video_ffmpeg_recorder_funcs = {
133   (vp_api_stage_handle_msg_t) video_stage_ffmpeg_recorder_handle,
134   (vp_api_stage_open_t) video_stage_ffmpeg_recorder_open,
135   (vp_api_stage_transform_t) video_stage_ffmpeg_recorder_transform,
136   (vp_api_stage_close_t) video_stage_ffmpeg_recorder_close
137 };
138
139 char video_filename_ffmpeg[VIDEO_FILENAME_LENGTH];
140
141 struct
142 {
143         int width,height;
144         char* buffer;
145         unsigned long long int timestamp_us;
146         int frame_number;
147 }previous_frame;
148
149
150 /******************************************************************************************************************************************/
151
152 void create_video_file(const char*filename,int width,int height)
153 {
154 /* auto detect the output format from the name. default is
155        mpeg. */
156     //fmt = av_guess_format(NULL, filename, NULL);
157
158 #if (LIBAVFORMAT_VERSION_INT>=AV_VERSION_INT(52,81,0))
159         #define libavformat_guess_format av_guess_format
160 #else
161         #define libavformat_guess_format guess_format
162 #endif
163
164         fmt = libavformat_guess_format(NULL, filename, NULL);
165
166         if (!fmt) {
167         printf("Could not deduce output format from file extension: using MPEG.\n");
168         //fmt = av_guess_format("mpeg", NULL, NULL);
169         fmt = libavformat_guess_format("mpeg", NULL, NULL);
170     }
171     if (!fmt) {
172         fprintf(stderr, "Could not find suitable output format\n");
173         exit(1);
174     }
175
176     /* allocate the output media context */
177     oc = avformat_alloc_context();
178     if (!oc) {
179         fprintf(stderr, "Memory error\n");
180         exit(1);
181     }
182     oc->oformat = fmt;
183     snprintf(oc->filename, sizeof(oc->filename), "%s", filename);
184
185     /* add the audio and video streams using the default format codecs
186        and initialize the codecs */
187     video_st = NULL;
188
189     if (fmt->video_codec != CODEC_ID_NONE) {
190         video_st = add_video_stream(oc, fmt->video_codec,width,height);
191     }
192
193     /* set the output parameters (must be done even if no
194        parameters). */
195     if (av_set_parameters(oc, NULL) < 0) {
196         fprintf(stderr, "Invalid output format parameters\n");
197         exit(1);
198     }
199
200     dump_format(oc, 0, filename, 1);
201
202     /* now that all the parameters are set, we can open the audio and
203        video codecs and allocate the necessary encode buffers */
204     if (video_st)
205         open_video(oc, video_st);
206
207     /* open the output file, if needed */
208     if (!(fmt->flags & AVFMT_NOFILE)) {
209         if (url_fopen(&oc->pb, filename, URL_WRONLY) < 0) {
210             fprintf(stderr, "Could not open '%s'\n", filename);
211             exit(1);
212         }
213     }
214
215     /* write the stream header, if any */
216     av_write_header(oc);
217  }
218
219
220 void close_video_file()
221 {
222 /* write the trailer, if any.  the trailer must be written
223      * before you close the CodecContexts open when you wrote the
224      * header; otherwise write_trailer may try to use memory that
225      * was freed on av_codec_close() */
226     av_write_trailer(oc);
227
228     /* close each codec */
229     if (video_st)
230         close_video(oc, video_st);
231
232     /* free the streams */
233     for(i = 0; i < oc->nb_streams; i++) {
234         av_freep(&oc->streams[i]->codec);
235         av_freep(&oc->streams[i]);
236     }
237
238     if (!(fmt->flags & AVFMT_NOFILE)) {
239         /* close the output file */
240         url_fclose(oc->pb);
241     }
242
243     /* free the stream */
244     av_free(oc);
245 }
246
247
248
249
250 /**************************************************************/
251 /* video output */
252
253
254
255 /* add a video output stream */
256  AVStream *add_video_stream(AVFormatContext *oc, enum CodecID codec_id, int width, int height)
257 {
258     AVCodecContext *c;
259     AVStream *st;
260
261     st = av_new_stream(oc, 0);
262     if (!st) {
263         fprintf(stderr, "Could not alloc stream\n");
264         exit(1);
265     }
266
267     c = st->codec;
268     c->codec_id = codec_id;
269     c->codec_type = AVMEDIA_TYPE_VIDEO;
270
271     /* put sample parameters */
272     c->bit_rate = (STREAM_BIT_RATE_KBITS)*1000;
273     /* resolution must be a multiple of two */
274     c->width = width;
275     c->height = height;
276     /* time base: this is the fundamental unit of time (in seconds) in terms
277        of which frame timestamps are represented. for fixed-fps content,
278        timebase should be 1/framerate and timestamp increments should be
279        identically 1. */
280     c->time_base.den = STREAM_FRAME_RATE;
281     c->time_base.num = 1;
282     c->gop_size = 12; /* emit one intra frame every twelve frames at most */
283     c->pix_fmt = STREAM_PIX_FMT;
284     if (c->codec_id == CODEC_ID_MPEG2VIDEO) {
285         /* just for testing, we also add B frames */
286         c->max_b_frames = 2;
287     }
288     if (c->codec_id == CODEC_ID_MPEG1VIDEO){
289         /* Needed to avoid using macroblocks in which some coeffs overflow.
290            This does not happen with normal video, it just happens here as
291            the motion of the chroma plane does not match the luma plane. */
292         c->mb_decision=2;
293     }
294     // some formats want stream headers to be separate
295     if(oc->oformat->flags & AVFMT_GLOBALHEADER)
296         c->flags |= CODEC_FLAG_GLOBAL_HEADER;
297
298     return st;
299 }
300
301  AVFrame *alloc_picture(enum PixelFormat pix_fmt, int width, int height)
302 {
303     AVFrame *picture;
304     uint8_t *picture_buf;
305     int size;
306
307     picture = avcodec_alloc_frame();
308     if (!picture)
309         return NULL;
310     size = avpicture_get_size(pix_fmt, width, height);
311     picture_buf = av_malloc(size);
312     if (!picture_buf) {
313         av_free(picture);
314         return NULL;
315     }
316     avpicture_fill((AVPicture *)picture, picture_buf,
317                    pix_fmt, width, height);
318     return picture;
319 }
320
321  void open_video(AVFormatContext *oc, AVStream *st)
322 {
323     AVCodec *codec;
324     AVCodecContext *c;
325
326     c = st->codec;
327
328     /* find the video encoder */
329     codec = avcodec_find_encoder(c->codec_id);
330     if (!codec) {
331         fprintf(stderr, "codec not found\n");
332         exit(1);
333     }
334
335     /* open the codec */
336     if (avcodec_open(c, codec) < 0) {
337         fprintf(stderr, "could not open codec\n");
338         exit(1);
339     }
340
341     video_outbuf = NULL;
342     if (!(oc->oformat->flags & AVFMT_RAWPICTURE)) {
343         /* allocate output buffer */
344         /* XXX: API change will be done */
345         /* buffers passed into lav* can be allocated any way you prefer,
346            as long as they're aligned enough for the architecture, and
347            they're freed appropriately (such as using av_free for buffers
348            allocated with av_malloc) */
349         video_outbuf_size = 200000;
350         video_outbuf = av_malloc(video_outbuf_size);
351     }
352
353     /* allocate the encoded raw picture */
354     picture_to_encode =  avcodec_alloc_frame();
355     //alloc_picture(c->pix_fmt, c->width, c->height);
356     /*if (!picture) {
357         fprintf(stderr, "Could not allocate picture\n");
358         exit(1);
359     }*/
360
361     /* if the output format is not YUV420P, then a temporary YUV420P
362        picture is needed too. It is then converted to the required
363        output format */
364     tmp_picture = NULL;
365     if (c->pix_fmt != PIX_FMT_YUV420P) {
366         tmp_picture = alloc_picture(PIX_FMT_YUV420P, c->width, c->height);
367         if (!tmp_picture) {
368             fprintf(stderr, "Could not allocate temporary picture\n");
369             exit(1);
370         }
371     }
372 }
373
374
375  void write_video_frame(AVFormatContext *oc, AVStream *st)
376 {
377     int out_size, ret;
378     AVCodecContext *c;
379     static struct SwsContext *img_convert_ctx;
380
381     //printf("Here0 \n");
382
383     c = st->codec;
384
385         if (c->pix_fmt != PIX_FMT_YUV420P) {
386             /* as we only generate a YUV420P picture, we must convert it
387                to the codec pixel format if needed */
388             if (img_convert_ctx == NULL) {
389
390                 #if (LIBSWSCALE_VERSION_INT<AV_VERSION_INT(0,12,0))
391                 img_convert_ctx = sws_getContext(c->width, c->height,
392                                                  PIX_FMT_YUV420P,
393                                                  c->width, c->height,
394                                                  c->pix_fmt,
395                                                  sws_flags, NULL, NULL, NULL);
396                 #else
397                 img_convert_ctx = sws_alloc_context();
398
399                 if (img_convert_ctx == NULL) {
400                     fprintf(stderr, "Cannot initialize the conversion context\n");
401                     exit(1);
402                 }
403
404                 /* see http://permalink.gmane.org/gmane.comp.video.ffmpeg.devel/118362 */
405                 /* see http://ffmpeg-users.933282.n4.nabble.com/Documentation-for-sws-init-context-td2956723.html */
406
407                 av_set_int(img_convert_ctx, "srcw", c->width);
408                 av_set_int(img_convert_ctx, "srch", c->height);
409
410                 av_set_int(img_convert_ctx, "dstw", c->width);
411                 av_set_int(img_convert_ctx, "dsth", c->height);
412
413                 av_set_int(img_convert_ctx, "src_format", PIX_FMT_YUV420P);
414                 av_set_int(img_convert_ctx, "dst_format", c->pix_fmt);
415
416                 av_set_int(img_convert_ctx, "param0", 0);
417                 av_set_int(img_convert_ctx, "param1", 0);
418
419                 av_set_int(img_convert_ctx, "flags", sws_flags);
420
421                 sws_init_context(img_convert_ctx,NULL,NULL);
422                 #endif
423
424             }
425             sws_scale(img_convert_ctx, (const uint8_t* const *)tmp_picture->data,
426                           tmp_picture->linesize,
427                       0, c->height, picture_to_encode->data, picture_to_encode->linesize);
428         } else {
429
430         }
431
432
433     if (oc->oformat->flags & AVFMT_RAWPICTURE) {
434         /* raw video case. The API will change slightly in the near
435            futur for that */
436         AVPacket pkt;
437         av_init_packet(&pkt);
438
439         pkt.flags |= AV_PKT_FLAG_KEY;
440         pkt.stream_index= st->index;
441         pkt.data= (uint8_t *)picture_to_encode;
442         pkt.size= sizeof(AVPicture);
443
444         ret = av_interleaved_write_frame(oc, &pkt);
445     } else {
446         /* encode the image */
447         //printf("Here1 \n");
448         out_size = avcodec_encode_video(c, video_outbuf, video_outbuf_size, picture_to_encode);
449         /* if zero size, it means the image was buffered */
450         if (out_size > 0) {
451             AVPacket pkt;
452             av_init_packet(&pkt);
453
454             if (c->coded_frame->pts != AV_NOPTS_VALUE)
455                 pkt.pts= av_rescale_q(c->coded_frame->pts, c->time_base, st->time_base);
456             if(c->coded_frame->key_frame)
457                 pkt.flags |= AV_PKT_FLAG_KEY;
458             pkt.stream_index= st->index;
459             pkt.data= video_outbuf;
460             pkt.size= out_size;
461
462             /* write the compressed frame in the media file */
463             ret = av_interleaved_write_frame(oc, &pkt);
464         } else {
465             ret = 0;
466         }
467     }
468     if (ret != 0) {
469         fprintf(stderr, "Error while writing video frame\n");
470         exit(1);
471     }
472     frame_count++;
473 }
474
475  void close_video(AVFormatContext *oc, AVStream *st)
476 {
477     avcodec_close(st->codec);
478     //av_free(picture->data[0]);
479     av_free(picture_to_encode);
480     picture_to_encode = NULL;
481     if (tmp_picture) {
482         av_free(tmp_picture->data[0]);
483         av_free(tmp_picture);
484         tmp_picture=NULL;
485     }
486     av_free(video_outbuf);
487 }
488
489
490 /******************************************************************************************************************************************/
491 C_RESULT
492 video_stage_ffmpeg_recorder_handle (video_stage_ffmpeg_recorder_config_t * cfg, PIPELINE_MSG msg_id, void *callback, void *param)
493 {
494         //return (VP_SUCCESS);
495
496         printf("FFMPEG recorder message handler.\n");
497         switch (msg_id)
498         {
499                 case PIPELINE_MSG_START:
500                         {
501
502                                 if(cfg->startRec==VIDEO_RECORD_STOP)
503                                         cfg->startRec=VIDEO_RECORD_HOLD;
504                                 else
505                                         cfg->startRec=VIDEO_RECORD_STOP;
506                         }
507                         break;
508                 default:
509                         break;
510         }
511         return (VP_SUCCESS);
512 }
513
514
515 /******************************************************************************************************************************************/
516 C_RESULT video_stage_ffmpeg_recorder_open(video_stage_ffmpeg_recorder_config_t *cfg)
517 {
518         //return C_OK;
519
520         previous_frame.width = previous_frame.height = 0;
521         previous_frame.buffer = NULL;
522         previous_frame.timestamp_us = 0;
523         previous_frame.frame_number =0;
524
525         cfg->startRec=VIDEO_RECORD_STOP;
526
527         /* initialize libavcodec, and register all codecs and formats */
528             av_register_all();
529
530   return C_OK;
531 }
532
533
534 /******************************************************************************************************************************************/
535 C_RESULT video_stage_ffmpeg_recorder_transform(video_stage_ffmpeg_recorder_config_t *cfg, vp_api_io_data_t *in, vp_api_io_data_t *out)
536 {
537          time_t temptime;
538          struct timeval tv;
539          struct tm *atm;
540          long long int current_timestamp_us;
541          static long long int first_frame_timestamp_us=0;
542          static int frame_counter=0;
543          int i;
544          int frame_size;
545          static int flag_video_file_open=0;
546
547          vp_os_mutex_lock( &out->lock );
548          vp_api_picture_t* picture = (vp_api_picture_t *) in->buffers;
549
550         gettimeofday(&tv,NULL);
551
552          temptime = (time_t)tv.tv_sec;
553          atm = localtime(&temptime);  //atm = localtime(&tv.tv_sec);
554
555          current_timestamp_us = tv.tv_sec *1000000 + tv.tv_usec;
556
557
558   if( out->status == VP_API_STATUS_INIT )
559   {
560     out->numBuffers   = 1;
561     out->indexBuffer  = 0;
562     out->lineSize     = NULL;
563     //out->buffers      = (int8_t **) vp_os_malloc( sizeof(int8_t *) );
564   }
565
566   out->size     = in->size;
567   out->status   = in->status;
568   out->buffers  = in->buffers;
569
570   if( in->status == VP_API_STATUS_ENDED ) {
571     out->status = in->status;
572   }
573   else if(in->status == VP_API_STATUS_STILL_RUNNING) {
574     out->status = VP_API_STATUS_PROCESSING;
575   }
576   else {
577     out->status = in->status;
578   }
579
580
581
582         if(cfg->startRec==VIDEO_RECORD_HOLD)
583         {
584                 /* Create a new video file */
585
586                 sprintf(video_filename_ffmpeg, "%s/video_%04d%02d%02d_%02d%02d%02d_w%i_h%i.mp4",
587                                 VIDEO_FILE_DEFAULT_PATH,
588                                 atm->tm_year+1900, atm->tm_mon+1, atm->tm_mday,
589                                 atm->tm_hour, atm->tm_min, atm->tm_sec,
590                                 picture->width,
591                                 picture->height);
592
593                 create_video_file(video_filename_ffmpeg, picture->width,picture->height);
594                 flag_video_file_open=1;
595
596                 cfg->startRec=VIDEO_RECORD_START;
597
598                 first_frame_timestamp_us = current_timestamp_us;
599                 frame_counter=1;
600         }
601
602   if( out->size > 0 && out->status == VP_API_STATUS_PROCESSING && cfg->startRec==VIDEO_RECORD_START)
603   {
604           frame_size = ( previous_frame.width * previous_frame.height )*3/2;
605
606           /* Send the previous frame to FFMPEG */
607           if (previous_frame.buffer!=NULL)
608                 {
609                   /* Compute the number of frames to store to achieve 60 FPS
610                    * This should be computed using the timestamp of the first frame
611                    * to avoid error accumulation.
612                    */
613                         int current_frame_number = (current_timestamp_us - first_frame_timestamp_us) / 16666;
614                         int nb_frames_to_write = current_frame_number - previous_frame.frame_number;
615
616                         if (picture_to_encode!=NULL){
617                                 picture_to_encode->data[0] = picture_to_encode->base[0] = picture->y_buf;
618                                 picture_to_encode->data[1] = picture_to_encode->base[1] = picture->cb_buf;
619                                 picture_to_encode->data[2] = picture_to_encode->base[2] = picture->cr_buf;
620
621                                 picture_to_encode->linesize[0] = picture->width;
622                                 picture_to_encode->linesize[1] = picture->width/2;
623                                 picture_to_encode->linesize[2] = picture->width/2;
624                         }
625
626                         for (i=0;i<nb_frames_to_write;i++)
627                         {
628                                 //printf("Storing %i frames\n",nb_frames_to_write);
629                                 write_video_frame(oc, video_st);
630                         }
631
632                         /* Pass infos to next iteration */
633                         previous_frame.frame_number = current_frame_number;
634                 }
635
636           /* Create a buffer to hold the current frame */
637                 //if (0)
638                 {
639           if (previous_frame.buffer!=NULL && (previous_frame.width!=picture->width || previous_frame.height!=picture->height))
640                 {
641                         vp_os_free(previous_frame.buffer);
642                         previous_frame.buffer=NULL;
643                 }
644                 if (previous_frame.buffer==NULL)
645                 {
646                         previous_frame.width = picture->width;
647                         previous_frame.height = picture->height;
648                         frame_size = ( previous_frame.width * previous_frame.height )*3/2;
649                         printf("Allocating previous frame.\n");
650                         previous_frame.buffer=vp_os_malloc( frame_size );
651                 }
652
653         /* Copy the current frame in a buffer so it can be encoded at next stage call */
654                 if (previous_frame.buffer!=NULL)
655                 {
656                         char * dest = previous_frame.buffer;
657                         int size = picture->width*picture->height;
658                         vp_os_memcpy(dest,picture->y_buf,size);
659
660                         dest+=size;
661                         size /= 4;
662                         vp_os_memcpy(dest,picture->cb_buf,size);
663
664                         dest+=size;
665                         vp_os_memcpy(dest,picture->cr_buf,size);
666                 }
667                 }
668   }
669
670
671   else
672         {
673                 if(cfg->startRec==VIDEO_RECORD_STOP && flag_video_file_open)
674                 {
675                         close_video_file();
676                         flag_video_file_open=0;
677                 }
678         }
679
680   vp_os_mutex_unlock( &out->lock );
681
682   return C_OK;
683 }
684
685
686
687 /******************************************************************************************************************************************/
688
689
690 C_RESULT video_stage_ffmpeg_recorder_close(video_stage_ffmpeg_recorder_config_t *cfg)
691 {
692   if( cfg->fp != NULL )
693     fclose( cfg->fp );
694
695   return C_OK;
696 }
697
698
699
700 #endif