RaaA: Create full config directory tree during initialization
[maemo-rb.git] / apps / mp3data.c
blob44208604bca7115561eb44c3fc69ac798f604040
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002 by Daniel Stenberg
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
23 * Parts of this code has been stolen from the Ample project and was written
24 * by David Härdeman. It has since been extended and enhanced pretty much by
25 * all sorts of friendly Rockbox people.
27 * A nice reference for MPEG header info:
28 * http://rockbox.haxx.se/docs/mpeghdr.html
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <stdbool.h>
36 #include <limits.h>
37 #include "debug.h"
38 #include "logf.h"
39 #include "mp3data.h"
40 #include "file.h"
41 #include "buffer.h"
43 // #define DEBUG_VERBOSE
45 #ifdef DEBUG_VERBOSE
46 #define VDEBUGF DEBUGF
47 #else
48 #define VDEBUGF(...) do { } while(0)
49 #endif
51 #define SYNC_MASK (0x7ffL << 21)
52 #define VERSION_MASK (3L << 19)
53 #define LAYER_MASK (3L << 17)
54 #define PROTECTION_MASK (1L << 16)
55 #define BITRATE_MASK (0xfL << 12)
56 #define SAMPLERATE_MASK (3L << 10)
57 #define PADDING_MASK (1L << 9)
58 #define PRIVATE_MASK (1L << 8)
59 #define CHANNELMODE_MASK (3L << 6)
60 #define MODE_EXT_MASK (3L << 4)
61 #define COPYRIGHT_MASK (1L << 3)
62 #define ORIGINAL_MASK (1L << 2)
63 #define EMPHASIS_MASK 3L
65 /* MPEG Version table, sorted by version index */
66 static const signed char version_table[4] = {
67 MPEG_VERSION2_5, -1, MPEG_VERSION2, MPEG_VERSION1
70 /* Bitrate table for mpeg audio, indexed by row index and birate index */
71 static const short bitrates[5][16] = {
72 {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0}, /* V1 L1 */
73 {0,32,48,56, 64, 80, 96,112,128,160,192,224,256,320,384,0}, /* V1 L2 */
74 {0,32,40,48, 56, 64, 80, 96,112,128,160,192,224,256,320,0}, /* V1 L3 */
75 {0,32,48,56, 64, 80, 96,112,128,144,160,176,192,224,256,0}, /* V2 L1 */
76 {0, 8,16,24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,0} /* V2 L2+L3 */
79 /* Bitrate pointer table, indexed by version and layer */
80 static const short *bitrate_table[3][3] =
82 {bitrates[0], bitrates[1], bitrates[2]},
83 {bitrates[3], bitrates[4], bitrates[4]},
84 {bitrates[3], bitrates[4], bitrates[4]}
87 /* Sampling frequency table, indexed by version and frequency index */
88 static const unsigned short freq_table[3][3] =
90 {44100, 48000, 32000}, /* MPEG Version 1 */
91 {22050, 24000, 16000}, /* MPEG version 2 */
92 {11025, 12000, 8000}, /* MPEG version 2.5 */
95 unsigned long bytes2int(unsigned long b0, unsigned long b1,
96 unsigned long b2, unsigned long b3)
98 return (b0 & 0xFF) << (3*8) |
99 (b1 & 0xFF) << (2*8) |
100 (b2 & 0xFF) << (1*8) |
101 (b3 & 0xFF) << (0*8);
104 /* check if 'head' is a valid mp3 frame header */
105 static bool is_mp3frameheader(unsigned long head)
107 if ((head & SYNC_MASK) != (unsigned long)SYNC_MASK) /* bad sync? */
108 return false;
109 if ((head & VERSION_MASK) == (1L << 19)) /* bad version? */
110 return false;
111 if (!(head & LAYER_MASK)) /* no layer? */
112 return false;
113 #if CONFIG_CODEC != SWCODEC
114 /* The MAS can't decode layer 1, so treat layer 1 data as invalid */
115 if ((head & LAYER_MASK) == LAYER_MASK)
116 return false;
117 #endif
118 if ((head & BITRATE_MASK) == BITRATE_MASK) /* bad bitrate? */
119 return false;
120 if (!(head & BITRATE_MASK)) /* no bitrate? */
121 return false;
122 if ((head & SAMPLERATE_MASK) == SAMPLERATE_MASK) /* bad sample rate? */
123 return false;
125 return true;
128 static bool mp3headerinfo(struct mp3info *info, unsigned long header)
130 int bitindex, freqindex;
132 /* MPEG Audio Version */
133 if ((header & VERSION_MASK) >> 19 >= sizeof(version_table))
134 return false;
136 info->version = version_table[(header & VERSION_MASK) >> 19];
137 if (info->version < 0)
138 return false;
140 /* Layer */
141 info->layer = 3 - ((header & LAYER_MASK) >> 17);
142 if (info->layer == 3)
143 return false;
145 info->protection = (header & PROTECTION_MASK) ? true : false;
147 /* Bitrate */
148 bitindex = (header & BITRATE_MASK) >> 12;
149 info->bitrate = bitrate_table[info->version][info->layer][bitindex];
150 if(info->bitrate == 0)
151 return false;
153 /* Sampling frequency */
154 freqindex = (header & SAMPLERATE_MASK) >> 10;
155 if (freqindex == 3)
156 return false;
157 info->frequency = freq_table[info->version][freqindex];
159 info->padding = (header & PADDING_MASK) ? 1 : 0;
161 /* Calculate number of bytes, calculation depends on layer */
162 if (info->layer == 0) {
163 info->frame_samples = 384;
164 info->frame_size = (12000 * info->bitrate / info->frequency
165 + info->padding) * 4;
167 else {
168 if ((info->version > MPEG_VERSION1) && (info->layer == 2))
169 info->frame_samples = 576;
170 else
171 info->frame_samples = 1152;
172 info->frame_size = (1000/8) * info->frame_samples * info->bitrate
173 / info->frequency + info->padding;
176 /* Frametime fraction denominator */
177 if (freqindex != 0) { /* 48/32/24/16/12/8 kHz */
178 info->ft_den = 1; /* integer number of milliseconds */
180 else { /* 44.1/22.05/11.025 kHz */
181 if (info->layer == 0) /* layer 1 */
182 info->ft_den = 147;
183 else /* layer 2+3 */
184 info->ft_den = 49;
186 /* Frametime fraction numerator */
187 info->ft_num = 1000 * info->ft_den * info->frame_samples / info->frequency;
189 info->channel_mode = (header & CHANNELMODE_MASK) >> 6;
190 info->mode_extension = (header & MODE_EXT_MASK) >> 4;
191 info->emphasis = header & EMPHASIS_MASK;
193 VDEBUGF( "Header: %08lx, Ver %d, lay %d, bitr %d, freq %ld, "
194 "chmode %d, mode_ext %d, emph %d, bytes: %d time: %d/%d\n",
195 header, info->version, info->layer+1, info->bitrate,
196 info->frequency, info->channel_mode, info->mode_extension,
197 info->emphasis, info->frame_size, info->ft_num, info->ft_den);
198 return true;
201 static unsigned long __find_next_frame(int fd, long *offset, long max_offset,
202 unsigned long last_header,
203 int(*getfunc)(int fd, unsigned char *c))
205 unsigned long header=0;
206 unsigned char tmp;
207 int i;
209 long pos = 0;
211 /* We remember the last header we found, to use as a template to see if
212 the header we find has the same frequency, layer etc */
213 last_header &= 0xffff0c00;
215 /* Fill up header with first 24 bits */
216 for(i = 0; i < 3; i++) {
217 header <<= 8;
218 if(!getfunc(fd, &tmp))
219 return 0;
220 header |= tmp;
221 pos++;
224 do {
225 header <<= 8;
226 if(!getfunc(fd, &tmp))
227 return 0;
228 header |= tmp;
229 pos++;
230 if(max_offset > 0 && pos > max_offset)
231 return 0;
232 } while(!is_mp3frameheader(header) ||
233 (last_header?((header & 0xffff0c00) != last_header):false));
235 *offset = pos - 4;
237 if(*offset)
238 VDEBUGF("Warning: skipping %ld bytes of garbage\n", *offset);
240 return header;
243 static int fileread(int fd, unsigned char *c)
245 return read(fd, c, 1);
248 unsigned long find_next_frame(int fd, long *offset, long max_offset, unsigned long last_header)
250 return __find_next_frame(fd, offset, max_offset, last_header, fileread);
253 #ifndef __PCTOOL__
254 static int fnf_read_index;
255 static int fnf_buf_len;
257 static int buf_getbyte(int fd, unsigned char *c)
259 if(fnf_read_index < fnf_buf_len)
261 *c = audiobuf[fnf_read_index++];
262 return 1;
264 else
266 fnf_buf_len = read(fd, audiobuf, audiobufend - audiobuf);
267 if(fnf_buf_len < 0)
268 return -1;
270 fnf_read_index = 0;
272 if(fnf_buf_len > 0)
274 *c = audiobuf[fnf_read_index++];
275 return 1;
277 else
278 return 0;
280 return 0;
283 static int buf_seek(int fd, int len)
285 fnf_read_index += len;
286 if(fnf_read_index > fnf_buf_len)
288 len = fnf_read_index - fnf_buf_len;
290 fnf_buf_len = read(fd, audiobuf, audiobufend - audiobuf);
291 if(fnf_buf_len < 0)
292 return -1;
294 fnf_read_index = 0;
295 fnf_read_index += len;
298 if(fnf_read_index > fnf_buf_len)
300 return -1;
302 else
303 return 0;
306 static void buf_init(void)
308 fnf_buf_len = 0;
309 fnf_read_index = 0;
312 static unsigned long buf_find_next_frame(int fd, long *offset, long max_offset,
313 unsigned long last_header)
315 return __find_next_frame(fd, offset, max_offset, last_header, buf_getbyte);
318 static int audiobuflen;
319 static int mem_pos;
320 static int mem_cnt;
321 static int mem_maxlen;
323 static int mem_getbyte(int dummy, unsigned char *c)
325 dummy = dummy;
327 *c = audiobuf[mem_pos++];
328 if(mem_pos >= audiobuflen)
329 mem_pos = 0;
331 if(mem_cnt++ >= mem_maxlen)
332 return 0;
333 else
334 return 1;
337 unsigned long mem_find_next_frame(int startpos, long *offset, long max_offset,
338 unsigned long last_header)
340 audiobuflen = audiobufend - audiobuf;
341 mem_pos = startpos;
342 mem_cnt = 0;
343 mem_maxlen = max_offset;
345 return __find_next_frame(0, offset, max_offset, last_header, mem_getbyte);
347 #endif
349 int get_mp3file_info(int fd, struct mp3info *info)
351 unsigned char frame[1800];
352 unsigned char *vbrheader;
353 unsigned long header;
354 long bytecount;
355 int num_offsets;
356 int i;
357 long offset;
358 int j;
359 long tmp;
361 header = find_next_frame(fd, &bytecount, 0x20000, 0);
362 /* Quit if we haven't found a valid header within 128K */
363 if(header == 0)
364 return -1;
366 memset(info, 0, sizeof(struct mp3info));
367 memset(frame, 0, sizeof(frame));
368 #if CONFIG_CODEC==SWCODEC
369 /* These two are needed for proper LAME gapless MP3 playback */
370 info->enc_delay = -1;
371 info->enc_padding = -1;
372 #endif
373 if(!mp3headerinfo(info, header))
374 return -2;
376 /* OK, we have found a frame. Let's see if it has a Xing header */
377 if (info->frame_size-4 >= (int)sizeof(frame))
379 DEBUGF("Error: Invalid id3 header, frame_size: %d\n", info->frame_size);
380 return -8;
383 if(read(fd, frame, info->frame_size-4) < 0)
384 return -3;
386 /* calculate position of VBR header */
387 if ( info->version == MPEG_VERSION1 ) {
388 if (info->channel_mode == 3) /* mono */
389 vbrheader = frame + 17;
390 else
391 vbrheader = frame + 32;
393 else {
394 if (info->channel_mode == 3) /* mono */
395 vbrheader = frame + 9;
396 else
397 vbrheader = frame + 17;
400 if (!memcmp(vbrheader, "Xing", 4)
401 || !memcmp(vbrheader, "Info", 4))
403 int i = 8; /* Where to start parsing info */
405 /* DEBUGF("Xing/Info header\n"); */
407 /* Remember where in the file the Xing header is */
408 info->vbr_header_pos = lseek(fd, 0, SEEK_CUR) - info->frame_size;
410 /* We want to skip the Xing frame when playing the stream */
411 bytecount += info->frame_size;
413 /* Now get the next frame to find out the real info about
414 the mp3 stream */
415 header = find_next_frame(fd, &tmp, 0x20000, 0);
416 if(header == 0)
417 return -4;
419 if(!mp3headerinfo(info, header))
420 return -5;
422 /* Is it a VBR file? */
423 info->is_vbr = info->is_xing_vbr = !memcmp(vbrheader, "Xing", 4);
425 if (vbrheader[7] & VBR_FRAMES_FLAG) /* Is the frame count there? */
427 info->frame_count = bytes2int(vbrheader[i], vbrheader[i+1],
428 vbrheader[i+2], vbrheader[i+3]);
429 if (info->frame_count <= ULONG_MAX / info->ft_num)
430 info->file_time = info->frame_count * info->ft_num / info->ft_den;
431 else
432 info->file_time = info->frame_count / info->ft_den * info->ft_num;
433 i += 4;
436 if (vbrheader[7] & VBR_BYTES_FLAG) /* Is byte count there? */
438 info->byte_count = bytes2int(vbrheader[i], vbrheader[i+1],
439 vbrheader[i+2], vbrheader[i+3]);
440 i += 4;
443 if (info->file_time && info->byte_count)
445 if (info->byte_count <= (ULONG_MAX/8))
446 info->bitrate = info->byte_count * 8 / info->file_time;
447 else
448 info->bitrate = info->byte_count / (info->file_time >> 3);
451 if (vbrheader[7] & VBR_TOC_FLAG) /* Is table-of-contents there? */
453 info->has_toc = true;
454 memcpy( info->toc, vbrheader+i, 100 );
455 i += 100;
457 if (vbrheader[7] & VBR_QUALITY_FLAG)
459 /* We don't care about this, but need to skip it */
460 i += 4;
462 #if CONFIG_CODEC==SWCODEC
463 i += 21;
464 info->enc_delay = ((int)vbrheader[i ] << 4) | (vbrheader[i+1] >> 4);
465 info->enc_padding = ((int)vbrheader[i+1] << 8) | vbrheader[i+2];
466 /* TODO: This sanity checking is rather silly, seeing as how the LAME
467 header contains a CRC field that can be used to verify integrity. */
468 if (!(info->enc_delay >= 0 && info->enc_delay <= 2880 &&
469 info->enc_padding >= 0 && info->enc_padding <= 2*1152))
471 /* Invalid data */
472 info->enc_delay = -1;
473 info->enc_padding = -1;
475 #endif
478 if (!memcmp(vbrheader, "VBRI", 4))
480 VDEBUGF("VBRI header\n");
482 /* We want to skip the VBRI frame when playing the stream */
483 bytecount += info->frame_size;
485 /* Now get the next frame to find out the real info about
486 the mp3 stream */
487 header = find_next_frame(fd, &tmp, 0x20000, 0);
488 if(header == 0)
489 return -6;
491 bytecount += tmp;
493 if(!mp3headerinfo(info, header))
494 return -7;
496 VDEBUGF("%04x: %04x %04x ", 0, (short)(header >> 16),
497 (short)(header & 0xffff));
498 for(i = 4;i < (int)sizeof(frame)-4;i+=2) {
499 if(i % 16 == 0) {
500 VDEBUGF("\n%04x: ", i-4);
502 VDEBUGF("%04x ", (frame[i-4] << 8) | frame[i-4+1]);
505 VDEBUGF("\n");
507 /* Yes, it is a FhG VBR file */
508 info->is_vbr = true;
509 info->is_vbri_vbr = true;
510 info->has_toc = false; /* We don't parse the TOC (yet) */
512 info->byte_count = bytes2int(vbrheader[10], vbrheader[11],
513 vbrheader[12], vbrheader[13]);
514 info->frame_count = bytes2int(vbrheader[14], vbrheader[15],
515 vbrheader[16], vbrheader[17]);
516 if (info->frame_count <= ULONG_MAX / info->ft_num)
517 info->file_time = info->frame_count * info->ft_num / info->ft_den;
518 else
519 info->file_time = info->frame_count / info->ft_den * info->ft_num;
521 if (info->byte_count <= (ULONG_MAX/8))
522 info->bitrate = info->byte_count * 8 / info->file_time;
523 else
524 info->bitrate = info->byte_count / (info->file_time >> 3);
526 /* We don't parse the TOC, since we don't yet know how to (FIXME) */
527 num_offsets = bytes2int(0, 0, vbrheader[18], vbrheader[19]);
528 VDEBUGF("Frame size (%dkpbs): %d bytes (0x%x)\n",
529 info->bitrate, info->frame_size, info->frame_size);
530 VDEBUGF("Frame count: %lx\n", info->frame_count);
531 VDEBUGF("Byte count: %lx\n", info->byte_count);
532 VDEBUGF("Offsets: %d\n", num_offsets);
533 VDEBUGF("Frames/entry: %ld\n",
534 bytes2int(0, 0, vbrheader[24], vbrheader[25]));
536 offset = 0;
538 for(i = 0;i < num_offsets;i++)
540 j = bytes2int(0, 0, vbrheader[26+i*2], vbrheader[27+i*2]);
541 offset += j;
542 VDEBUGF("%03d: %lx (%x)\n", i, offset - bytecount, j);
546 return bytecount;
549 #ifndef __PCTOOL__
550 static void long2bytes(unsigned char *buf, long val)
552 buf[0] = (val >> 24) & 0xff;
553 buf[1] = (val >> 16) & 0xff;
554 buf[2] = (val >> 8) & 0xff;
555 buf[3] = val & 0xff;
558 int count_mp3_frames(int fd, int startpos, int filesize,
559 void (*progressfunc)(int))
561 unsigned long header = 0;
562 struct mp3info info;
563 int num_frames;
564 long bytes;
565 int cnt;
566 long progress_chunk = filesize / 50; /* Max is 50%, in 1% increments */
567 int progress_cnt = 0;
568 bool is_vbr = false;
569 int last_bitrate = 0;
570 int header_template = 0;
572 if(lseek(fd, startpos, SEEK_SET) < 0)
573 return -1;
575 buf_init();
577 /* Find out the total number of frames */
578 num_frames = 0;
579 cnt = 0;
581 while((header = buf_find_next_frame(fd, &bytes, -1, header_template))) {
582 mp3headerinfo(&info, header);
584 if(!header_template)
585 header_template = header;
587 /* See if this really is a VBR file */
588 if(last_bitrate && info.bitrate != last_bitrate)
590 is_vbr = true;
592 last_bitrate = info.bitrate;
594 buf_seek(fd, info.frame_size-4);
595 num_frames++;
596 if(progressfunc)
598 cnt += bytes + info.frame_size;
599 if(cnt > progress_chunk)
601 progress_cnt++;
602 progressfunc(progress_cnt);
603 cnt = 0;
607 VDEBUGF("Total number of frames: %d\n", num_frames);
609 if(is_vbr)
610 return num_frames;
611 else
613 DEBUGF("Not a VBR file\n");
614 return 0;
618 static const char cooltext[] = "Rockbox - rocks your box";
620 /* buf needs to be the audio buffer with TOC generation enabled,
621 and at least MAX_XING_HEADER_SIZE bytes otherwise */
622 int create_xing_header(int fd, long startpos, long filesize,
623 unsigned char *buf, unsigned long num_frames,
624 unsigned long rec_time, unsigned long header_template,
625 void (*progressfunc)(int), bool generate_toc)
627 struct mp3info info;
628 unsigned char toc[100];
629 unsigned long header = 0;
630 unsigned long xing_header_template = header_template;
631 unsigned long filepos;
632 long pos, last_pos;
633 long j;
634 long bytes;
635 int i;
636 int index;
638 DEBUGF("create_xing_header()\n");
640 if(generate_toc)
642 lseek(fd, startpos, SEEK_SET);
643 buf_init();
645 /* Generate filepos table */
646 last_pos = 0;
647 filepos = 0;
648 header = 0;
649 for(i = 0;i < 100;i++) {
650 /* Calculate the absolute frame number for this seek point */
651 pos = i * num_frames / 100;
653 /* Advance from the last seek point to this one */
654 for(j = 0;j < pos - last_pos;j++)
656 header = buf_find_next_frame(fd, &bytes, -1, header_template);
657 filepos += bytes;
658 mp3headerinfo(&info, header);
659 buf_seek(fd, info.frame_size-4);
660 filepos += info.frame_size;
662 if(!header_template)
663 header_template = header;
666 /* Save a header for later use if header_template is empty.
667 We only save one header, and we want to save one in the
668 middle of the stream, just in case the first and the last
669 headers are corrupt. */
670 if(!xing_header_template && i == 1)
671 xing_header_template = header;
673 if(progressfunc)
675 progressfunc(50 + i/2);
678 /* Fill in the TOC entry */
679 /* each toc is a single byte indicating how many 256ths of the
680 * way through the file, is that percent of the way through the
681 * song. the easy method, filepos*256/filesize, chokes when
682 * the upper 8 bits of the file position are nonzero
683 * (i.e. files over 16mb in size).
685 if (filepos > (ULONG_MAX/256))
687 /* instead of multiplying filepos by 256, we divide
688 * filesize by 256.
690 toc[i] = filepos / (filesize >> 8);
692 else
694 toc[i] = filepos * 256 / filesize;
697 VDEBUGF("Pos %d: %ld relpos: %ld filepos: %lx tocentry: %x\n",
698 i, pos, pos-last_pos, filepos, toc[i]);
700 last_pos = pos;
704 /* Use the template header and create a new one.
705 We ignore the Protection bit even if the rest of the stream is
706 protected. */
707 header = xing_header_template & ~(BITRATE_MASK|PROTECTION_MASK|PADDING_MASK);
708 header |= 8 << 12; /* This gives us plenty of space, 192..576 bytes */
710 if (!mp3headerinfo(&info, header))
711 return 0; /* invalid header */
713 if (num_frames == 0 && rec_time) {
714 /* estimate the number of frames based on the recording time */
715 if (rec_time <= ULONG_MAX / info.ft_den)
716 num_frames = rec_time * info.ft_den / info.ft_num;
717 else
718 num_frames = rec_time / info.ft_num * info.ft_den;
721 /* Clear the frame */
722 memset(buf, 0, MAX_XING_HEADER_SIZE);
724 /* Write the header to the buffer */
725 long2bytes(buf, header);
727 /* Calculate position of VBR header */
728 if (info.version == MPEG_VERSION1) {
729 if (info.channel_mode == 3) /* mono */
730 index = 21;
731 else
732 index = 36;
734 else {
735 if (info.channel_mode == 3) /* mono */
736 index = 13;
737 else
738 index = 21;
741 /* Create the Xing data */
742 memcpy(&buf[index], "Xing", 4);
743 long2bytes(&buf[index+4], (num_frames ? VBR_FRAMES_FLAG : 0)
744 | (filesize ? VBR_BYTES_FLAG : 0)
745 | (generate_toc ? VBR_TOC_FLAG : 0));
746 index += 8;
747 if(num_frames)
749 long2bytes(&buf[index], num_frames);
750 index += 4;
753 if(filesize)
755 long2bytes(&buf[index], filesize - startpos);
756 index += 4;
759 /* Copy the TOC */
760 memcpy(buf + index, toc, 100);
762 /* And some extra cool info */
763 memcpy(buf + index + 100, cooltext, sizeof(cooltext));
765 #ifdef DEBUG
766 for(i = 0;i < info.frame_size;i++)
768 if(i && !(i % 16))
769 DEBUGF("\n");
771 DEBUGF("%02x ", buf[i]);
773 #endif
775 return info.frame_size;
778 #endif