1 /*
2 * GPAC - Multimedia Framework C SDK
3 *
4 * Authors: Arash Shafiei
5 * Copyright (c) Telecom ParisTech 2000-2013
6 * All rights reserved
7 *
8 * This file is part of GPAC / dashcast
9 *
10 * GPAC is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; either version 2, or (at your option)
13 * any later version.
14 *
15 * GPAC is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; see the file COPYING. If not, write to
22 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 */
25
26 #include "audio_muxer.h"
27 #include "libavformat/avio.h"
28
29 #ifndef GPAC_DISABLE_ISOM
30
dc_gpac_audio_moov_create(AudioOutputFile * audio_output_file,char * filename)31 int dc_gpac_audio_moov_create(AudioOutputFile *audio_output_file, char *filename)
32 {
33 GF_Err ret;
34 u32 di, track;
35 u8 bpsample;
36 GF_ESD *esd;
37 #ifndef GPAC_DISABLE_AV_PARSERS
38 GF_M4ADecSpecInfo acfg;
39 #endif
40 AVCodecContext *audio_codec_ctx = audio_output_file->codec_ctx;
41
42 audio_output_file->isof = gf_isom_open(filename, GF_ISOM_OPEN_WRITE, NULL);
43 if (!audio_output_file->isof) {
44 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Cannot open iso file %s\n", filename));
45 return -1;
46 }
47
48 esd = gf_odf_desc_esd_new(2);
49 if (!esd) {
50 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Cannot create GF_ESD\n"));
51 return -1;
52 }
53
54 esd->decoderConfig = (GF_DecoderConfig *) gf_odf_desc_new(GF_ODF_DCD_TAG);
55 esd->slConfig = (GF_SLConfig *) gf_odf_desc_new(GF_ODF_SLC_TAG);
56 esd->decoderConfig->streamType = GF_STREAM_AUDIO;
57 if (!strcmp(audio_output_file->codec_ctx->codec->name, "aac")) { //TODO: find an automatic table
58 esd->decoderConfig->objectTypeIndication = GPAC_OTI_AUDIO_AAC_MPEG4;
59 esd->decoderConfig->bufferSizeDB = 20;
60 esd->slConfig->timestampResolution = audio_codec_ctx->sample_rate;
61 esd->decoderConfig->decoderSpecificInfo = (GF_DefaultDescriptor *) gf_odf_desc_new(GF_ODF_DSI_TAG);
62 esd->ESID = 1;
63
64 #ifndef GPAC_DISABLE_AV_PARSERS
65 memset(&acfg, 0, sizeof(GF_M4ADecSpecInfo));
66 acfg.base_object_type = GF_M4A_AAC_LC;
67 acfg.base_sr = audio_codec_ctx->sample_rate;
68 acfg.nb_chan = audio_codec_ctx->channels;
69 acfg.sbr_object_type = 0;
70 acfg.audioPL = gf_m4a_get_profile(&acfg);
71
72 ret = gf_m4a_write_config(&acfg, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength);
73 assert(ret == GF_OK);
74 #endif
75 } else {
76 if (strcmp(audio_output_file->codec_ctx->codec->name, "mp2")) {
77 GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("Unlisted codec, setting GPAC_OTI_AUDIO_MPEG1 descriptor.\n"));
78 }
79 esd->decoderConfig->objectTypeIndication = GPAC_OTI_AUDIO_MPEG1;
80 esd->decoderConfig->bufferSizeDB = 20;
81 esd->slConfig->timestampResolution = audio_codec_ctx->sample_rate;
82 esd->decoderConfig->decoderSpecificInfo = (GF_DefaultDescriptor *) gf_odf_desc_new(GF_ODF_DSI_TAG);
83 esd->ESID = 1;
84
85 #ifndef GPAC_DISABLE_AV_PARSERS
86 memset(&acfg, 0, sizeof(GF_M4ADecSpecInfo));
87 acfg.base_object_type = GF_M4A_LAYER2;
88 acfg.base_sr = audio_codec_ctx->sample_rate;
89 acfg.nb_chan = audio_codec_ctx->channels;
90 acfg.sbr_object_type = 0;
91 acfg.audioPL = gf_m4a_get_profile(&acfg);
92
93 ret = gf_m4a_write_config(&acfg, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength);
94 assert(ret == GF_OK);
95 #endif
96 }
97
98 //gf_isom_store_movie_config(video_output_file->isof, 0);
99 track = gf_isom_new_track(audio_output_file->isof, esd->ESID, GF_ISOM_MEDIA_AUDIO, audio_codec_ctx->sample_rate);
100 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("TimeScale: %d \n", audio_codec_ctx->time_base.den));
101 if (!track) {
102 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Cannot create new track\n"));
103 return -1;
104 }
105
106 ret = gf_isom_set_track_enabled(audio_output_file->isof, track, 1);
107 if (ret != GF_OK) {
108 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_set_track_enabled\n", gf_error_to_string(ret)));
109 return -1;
110 }
111
112 // if (!esd->ESID) esd->ESID = gf_isom_get_track_id(audio_output_file->isof, track);
113
114 ret = gf_isom_new_mpeg4_description(audio_output_file->isof, track, esd, NULL, NULL, &di);
115 if (ret != GF_OK) {
116 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_new_mpeg4_description\n", gf_error_to_string(ret)));
117 return -1;
118 }
119
120 gf_odf_desc_del((GF_Descriptor *) esd);
121 esd = NULL;
122
123 bpsample = av_get_bytes_per_sample(audio_output_file->codec_ctx->sample_fmt) * 8;
124
125 ret = gf_isom_set_audio_info(audio_output_file->isof, track, di, audio_codec_ctx->sample_rate, audio_output_file->codec_ctx->channels, bpsample, GF_IMPORT_AUDIO_SAMPLE_ENTRY_v0_BS);
126 if (ret != GF_OK) {
127 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_set_audio_info\n", gf_error_to_string(ret)));
128 return -1;
129 }
130
131 #ifndef GPAC_DISABLE_AV_PARSERS
132 ret = gf_isom_set_pl_indication(audio_output_file->isof, GF_ISOM_PL_AUDIO, acfg.audioPL);
133 if (ret != GF_OK) {
134 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_set_pl_indication\n", gf_error_to_string(ret)));
135 return -1;
136 }
137 #endif
138
139 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("time scale: %d sample dur: %d \n", audio_codec_ctx->time_base.den, audio_output_file->codec_ctx->frame_size));
140
141 ret = gf_isom_setup_track_fragment(audio_output_file->isof, track, 1, audio_output_file->codec_ctx->frame_size, 0, 0, 0, 0, 0);
142 if (ret != GF_OK) {
143 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_setup_track_fragment\n", gf_error_to_string(ret)));
144 return -1;
145 }
146
147 //gf_isom_add_track_to_root_od(video_output_file->isof,1);
148
149 ret = gf_isom_finalize_for_fragment(audio_output_file->isof, 1);
150 if (ret != GF_OK) {
151 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_finalize_for_fragment\n", gf_error_to_string(ret)));
152 return -1;
153 }
154
155 ret = gf_media_get_rfc_6381_codec_name(audio_output_file->isof, track, audio_output_file->audio_data_conf->codec6381, GF_FALSE, GF_FALSE);
156 if (ret != GF_OK) return -1;
157 return 0;
158 }
159
dc_gpac_audio_isom_open_seg(AudioOutputFile * audio_output_file,char * filename)160 int dc_gpac_audio_isom_open_seg(AudioOutputFile *audio_output_file, char *filename)
161 {
162 GF_Err ret;
163 ret = gf_isom_start_segment(audio_output_file->isof, filename, GF_TRUE);
164 if (ret != GF_OK) {
165 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_start_segment\n", gf_error_to_string(ret)));
166 return -1;
167 }
168
169 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DashCast] Audio segment %s started at "LLU"\n", filename, gf_net_get_utc() ));
170
171 audio_output_file->dts = 0;
172
173 return 0;
174 }
175
dc_gpac_audio_isom_write(AudioOutputFile * audio_output_file)176 int dc_gpac_audio_isom_write(AudioOutputFile *audio_output_file)
177 {
178 GF_Err ret;
179 audio_output_file->sample->data = (char *) audio_output_file->packet.data;
180 audio_output_file->sample->dataLength = audio_output_file->packet.size;
181
182 audio_output_file->sample->DTS = audio_output_file->dts; //audio_output_file->aframe->pts;
183 audio_output_file->sample->IsRAP = RAP; //audio_output_file->aframe->key_frame;//audio_codec_ctx->coded_frame->key_frame;
184
185 ret = gf_isom_fragment_add_sample(audio_output_file->isof, 1, audio_output_file->sample, 1, audio_output_file->codec_ctx->frame_size, 0, 0, 0);
186 audio_output_file->dts += audio_output_file->codec_ctx->frame_size;
187 if (ret != GF_OK) {
188 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_fragment_add_sample\n", gf_error_to_string(ret)));
189 return -1;
190 }
191 return 0;
192 }
193
dc_gpac_audio_isom_close_seg(AudioOutputFile * audio_output_file)194 int dc_gpac_audio_isom_close_seg(AudioOutputFile *audio_output_file)
195 {
196 u64 seg_size;
197 GF_Err ret;
198 ret = gf_isom_close_segment(audio_output_file->isof, 0, 0, 0, 0, 0, 0, 0, GF_TRUE, GF_FALSE, audio_output_file->seg_marker, NULL, NULL, &seg_size);
199 if (ret != GF_OK) {
200 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_close_segment\n", gf_error_to_string(ret)));
201 return -1;
202 }
203 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DashCast] Audio segment %s closed at "LLU" - size "LLU" bytes\n", gf_isom_get_segment_name(audio_output_file->isof), gf_net_get_utc(), seg_size ));
204
205 //audio_output_file->acc_samples = 0;
206
207 return 0;
208 }
209
dc_gpac_audio_isom_close(AudioOutputFile * audio_output_file)210 int dc_gpac_audio_isom_close(AudioOutputFile *audio_output_file)
211 {
212 GF_Err ret;
213 ret = gf_isom_close(audio_output_file->isof);
214 if (ret != GF_OK) {
215 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("%s: gf_isom_close\n", gf_error_to_string(ret)));
216 return -1;
217 }
218
219 //audio_output_file->acc_samples = 0;
220
221 return 0;
222 }
223
224 #endif
225
226
227
dc_ffmpeg_audio_muxer_open(AudioOutputFile * audio_output_file,char * filename)228 int dc_ffmpeg_audio_muxer_open(AudioOutputFile *audio_output_file, char *filename)
229 {
230 AVStream *audio_stream;
231 AVOutputFormat *output_fmt;
232 AVDictionary *opts = NULL;
233
234 AVCodecContext *audio_codec_ctx = audio_output_file->codec_ctx;
235 audio_output_file->av_fmt_ctx = NULL;
236
237 // strcpy(audio_output_file->filename, audio_data_conf->filename);
238 // audio_output_file->abr = audio_data_conf->bitrate;
239 // audio_output_file->asr = audio_data_conf->samplerate;
240 // audio_output_file->ach = audio_data_conf->channels;
241 // strcpy(audio_output_file->codec, audio_data_conf->codec);
242
243 /* Find output format */
244 output_fmt = av_guess_format(NULL, filename, NULL);
245 if (!output_fmt) {
246 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Cannot find suitable output format\n"));
247 return -1;
248 }
249
250 audio_output_file->av_fmt_ctx = avformat_alloc_context();
251 if (!audio_output_file->av_fmt_ctx) {
252 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Cannot allocate memory for pOutVideoFormatCtx\n"));
253 return -1;
254 }
255
256 audio_output_file->av_fmt_ctx->oformat = output_fmt;
257 strcpy(audio_output_file->av_fmt_ctx->filename, filename);
258
259 /* Open the output file */
260 if (!(output_fmt->flags & AVFMT_NOFILE)) {
261 if (avio_open(&audio_output_file->av_fmt_ctx->pb, filename, URL_WRONLY) < 0) {
262 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Cannot not open '%s'\n", filename));
263 return -1;
264 }
265 }
266
267 audio_stream = avformat_new_stream(audio_output_file->av_fmt_ctx, audio_output_file->codec);
268 if (!audio_stream) {
269 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Cannot create output video stream\n"));
270 return -1;
271 }
272
273 audio_stream->codec->codec_id = audio_output_file->codec->id;
274 audio_stream->codec->codec_type = AVMEDIA_TYPE_AUDIO;
275 audio_stream->codec->bit_rate = audio_codec_ctx->bit_rate;//audio_output_file->audio_data_conf->bitrate;
276 audio_stream->codec->sample_rate = audio_codec_ctx->sample_rate;//audio_output_file->audio_data_conf->samplerate;
277 audio_stream->codec->channels = audio_codec_ctx->channels;//audio_output_file->audio_data_conf->channels;
278 assert(audio_codec_ctx->codec->sample_fmts);
279 audio_stream->codec->sample_fmt = audio_codec_ctx->codec->sample_fmts[0];
280
281 // if (audio_output_file->av_fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
282 // audio_output_file->codec_ctx->flags |= CODEC_FLAG_GLOBAL_HEADER;
283
284 //video_stream->codec = video_output_file->codec_ctx;
285
286 /* open the video codec */
287 av_dict_set(&opts, "strict", "experimental", 0);
288 if (avcodec_open2(audio_stream->codec, audio_output_file->codec, &opts) < 0) {
289 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Cannot open output video codec\n"));
290 av_dict_free(&opts);
291 return -1;
292 }
293 av_dict_free(&opts);
294
295 return avformat_write_header(audio_output_file->av_fmt_ctx, NULL);
296
297 }
298
dc_ffmpeg_audio_muxer_write(AudioOutputFile * audio_output_file)299 int dc_ffmpeg_audio_muxer_write(AudioOutputFile *audio_output_file)
300 {
301 AVStream *audio_stream = audio_output_file->av_fmt_ctx->streams[audio_output_file->astream_idx];
302 AVCodecContext *audio_codec_ctx = audio_stream->codec;
303
304 audio_output_file->packet.stream_index = audio_stream->index;
305
306 if (audio_output_file->packet.pts != AV_NOPTS_VALUE)
307 audio_output_file->packet.pts = av_rescale_q(audio_output_file->packet.pts, audio_codec_ctx->time_base, audio_stream->time_base);
308
309 if (audio_output_file->packet.duration > 0)
310 audio_output_file->packet.duration = (int)av_rescale_q(audio_output_file->packet.duration, audio_codec_ctx->time_base, audio_stream->time_base);
311 /*
312 * if (pkt.pts != AV_NOPTS_VALUE)
313 * pkt.pts = av_rescale_q(pkt.pts, audioEncCtx->time_base, audioStream->time_base);
314 */
315
316 audio_output_file->packet.flags |= AV_PKT_FLAG_KEY;
317
318 if (av_interleaved_write_frame(audio_output_file->av_fmt_ctx, &audio_output_file->packet) != 0) {
319 GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Writing frame is not successful\n"));
320 av_free_packet(&audio_output_file->packet);
321 return -1;
322 }
323
324 av_free_packet(&audio_output_file->packet);
325
326 return 0;
327 }
328
dc_ffmpeg_audio_muxer_close(AudioOutputFile * audio_output_file)329 int dc_ffmpeg_audio_muxer_close(AudioOutputFile *audio_output_file)
330 {
331 u32 i;
332
333 av_write_trailer(audio_output_file->av_fmt_ctx);
334 avio_close(audio_output_file->av_fmt_ctx->pb);
335
336 // free the streams
337 for (i = 0; i < audio_output_file->av_fmt_ctx->nb_streams; i++) {
338 avcodec_close(audio_output_file->av_fmt_ctx->streams[i]->codec);
339 av_freep(&audio_output_file->av_fmt_ctx->streams[i]->info);
340 }
341
342 //video_output_file->av_fmt_ctx->streams[video_output_file->vstream_idx]->codec = NULL;
343 avformat_free_context(audio_output_file->av_fmt_ctx);
344
345 //audio_output_file->acc_samples = 0;
346
347 return 0;
348
349 }
350
dc_audio_muxer_init(AudioOutputFile * audio_output_file,AudioDataConf * audio_data_conf,AudioMuxerType muxer_type,int frame_per_seg,int frame_per_frag,u32 seg_marker)351 int dc_audio_muxer_init(AudioOutputFile *audio_output_file, AudioDataConf *audio_data_conf, AudioMuxerType muxer_type, int frame_per_seg, int frame_per_frag, u32 seg_marker)
352 {
353 char name[GF_MAX_PATH];
354 snprintf(name, sizeof(name), "audio encoder %s", audio_data_conf->filename);
355 dc_consumer_init(&audio_output_file->consumer, AUDIO_CB_SIZE, name);
356
357 #ifndef GPAC_DISABLE_ISOM
358 audio_output_file->sample = gf_isom_sample_new();
359 audio_output_file->isof = NULL;
360 #endif
361
362 audio_output_file->muxer_type = muxer_type;
363 audio_output_file->frame_per_seg = frame_per_seg;
364 audio_output_file->frame_per_frag = frame_per_frag;
365 audio_output_file->seg_marker = seg_marker;
366 return 0;
367 }
368
dc_audio_muxer_free(AudioOutputFile * audio_output_file)369 void dc_audio_muxer_free(AudioOutputFile *audio_output_file)
370 {
371 #ifndef GPAC_DISABLE_ISOM
372 if (audio_output_file->isof != NULL) {
373 gf_isom_close(audio_output_file->isof);
374 }
375 //gf_isom_sample_del(&audio_output_file->sample);
376 #endif
377 }
378
dc_audio_muxer_open(AudioOutputFile * audio_output_file,char * directory,char * id_name,int seg)379 GF_Err dc_audio_muxer_open(AudioOutputFile *audio_output_file, char *directory, char *id_name, int seg)
380 {
381 GF_Err ret = GF_NOT_SUPPORTED;
382 char name[GF_MAX_PATH];
383
384 switch (audio_output_file->muxer_type) {
385 case FFMPEG_AUDIO_MUXER:
386 snprintf(name, sizeof(name), "%s/%s_%d_ffmpeg.mp4", directory, id_name, seg);
387 return dc_ffmpeg_audio_muxer_open(audio_output_file, name);
388 #ifndef GPAC_DISABLE_ISOM
389 case GPAC_AUDIO_MUXER:
390 snprintf(name, sizeof(name), "%s/%s_%d_gpac.mp4", directory, id_name, seg);
391 dc_gpac_audio_moov_create(audio_output_file, name);
392 return dc_gpac_audio_isom_open_seg(audio_output_file, NULL);
393 case GPAC_INIT_AUDIO_MUXER:
394 if (seg == 1) {
395 snprintf(name, sizeof(name), "%s/%s_init_gpac.mp4", directory, id_name);
396 dc_gpac_audio_moov_create(audio_output_file, name);
397 audio_output_file->first_dts = 0;
398 }
399 snprintf(name, sizeof(name), "%s/%s_%d_gpac.m4s", directory, id_name, seg);
400 ret = dc_gpac_audio_isom_open_seg(audio_output_file, name);
401 return ret;
402 #endif
403 default:
404 ret = GF_BAD_PARAM;
405 break;
406 }
407
408 return ret;
409 }
410
dc_audio_muxer_write(AudioOutputFile * audio_output_file,int frame_nb,Bool insert_ntp)411 int dc_audio_muxer_write(AudioOutputFile *audio_output_file, int frame_nb, Bool insert_ntp)
412 {
413 switch (audio_output_file->muxer_type) {
414 case FFMPEG_AUDIO_MUXER:
415 return dc_ffmpeg_audio_muxer_write(audio_output_file);
416 #ifndef GPAC_DISABLE_ISOM
417 case GPAC_AUDIO_MUXER:
418 case GPAC_INIT_AUDIO_MUXER:
419 if (frame_nb % audio_output_file->frame_per_frag == 0) {
420 gf_isom_start_fragment(audio_output_file->isof, 1);
421
422 if (insert_ntp) {
423 gf_isom_set_fragment_reference_time(audio_output_file->isof, 1, audio_output_file->frame_ntp, audio_output_file->first_dts * audio_output_file->codec_ctx->frame_size);
424 }
425
426 gf_isom_set_traf_base_media_decode_time(audio_output_file->isof, 1, audio_output_file->first_dts * audio_output_file->codec_ctx->frame_size);
427 audio_output_file->first_dts += audio_output_file->frame_per_frag;
428 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DashCast] Audio start fragment first DTS %u at "LLU"\n", audio_output_file->first_dts, gf_net_get_utc() ));
429 }
430 dc_gpac_audio_isom_write(audio_output_file);
431 if (frame_nb % audio_output_file->frame_per_frag == audio_output_file->frame_per_frag - 1) {
432 gf_isom_flush_fragments(audio_output_file->isof, 1);
433 GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DashCast] Audio flush fragment first DTS %u at "LLU"\n", audio_output_file->first_dts, gf_net_get_utc() ));
434 }
435 //TODO - do same as video, flush based on time in case of losses
436 if (frame_nb + 1 == audio_output_file->frame_per_seg) {
437 return 1;
438 }
439
440 return 0;
441 #endif
442
443 default:
444 return GF_BAD_PARAM;
445 }
446 return GF_BAD_PARAM;
447 }
448
dc_audio_muxer_close(AudioOutputFile * audio_output_file)449 int dc_audio_muxer_close(AudioOutputFile *audio_output_file)
450 {
451 switch (audio_output_file->muxer_type) {
452 case FFMPEG_AUDIO_MUXER:
453 return dc_ffmpeg_audio_muxer_close(audio_output_file);
454 #ifndef GPAC_DISABLE_ISOM
455 case GPAC_AUDIO_MUXER:
456 dc_gpac_audio_isom_close_seg(audio_output_file);
457 return dc_gpac_audio_isom_close(audio_output_file);
458 case GPAC_INIT_AUDIO_MUXER:
459 return dc_gpac_audio_isom_close_seg(audio_output_file);
460 #endif
461 default:
462 return GF_BAD_PARAM;
463 }
464
465 return GF_BAD_PARAM;
466 }
467