1/*
2** Copyright 2010, The Android Open-Source Project
3** Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved.
4**
5** Licensed under the Apache License, Version 2.0 (the "License");
6** you may not use this file except in compliance with the License.
7** You may obtain a copy of the License at
8**
9**     http://www.apache.org/licenses/LICENSE-2.0
10**
11** Unless required by applicable law or agreed to in writing, software
12** distributed under the License is distributed on an "AS IS" BASIS,
13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14** See the License for the specific language governing permissions and
15** limitations under the License.
16*/
17
18#include <stdio.h>
19#include <stdlib.h>
20#include <fcntl.h>
21#include <unistd.h>
22#include <stdint.h>
23#include <string.h>
24#include <errno.h>
25#include <sys/poll.h>
26#include <sys/ioctl.h>
27#include <getopt.h>
28
29#include <sound/asound.h>
30#include "alsa_audio.h"
31
32#ifndef ANDROID
33#define strlcat g_strlcat
34#define strlcpy g_strlcpy
35#endif
36
37#define ID_RIFF 0x46464952
38#define ID_WAVE 0x45564157
39#define ID_FMT  0x20746d66
40#define ID_DATA 0x61746164
41
42#define FORMAT_PCM 1
43#define LOG_NDEBUG 1
44static pcm_flag = 1;
45static debug = 0;
46static uint32_t play_max_sz = 2147483648LL;
47static int format = SNDRV_PCM_FORMAT_S16_LE;
48static int period = 0;
49static int compressed = 0;
50static char *compr_codec;
51static int piped = 0;
52
53static struct option long_options[] =
54{
55    {"pcm", 0, 0, 'P'},
56    {"debug", 0, 0, 'V'},
57    {"Mmap", 0, 0, 'M'},
58    {"HW", 1, 0, 'D'},
59    {"Rate", 1, 0, 'R'},
60    {"channel", 1, 0, 'C'},
61    {"format", 1, 0, 'F'},
62    {"period", 1, 0, 'B'},
63    {"compressed", 0, 0, 'T'},
64    {0, 0, 0, 0}
65};
66
67struct wav_header {
68    uint32_t riff_id;
69    uint32_t riff_sz;
70    uint32_t riff_fmt;
71    uint32_t fmt_id;
72    uint32_t fmt_sz;
73    uint16_t audio_format;
74    uint16_t num_channels;
75    uint32_t sample_rate;
76    uint32_t byte_rate;       /* sample_rate * num_channels * bps / 8 */
77    uint16_t block_align;     /* num_channels * bps / 8 */
78    uint16_t bits_per_sample;
79    uint32_t data_id;
80    uint32_t data_sz;
81};
82
83static int set_params(struct pcm *pcm)
84{
85     struct snd_pcm_hw_params *params;
86     struct snd_pcm_sw_params *sparams;
87
88     unsigned long periodSize, bufferSize, reqBuffSize;
89     unsigned int periodTime, bufferTime;
90     unsigned int requestedRate = pcm->rate;
91     int channels = (pcm->flags & PCM_MONO) ? 1 : ((pcm->flags & PCM_5POINT1)? 6 : 2 );
92
93     params = (struct snd_pcm_hw_params*) calloc(1, sizeof(struct snd_pcm_hw_params));
94     if (!params) {
95          fprintf(stderr, "Aplay:Failed to allocate ALSA hardware parameters!");
96          return -ENOMEM;
97     }
98
99     param_init(params);
100
101     param_set_mask(params, SNDRV_PCM_HW_PARAM_ACCESS,
102                    (pcm->flags & PCM_MMAP)? SNDRV_PCM_ACCESS_MMAP_INTERLEAVED : SNDRV_PCM_ACCESS_RW_INTERLEAVED);
103     param_set_mask(params, SNDRV_PCM_HW_PARAM_FORMAT, pcm->format);
104     param_set_mask(params, SNDRV_PCM_HW_PARAM_SUBFORMAT,
105                    SNDRV_PCM_SUBFORMAT_STD);
106     if (period)
107         param_set_min(params, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, period);
108     else
109         param_set_min(params, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 10);
110     param_set_int(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, 16);
111     param_set_int(params, SNDRV_PCM_HW_PARAM_FRAME_BITS,
112                    pcm->channels * 16);
113     param_set_int(params, SNDRV_PCM_HW_PARAM_CHANNELS,
114                    pcm->channels);
115     param_set_int(params, SNDRV_PCM_HW_PARAM_RATE, pcm->rate);
116     param_set_hw_refine(pcm, params);
117
118     if (param_set_hw_params(pcm, params)) {
119         fprintf(stderr, "Aplay:cannot set hw params\n");
120         return -errno;
121     }
122     if (debug)
123         param_dump(params);
124
125     pcm->buffer_size = pcm_buffer_size(params);
126     pcm->period_size = pcm_period_size(params);
127     pcm->period_cnt = pcm->buffer_size/pcm->period_size;
128     if (debug) {
129        fprintf (stderr,"period_cnt = %d\n", pcm->period_cnt);
130        fprintf (stderr,"period_size = %d\n", pcm->period_size);
131        fprintf (stderr,"buffer_size = %d\n", pcm->buffer_size);
132     }
133     sparams = (struct snd_pcm_sw_params*) calloc(1, sizeof(struct snd_pcm_sw_params));
134     if (!sparams) {
135         fprintf(stderr, "Aplay:Failed to allocate ALSA software parameters!\n");
136         return -ENOMEM;
137     }
138     // Get the current software parameters
139    sparams->tstamp_mode = SNDRV_PCM_TSTAMP_NONE;
140    sparams->period_step = 1;
141
142    sparams->avail_min = pcm->period_size/(channels * 2) ;
143    sparams->start_threshold =  pcm->period_size/(channels * 2) ;
144    sparams->stop_threshold =  pcm->buffer_size ;
145    sparams->xfer_align =  pcm->period_size/(channels * 2) ; /* needed for old kernels */
146
147    sparams->silence_size = 0;
148    sparams->silence_threshold = 0;
149
150    if (param_set_sw_params(pcm, sparams)) {
151        fprintf(stderr, "Aplay:cannot set sw params");
152        return -errno;
153    }
154    if (debug) {
155       fprintf (stderr,"sparams->avail_min= %lu\n", sparams->avail_min);
156       fprintf (stderr," sparams->start_threshold= %lu\n", sparams->start_threshold);
157       fprintf (stderr," sparams->stop_threshold= %lu\n", sparams->stop_threshold);
158       fprintf (stderr," sparams->xfer_align= %lu\n", sparams->xfer_align);
159       fprintf (stderr," sparams->boundary= %lu\n", sparams->boundary);
160    }
161    return 0;
162}
163
164static int play_file(unsigned rate, unsigned channels, int fd,
165              unsigned flags, const char *device, unsigned data_sz)
166{
167    struct pcm *pcm;
168    struct mixer *mixer;
169    struct pcm_ctl *ctl = NULL;
170    unsigned bufsize;
171    char *data;
172    long avail;
173    long frames;
174    int nfds = 1;
175    struct snd_xferi x;
176    unsigned offset = 0;
177    int err;
178    static int start = 0;
179    struct pollfd pfd[1];
180    int remainingData = 0;
181
182    flags |= PCM_OUT;
183
184    if (channels == 1)
185        flags |= PCM_MONO;
186    else if (channels == 6)
187	flags |= PCM_5POINT1;
188    else
189        flags |= PCM_STEREO;
190
191    if (debug)
192        flags |= DEBUG_ON;
193    else
194        flags |= DEBUG_OFF;
195
196    pcm = pcm_open(flags, device);
197    if (pcm < 0)
198        return pcm;
199
200    if (!pcm_ready(pcm)) {
201        pcm_close(pcm);
202        return -EBADFD;
203    }
204
205#ifdef QCOM_COMPRESSED_AUDIO_ENABLED
206    if (compressed) {
207       struct snd_compr_caps compr_cap;
208       struct snd_compr_params compr_params;
209       if (ioctl(pcm->fd, SNDRV_COMPRESS_GET_CAPS, &compr_cap)) {
210          fprintf(stderr, "Aplay: SNDRV_COMPRESS_GET_CAPS, failed Error no %d \n", errno);
211          pcm_close(pcm);
212          return -errno;
213       }
214       if (!period)
215           period = compr_cap.min_fragment_size;
216           switch (get_compressed_format(compr_codec)) {
217           case FORMAT_MP3:
218               compr_params.codec.id = compr_cap.codecs[FORMAT_MP3];
219               break;
220           case FORMAT_AC3_PASS_THROUGH:
221               compr_params.codec.id = compr_cap.codecs[FORMAT_AC3_PASS_THROUGH];
222               printf("codec -d = %x\n", compr_params.codec.id);
223               break;
224           default:
225               break;
226           }
227       if (ioctl(pcm->fd, SNDRV_COMPRESS_SET_PARAMS, &compr_params)) {
228          fprintf(stderr, "Aplay: SNDRV_COMPRESS_SET_PARAMS,failed Error no %d \n", errno);
229          pcm_close(pcm);
230          return -errno;
231       }
232    }
233#endif
234    pcm->channels = channels;
235    pcm->rate = rate;
236    pcm->flags = flags;
237    pcm->format = format;
238    if (set_params(pcm)) {
239        fprintf(stderr, "Aplay:params setting failed\n");
240        pcm_close(pcm);
241        return -errno;
242    }
243
244    if (!pcm_flag) {
245       if (pcm_prepare(pcm)) {
246          fprintf(stderr, "Aplay:Failed in pcm_prepare\n");
247          pcm_close(pcm);
248          return -errno;
249       }
250       if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START)) {
251          fprintf(stderr, "Aplay: Hostless IOCTL_START Error no %d \n", errno);
252          pcm_close(pcm);
253          return -errno;
254       }
255        while(1);
256    }
257
258    remainingData = data_sz;
259
260    if (flags & PCM_MMAP) {
261        u_int8_t *dst_addr = NULL;
262        struct snd_pcm_sync_ptr *sync_ptr1 = pcm->sync_ptr;
263        if (mmap_buffer(pcm)) {
264             fprintf(stderr, "Aplay:params setting failed\n");
265             pcm_close(pcm);
266             return -errno;
267        }
268        if (pcm_prepare(pcm)) {
269          fprintf(stderr, "Aplay:Failed in pcm_prepare\n");
270          pcm_close(pcm);
271          return -errno;
272        }
273
274        bufsize = pcm->period_size;
275        if (debug)
276          fprintf(stderr, "Aplay:bufsize = %d\n", bufsize);
277
278        pfd[0].fd = pcm->timer_fd;
279        pfd[0].events = POLLIN;
280
281        frames = (pcm->flags & PCM_MONO) ? (bufsize / 2) : (bufsize / 4);
282        for (;;) {
283             if (!pcm->running) {
284                  if (pcm_prepare(pcm)) {
285                      fprintf(stderr, "Aplay:Failed in pcm_prepare\n");
286                      pcm_close(pcm);
287                      return -errno;
288                  }
289                  pcm->running = 1;
290                  start = 0;
291             }
292             /* Sync the current Application pointer from the kernel */
293             pcm->sync_ptr->flags = SNDRV_PCM_SYNC_PTR_APPL | SNDRV_PCM_SYNC_PTR_AVAIL_MIN;//SNDRV_PCM_SYNC_PTR_HWSYNC;
294             err = sync_ptr(pcm);
295             if (err == EPIPE) {
296                 fprintf(stderr, "Aplay:Failed in sync_ptr \n");
297                 /* we failed to make our window -- try to restart */
298                 pcm->underruns++;
299                 pcm->running = 0;
300                 continue;
301             }
302             /*
303              * Check for the available buffer in driver. If available buffer is
304              * less than avail_min we need to wait
305              */
306             avail = pcm_avail(pcm);
307             if (avail < 0) {
308                 fprintf(stderr, "Aplay:Failed in pcm_avail\n");
309                 pcm_close(pcm);
310                 return avail;
311             }
312             if (avail < pcm->sw_p->avail_min) {
313                 poll(pfd, nfds, TIMEOUT_INFINITE);
314                 continue;
315             }
316             /*
317              * Now that we have buffer size greater than avail_min available to
318              * to be written we need to calcutate the buffer offset where we can
319              * start writting.
320              */
321             dst_addr = dst_address(pcm);
322
323             if (debug) {
324                 fprintf(stderr, "dst_addr = 0x%08x\n", dst_addr);
325                 fprintf(stderr, "Aplay:avail = %d frames = %d\n",avail, frames);
326                 fprintf(stderr, "Aplay:sync_ptr->s.status.hw_ptr %ld  pcm->buffer_size %d  sync_ptr->c.control.appl_ptr %ld\n",
327                            pcm->sync_ptr->s.status.hw_ptr,
328                            pcm->buffer_size,
329                            pcm->sync_ptr->c.control.appl_ptr);
330             }
331
332             /*
333              * Read from the file to the destination buffer in kernel mmaped buffer
334              * This reduces a extra copy of intermediate buffer.
335              */
336             memset(dst_addr, 0x0, bufsize);
337
338             if (data_sz && !piped) {
339                 if (remainingData < bufsize) {
340                     bufsize = remainingData;
341                     frames = (pcm->flags & PCM_MONO) ? (remainingData / 2) : (remainingData / 4);
342                 }
343             }
344
345             err = read(fd, dst_addr , bufsize);
346             if (debug)
347                 fprintf(stderr, "read %d bytes from file\n", err);
348             if (err <= 0)
349                 break;
350
351             if (data_sz && !piped) {
352                 remainingData -= bufsize;
353                 if (remainingData <= 0)
354                     break;
355             }
356
357             /*
358              * Increment the application pointer with data written to kernel.
359              * Update kernel with the new sync pointer.
360              */
361             pcm->sync_ptr->c.control.appl_ptr += frames;
362             pcm->sync_ptr->flags = 0;
363
364             err = sync_ptr(pcm);
365             if (err == EPIPE) {
366                 fprintf(stderr, "Aplay:Failed in sync_ptr 2 \n");
367                 /* we failed to make our window -- try to restart */
368                 pcm->underruns++;
369                 pcm->running = 0;
370                 continue;
371             }
372
373             if (debug) {
374                 fprintf(stderr, "Aplay:sync_ptr->s.status.hw_ptr %ld  sync_ptr->c.control.appl_ptr %ld\n",
375                            pcm->sync_ptr->s.status.hw_ptr,
376                            pcm->sync_ptr->c.control.appl_ptr);
377#ifdef QCOM_COMPRESSED_AUDIO_ENABLED
378                 if (compressed && start) {
379                    struct snd_compr_tstamp tstamp;
380		    if (ioctl(pcm->fd, SNDRV_COMPRESS_TSTAMP, &tstamp))
381			fprintf(stderr, "Aplay: failed SNDRV_COMPRESS_TSTAMP\n");
382                    else
383	                fprintf(stderr, "timestamp = %lld\n", tstamp.timestamp);
384		}
385#endif
386             }
387             /*
388              * If we have reached start threshold of buffer prefill,
389              * its time to start the driver.
390              */
391                 if(start)
392                     goto start_done;
393                 if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START)) {
394                     err = -errno;
395                     if (errno == EPIPE) {
396                         fprintf(stderr, "Aplay:Failed in SNDRV_PCM_IOCTL_START\n");
397                         /* we failed to make our window -- try to restart */
398                         pcm->underruns++;
399                         pcm->running = 0;
400                         continue;
401                    } else {
402                        fprintf(stderr, "Aplay:Error no %d \n", errno);
403                        pcm_close(pcm);
404                        return -errno;
405                    }
406                } else
407                    start = 1;
408
409start_done:
410                offset += frames;
411        }
412        while(1) {
413            pcm->sync_ptr->flags = SNDRV_PCM_SYNC_PTR_APPL | SNDRV_PCM_SYNC_PTR_AVAIL_MIN;//SNDRV_PCM_SYNC_PTR_HWSYNC;
414            sync_ptr(pcm);
415            /*
416             * Check for the available buffer in driver. If available buffer is
417             * less than avail_min we need to wait
418             */
419            if (pcm->sync_ptr->s.status.hw_ptr >= pcm->sync_ptr->c.control.appl_ptr) {
420                fprintf(stderr, "Aplay:sync_ptr->s.status.hw_ptr %ld  sync_ptr->c.control.appl_ptr %ld\n",
421                           pcm->sync_ptr->s.status.hw_ptr,
422                           pcm->sync_ptr->c.control.appl_ptr);
423                break;
424            } else
425                poll(pfd, nfds, TIMEOUT_INFINITE);
426        }
427    } else {
428        if (pcm_prepare(pcm)) {
429            fprintf(stderr, "Aplay:Failed in pcm_prepare\n");
430            pcm_close(pcm);
431            return -errno;
432        }
433
434        bufsize = pcm->period_size;
435
436        data = calloc(1, bufsize);
437        if (!data) {
438            fprintf(stderr, "Aplay:could not allocate %d bytes\n", bufsize);
439            pcm_close(pcm);
440            return -ENOMEM;
441        }
442
443        if (data_sz && !piped) {
444            if (remainingData < bufsize)
445                bufsize = remainingData;
446        }
447
448        while (read(fd, data, bufsize) > 0) {
449            if (pcm_write(pcm, data, bufsize)){
450                fprintf(stderr, "Aplay: pcm_write failed\n");
451                free(data);
452                pcm_close(pcm);
453                return -errno;
454            }
455            memset(data, 0, bufsize);
456
457            if (data_sz && !piped) {
458                remainingData -= bufsize;
459                if (remainingData <= 0)
460                    break;
461                if (remainingData < bufsize)
462                       bufsize = remainingData;
463            }
464        }
465        free(data);
466    }
467    fprintf(stderr, "Aplay: Done playing\n");
468    pcm_close(pcm);
469    return 0;
470}
471
472int play_raw(const char *fg, int rate, int ch, const char *device, const char *fn)
473{
474    int fd;
475    unsigned flag = 0;
476
477    if(!fn) {
478        fd = fileno(stdin);
479        piped = 1;
480    } else {
481        fd = open(fn, O_RDONLY);
482        if (fd < 0) {
483            fprintf(stderr, "Aplay:aplay: cannot open '%s'\n", fn);
484            return fd;
485        }
486    }
487
488    if (!strncmp(fg, "M", sizeof("M")))
489        flag = PCM_MMAP;
490    else if (!strncmp(fg, "N", sizeof("N")))
491        flag = PCM_NMMAP;
492
493    fprintf(stderr, "aplay: Playing '%s': format %s ch = %d\n",
494		    fn, get_format_desc(format), ch );
495    return play_file(rate, ch, fd, flag, device, 0);
496}
497
498int play_wav(const char *fg, int rate, int ch, const char *device, const char *fn)
499{
500    struct wav_header hdr;
501    int fd;
502    unsigned flag = 0;
503
504    if (pcm_flag) {
505        if(!fn) {
506            fd = fileno(stdin);
507            piped = 1;
508        } else {
509            fd = open(fn, O_RDONLY);
510            if (fd < 0) {
511                fprintf(stderr, "Aplay:aplay: cannot open '%s'\n", fn);
512                return fd;
513            }
514        }
515        if (compressed) {
516            hdr.sample_rate = rate;
517            hdr.num_channels = ch;
518            hdr.data_sz = 0;
519            goto ignore_header;
520        }
521
522        if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) {
523            fprintf(stderr, "Aplay:aplay: cannot read header\n");
524            return -errno;
525        }
526
527        if ((hdr.riff_id != ID_RIFF) ||
528            (hdr.riff_fmt != ID_WAVE) ||
529            (hdr.fmt_id != ID_FMT)) {
530            fprintf(stderr, "Aplay:aplay: '%s' is not a riff/wave file\n", fn);
531            return -EINVAL;
532        }
533        if ((hdr.audio_format != FORMAT_PCM) ||
534            (hdr.fmt_sz != 16)) {
535            fprintf(stderr, "Aplay:aplay: '%s' is not pcm format\n", fn);
536            return -EINVAL;
537        }
538        if (hdr.bits_per_sample != 16) {
539            fprintf(stderr, "Aplay:aplay: '%s' is not 16bit per sample\n", fn);
540            return -EINVAL;
541        }
542    } else {
543        fd = -EBADFD;
544        hdr.sample_rate = rate;
545        hdr.num_channels = ch;
546        hdr.data_sz = 0;
547    }
548
549ignore_header:
550    if (!strncmp(fg, "M", sizeof("M")))
551        flag = PCM_MMAP;
552    else if (!strncmp(fg, "N", sizeof("N")))
553        flag = PCM_NMMAP;
554    fprintf(stderr, "aplay: Playing '%s':%s\n", fn, get_format_desc(format) );
555
556    return play_file(hdr.sample_rate, hdr.num_channels, fd, flag, device, hdr.data_sz);
557}
558
559int main(int argc, char **argv)
560{
561    int option_index = 0;
562    int c,i;
563    int ch = 2;
564    int rate = 44100;
565    char *mmap = "N";
566    char *device = "hw:0,0";
567    char *filename;
568    int rc = 0;
569
570    if (argc <2) {
571          printf("\nUsage: aplay [options] <file>\n"
572                "options:\n"
573                "-D <hw:C,D>	-- Alsa PCM by name\n"
574                "-M		-- Mmap stream\n"
575                "-P		-- Hostless steam[No PCM]\n"
576		"-C             -- Channels\n"
577		"-R             -- Rate\n"
578                "-V		-- verbose\n"
579		"-F             -- Format\n"
580                "-B             -- Period\n"
581                "-T <MP3, AAC, AC3_PASS_THROUGH>  -- Compressed\n"
582                "<file> \n");
583           fprintf(stderr, "Formats Supported:\n");
584           for (i = 0; i <= SNDRV_PCM_FORMAT_LAST; ++i)
585               if (get_format_name(i))
586                   fprintf(stderr, "%s ", get_format_name(i));
587           fprintf(stderr, "\nSome of these may not be available on selected hardware\n");
588           return 0;
589     }
590     while ((c = getopt_long(argc, argv, "PVMD:R:C:F:B:T:", long_options, &option_index)) != -1) {
591       switch (c) {
592       case 'P':
593          pcm_flag = 0;
594          break;
595       case 'V':
596          debug = 1;
597          break;
598       case 'M':
599          mmap = "M";
600          break;
601       case 'D':
602          device = optarg;
603          break;
604       case 'R':
605          rate = (int)strtol(optarg, NULL, 0);
606          break;
607       case 'C':
608          ch = (int)strtol(optarg, NULL, 0);
609          break;
610       case 'F':
611          printf("optarg = %s\n", optarg);
612          format = get_format(optarg);
613          break;
614       case 'B':
615          period = (int)strtol(optarg, NULL, 0);
616          break;
617       case 'T':
618          compressed = 1;
619          printf("compressed codec type requested = %s\n", optarg);
620          compr_codec = optarg;
621          break;
622       default:
623          printf("\nUsage: aplay [options] <file>\n"
624                "options:\n"
625                "-D <hw:C,D>	-- Alsa PCM by name\n"
626                "-M		-- Mmap stream\n"
627                "-P		-- Hostless steam[No PCM]\n"
628                "-V		-- verbose\n"
629                "-C		-- Channels\n"
630		"-R             -- Rate\n"
631		"-F             -- Format\n"
632                "-B             -- Period\n"
633                "-T             -- Compressed\n"
634                "<file> \n");
635           fprintf(stderr, "Formats Supported:\n");
636           for (i = 0; i < SNDRV_PCM_FORMAT_LAST; ++i)
637               if (get_format_name(i))
638                   fprintf(stderr, "%s ", get_format_name(i));
639           fprintf(stderr, "\nSome of these may not be available on selected hardware\n");
640          return -EINVAL;
641       }
642
643    }
644    filename = (char*) calloc(1, 30);
645    if (!filename) {
646          fprintf(stderr, "Aplay:Failed to allocate filename!");
647          return -ENOMEM;
648    }
649    if (optind > argc - 1) {
650       free(filename);
651       filename = NULL;
652    } else {
653       strlcpy(filename, argv[optind++], 30);
654    }
655
656    if (pcm_flag) {
657	 if (format == SNDRV_PCM_FORMAT_S16_LE)
658             rc = play_wav(mmap, rate, ch, device, filename);
659         else
660             rc = play_raw(mmap, rate, ch, device, filename);
661    } else {
662        rc = play_wav(mmap, rate, ch, device, "dummy");
663    }
664    if (filename)
665        free(filename);
666
667    return rc;
668}
669
670