set viewport to default one just for safe.
[kugel-rb.git] / apps / mp3data.c
blobc0279a7540f249888123c895213ac93485dd2411
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 #define SYNC_MASK (0x7ffL << 21)
46 #define VERSION_MASK (3L << 19)
47 #define LAYER_MASK (3L << 17)
48 #define PROTECTION_MASK (1L << 16)
49 #define BITRATE_MASK (0xfL << 12)
50 #define SAMPLERATE_MASK (3L << 10)
51 #define PADDING_MASK (1L << 9)
52 #define PRIVATE_MASK (1L << 8)
53 #define CHANNELMODE_MASK (3L << 6)
54 #define MODE_EXT_MASK (3L << 4)
55 #define COPYRIGHT_MASK (1L << 3)
56 #define ORIGINAL_MASK (1L << 2)
57 #define EMPHASIS_MASK 3L
59 /* MPEG Version table, sorted by version index */
60 static const signed char version_table[4] = {
61 MPEG_VERSION2_5, -1, MPEG_VERSION2, MPEG_VERSION1
64 /* Bitrate table for mpeg audio, indexed by row index and birate index */
65 static const short bitrates[5][16] = {
66 {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0}, /* V1 L1 */
67 {0,32,48,56, 64, 80, 96,112,128,160,192,224,256,320,384,0}, /* V1 L2 */
68 {0,32,40,48, 56, 64, 80, 96,112,128,160,192,224,256,320,0}, /* V1 L3 */
69 {0,32,48,56, 64, 80, 96,112,128,144,160,176,192,224,256,0}, /* V2 L1 */
70 {0, 8,16,24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,0} /* V2 L2+L3 */
73 /* Bitrate pointer table, indexed by version and layer */
74 static const short *bitrate_table[3][3] =
76 {bitrates[0], bitrates[1], bitrates[2]},
77 {bitrates[3], bitrates[4], bitrates[4]},
78 {bitrates[3], bitrates[4], bitrates[4]}
81 /* Sampling frequency table, indexed by version and frequency index */
82 static const unsigned short freq_table[3][3] =
84 {44100, 48000, 32000}, /* MPEG Version 1 */
85 {22050, 24000, 16000}, /* MPEG version 2 */
86 {11025, 12000, 8000}, /* MPEG version 2.5 */
89 unsigned long bytes2int(unsigned long b0,
90 unsigned long b1,
91 unsigned long b2,
92 unsigned long b3)
94 return (((long)(b0 & 0xFF) << (3*8)) |
95 ((long)(b1 & 0xFF) << (2*8)) |
96 ((long)(b2 & 0xFF) << (1*8)) |
97 ((long)(b3 & 0xFF) << (0*8)));
100 /* check if 'head' is a valid mp3 frame header */
101 static bool is_mp3frameheader(unsigned long head)
103 if ((head & SYNC_MASK) != (unsigned long)SYNC_MASK) /* bad sync? */
104 return false;
105 if ((head & VERSION_MASK) == (1L << 19)) /* bad version? */
106 return false;
107 if (!(head & LAYER_MASK)) /* no layer? */
108 return false;
109 #if CONFIG_CODEC != SWCODEC
110 /* The MAS can't decode layer 1, so treat layer 1 data as invalid */
111 if ((head & LAYER_MASK) == LAYER_MASK)
112 return false;
113 #endif
114 if ((head & BITRATE_MASK) == BITRATE_MASK) /* bad bitrate? */
115 return false;
116 if (!(head & BITRATE_MASK)) /* no bitrate? */
117 return false;
118 if ((head & SAMPLERATE_MASK) == SAMPLERATE_MASK) /* bad sample rate? */
119 return false;
121 return true;
124 static bool mp3headerinfo(struct mp3info *info, unsigned long header)
126 int bitindex, freqindex;
128 /* MPEG Audio Version */
129 if ((header & VERSION_MASK) >> 19 >= sizeof(version_table))
130 return false;
132 info->version = version_table[(header & VERSION_MASK) >> 19];
133 if (info->version < 0)
134 return false;
136 /* Layer */
137 info->layer = 3 - ((header & LAYER_MASK) >> 17);
138 if (info->layer == 3)
139 return false;
141 info->protection = (header & PROTECTION_MASK) ? true : false;
143 /* Bitrate */
144 bitindex = (header & BITRATE_MASK) >> 12;
145 info->bitrate = bitrate_table[info->version][info->layer][bitindex];
146 if(info->bitrate == 0)
147 return false;
149 /* Sampling frequency */
150 freqindex = (header & SAMPLERATE_MASK) >> 10;
151 if (freqindex == 3)
152 return false;
153 info->frequency = freq_table[info->version][freqindex];
155 info->padding = (header & PADDING_MASK) ? 1 : 0;
157 /* Calculate number of bytes, calculation depends on layer */
158 if (info->layer == 0) {
159 info->frame_samples = 384;
160 info->frame_size = (12000 * info->bitrate / info->frequency
161 + info->padding) * 4;
163 else {
164 if ((info->version > MPEG_VERSION1) && (info->layer == 2))
165 info->frame_samples = 576;
166 else
167 info->frame_samples = 1152;
168 info->frame_size = (1000/8) * info->frame_samples * info->bitrate
169 / info->frequency + info->padding;
172 /* Frametime fraction denominator */
173 if (freqindex != 0) { /* 48/32/24/16/12/8 kHz */
174 info->ft_den = 1; /* integer number of milliseconds */
176 else { /* 44.1/22.05/11.025 kHz */
177 if (info->layer == 0) /* layer 1 */
178 info->ft_den = 147;
179 else /* layer 2+3 */
180 info->ft_den = 49;
182 /* Frametime fraction numerator */
183 info->ft_num = 1000 * info->ft_den * info->frame_samples / info->frequency;
185 info->channel_mode = (header & CHANNELMODE_MASK) >> 6;
186 info->mode_extension = (header & MODE_EXT_MASK) >> 4;
187 info->emphasis = header & EMPHASIS_MASK;
189 #ifdef DEBUG_VERBOSE
190 DEBUGF( "Header: %08lx, Ver %d, lay %d, bitr %d, freq %ld, "
191 "chmode %d, mode_ext %d, emph %d, bytes: %d time: %d/%d\n",
192 header, info->version, info->layer+1, info->bitrate,
193 info->frequency, info->channel_mode, info->mode_extension,
194 info->emphasis, info->frame_size, info->ft_num, info->ft_den);
195 #endif
196 return true;
199 static unsigned long __find_next_frame(int fd, long *offset, long max_offset,
200 unsigned long last_header,
201 int(*getfunc)(int fd, unsigned char *c))
203 unsigned long header=0;
204 unsigned char tmp;
205 int i;
207 long pos = 0;
209 /* We remember the last header we found, to use as a template to see if
210 the header we find has the same frequency, layer etc */
211 last_header &= 0xffff0c00;
213 /* Fill up header with first 24 bits */
214 for(i = 0; i < 3; i++) {
215 header <<= 8;
216 if(!getfunc(fd, &tmp))
217 return 0;
218 header |= tmp;
219 pos++;
222 do {
223 header <<= 8;
224 if(!getfunc(fd, &tmp))
225 return 0;
226 header |= tmp;
227 pos++;
228 if(max_offset > 0 && pos > max_offset)
229 return 0;
230 } while(!is_mp3frameheader(header) ||
231 (last_header?((header & 0xffff0c00) != last_header):false));
233 *offset = pos - 4;
235 #if defined(DEBUG)
236 if(*offset)
237 DEBUGF("Warning: skipping %ld bytes of garbage\n", *offset);
238 #endif
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 frames_per_entry;
357 int i;
358 long offset;
359 int j;
360 long tmp;
362 header = find_next_frame(fd, &bytecount, 0x20000, 0);
363 /* Quit if we haven't found a valid header within 128K */
364 if(header == 0)
365 return -1;
367 memset(info, 0, sizeof(struct mp3info));
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 #if defined(DEBUG)
380 DEBUGF("Error: Invalid id3 header, frame_size: %d\n", info->frame_size);
381 #endif
382 return -8;
385 if(read(fd, frame, info->frame_size-4) < 0)
386 return -3;
388 /* calculate position of VBR header */
389 if ( info->version == MPEG_VERSION1 ) {
390 if (info->channel_mode == 3) /* mono */
391 vbrheader = frame + 17;
392 else
393 vbrheader = frame + 32;
395 else {
396 if (info->channel_mode == 3) /* mono */
397 vbrheader = frame + 9;
398 else
399 vbrheader = frame + 17;
402 if (!memcmp(vbrheader, "Xing", 4)
403 || !memcmp(vbrheader, "Info", 4))
405 int i = 8; /* Where to start parsing info */
407 /* DEBUGF("Xing/Info header\n"); */
409 /* Remember where in the file the Xing header is */
410 info->vbr_header_pos = lseek(fd, 0, SEEK_CUR) - info->frame_size;
412 /* We want to skip the Xing frame when playing the stream */
413 bytecount += info->frame_size;
415 /* Now get the next frame to find out the real info about
416 the mp3 stream */
417 header = find_next_frame(fd, &tmp, 0x20000, 0);
418 if(header == 0)
419 return -4;
421 if(!mp3headerinfo(info, header))
422 return -5;
424 /* Is it a VBR file? */
425 info->is_vbr = info->is_xing_vbr = !memcmp(vbrheader, "Xing", 4);
427 if (vbrheader[7] & VBR_FRAMES_FLAG) /* Is the frame count there? */
429 info->frame_count = bytes2int(vbrheader[i], vbrheader[i+1],
430 vbrheader[i+2], vbrheader[i+3]);
431 if (info->frame_count <= ULONG_MAX / info->ft_num)
432 info->file_time = info->frame_count * info->ft_num / info->ft_den;
433 else
434 info->file_time = info->frame_count / info->ft_den * info->ft_num;
435 i += 4;
438 if (vbrheader[7] & VBR_BYTES_FLAG) /* Is byte count there? */
440 info->byte_count = bytes2int(vbrheader[i], vbrheader[i+1],
441 vbrheader[i+2], vbrheader[i+3]);
442 i += 4;
445 if (info->file_time && info->byte_count)
447 if (info->byte_count <= (ULONG_MAX/8))
448 info->bitrate = info->byte_count * 8 / info->file_time;
449 else
450 info->bitrate = info->byte_count / (info->file_time >> 3);
453 if (vbrheader[7] & VBR_TOC_FLAG) /* Is table-of-contents there? */
455 info->has_toc = true;
456 memcpy( info->toc, vbrheader+i, 100 );
457 i += 100;
459 if (vbrheader[7] & VBR_QUALITY_FLAG)
461 /* We don't care about this, but need to skip it */
462 i += 4;
464 #if CONFIG_CODEC==SWCODEC
465 i += 21;
466 info->enc_delay = (vbrheader[i] << 4) | (vbrheader[i + 1] >> 4);
467 info->enc_padding = ((vbrheader[i + 1] & 0x0f) << 8) | vbrheader[i + 2];
468 /* TODO: This sanity checking is rather silly, seeing as how the LAME
469 header contains a CRC field that can be used to verify integrity. */
470 if (!(info->enc_delay >= 0 && info->enc_delay <= 2880 &&
471 info->enc_padding >= 0 && info->enc_padding <= 2*1152))
473 /* Invalid data */
474 info->enc_delay = -1;
475 info->enc_padding = -1;
477 #endif
480 if (!memcmp(vbrheader, "VBRI", 4))
482 DEBUGF("VBRI header\n");
484 /* We want to skip the VBRI frame when playing the stream */
485 bytecount += info->frame_size;
487 /* Now get the next frame to find out the real info about
488 the mp3 stream */
489 header = find_next_frame(fd, &tmp, 0x20000, 0);
490 if(header == 0)
491 return -6;
493 bytecount += tmp;
495 if(!mp3headerinfo(info, header))
496 return -7;
498 DEBUGF("%04x: %04x %04x ", 0, (short)(header >> 16),
499 (short)(header & 0xffff));
500 for(i = 4;i < (int)sizeof(frame)-4;i+=2) {
501 if(i % 16 == 0) {
502 DEBUGF("\n%04x: ", i-4);
504 DEBUGF("%04x ", (frame[i-4] << 8) | frame[i-4+1]);
507 DEBUGF("\n");
509 /* Yes, it is a FhG VBR file */
510 info->is_vbr = true;
511 info->is_vbri_vbr = true;
512 info->has_toc = false; /* We don't parse the TOC (yet) */
514 info->byte_count = bytes2int(vbrheader[10], vbrheader[11],
515 vbrheader[12], vbrheader[13]);
516 info->frame_count = bytes2int(vbrheader[14], vbrheader[15],
517 vbrheader[16], vbrheader[17]);
518 if (info->frame_count <= ULONG_MAX / info->ft_num)
519 info->file_time = info->frame_count * info->ft_num / info->ft_den;
520 else
521 info->file_time = info->frame_count / info->ft_den * info->ft_num;
523 if (info->byte_count <= (ULONG_MAX/8))
524 info->bitrate = info->byte_count * 8 / info->file_time;
525 else
526 info->bitrate = info->byte_count / (info->file_time >> 3);
528 /* We don't parse the TOC, since we don't yet know how to (FIXME) */
529 num_offsets = bytes2int(0, 0, vbrheader[18], vbrheader[19]);
530 frames_per_entry = bytes2int(0, 0, vbrheader[24], vbrheader[25]);
531 DEBUGF("Frame size (%dkpbs): %d bytes (0x%x)\n",
532 info->bitrate, info->frame_size, info->frame_size);
533 DEBUGF("Frame count: %lx\n", info->frame_count);
534 DEBUGF("Byte count: %lx\n", info->byte_count);
535 DEBUGF("Offsets: %d\n", num_offsets);
536 DEBUGF("Frames/entry: %d\n", frames_per_entry);
538 offset = 0;
540 for(i = 0;i < num_offsets;i++)
542 j = bytes2int(0, 0, vbrheader[26+i*2], vbrheader[27+i*2]);
543 offset += j;
544 DEBUGF("%03d: %lx (%x)\n", i, offset - bytecount, j);
548 return bytecount;
551 #ifndef __PCTOOL__
552 static void long2bytes(unsigned char *buf, long val)
554 buf[0] = (val >> 24) & 0xff;
555 buf[1] = (val >> 16) & 0xff;
556 buf[2] = (val >> 8) & 0xff;
557 buf[3] = val & 0xff;
560 int count_mp3_frames(int fd, int startpos, int filesize,
561 void (*progressfunc)(int))
563 unsigned long header = 0;
564 struct mp3info info;
565 int num_frames;
566 long bytes;
567 int cnt;
568 long progress_chunk = filesize / 50; /* Max is 50%, in 1% increments */
569 int progress_cnt = 0;
570 bool is_vbr = false;
571 int last_bitrate = 0;
572 int header_template = 0;
574 if(lseek(fd, startpos, SEEK_SET) < 0)
575 return -1;
577 buf_init();
579 /* Find out the total number of frames */
580 num_frames = 0;
581 cnt = 0;
583 while((header = buf_find_next_frame(fd, &bytes, -1, header_template))) {
584 mp3headerinfo(&info, header);
586 if(!header_template)
587 header_template = header;
589 /* See if this really is a VBR file */
590 if(last_bitrate && info.bitrate != last_bitrate)
592 is_vbr = true;
594 last_bitrate = info.bitrate;
596 buf_seek(fd, info.frame_size-4);
597 num_frames++;
598 if(progressfunc)
600 cnt += bytes + info.frame_size;
601 if(cnt > progress_chunk)
603 progress_cnt++;
604 progressfunc(progress_cnt);
605 cnt = 0;
609 DEBUGF("Total number of frames: %d\n", num_frames);
611 if(is_vbr)
612 return num_frames;
613 else
615 DEBUGF("Not a VBR file\n");
616 return 0;
620 static const char cooltext[] = "Rockbox - rocks your box";
622 /* buf needs to be the audio buffer with TOC generation enabled,
623 and at least MAX_XING_HEADER_SIZE bytes otherwise */
624 int create_xing_header(int fd, long startpos, long filesize,
625 unsigned char *buf, unsigned long num_frames,
626 unsigned long rec_time, unsigned long header_template,
627 void (*progressfunc)(int), bool generate_toc)
629 struct mp3info info;
630 unsigned char toc[100];
631 unsigned long header = 0;
632 unsigned long xing_header_template = header_template;
633 unsigned long filepos;
634 long pos, last_pos;
635 long j;
636 long bytes;
637 int i;
638 int index;
640 DEBUGF("create_xing_header()\n");
642 if(generate_toc)
644 lseek(fd, startpos, SEEK_SET);
645 buf_init();
647 /* Generate filepos table */
648 last_pos = 0;
649 filepos = 0;
650 header = 0;
651 for(i = 0;i < 100;i++) {
652 /* Calculate the absolute frame number for this seek point */
653 pos = i * num_frames / 100;
655 /* Advance from the last seek point to this one */
656 for(j = 0;j < pos - last_pos;j++)
658 header = buf_find_next_frame(fd, &bytes, -1, header_template);
659 filepos += bytes;
660 mp3headerinfo(&info, header);
661 buf_seek(fd, info.frame_size-4);
662 filepos += info.frame_size;
664 if(!header_template)
665 header_template = header;
668 /* Save a header for later use if header_template is empty.
669 We only save one header, and we want to save one in the
670 middle of the stream, just in case the first and the last
671 headers are corrupt. */
672 if(!xing_header_template && i == 1)
673 xing_header_template = header;
675 if(progressfunc)
677 progressfunc(50 + i/2);
680 /* Fill in the TOC entry */
681 /* each toc is a single byte indicating how many 256ths of the
682 * way through the file, is that percent of the way through the
683 * song. the easy method, filepos*256/filesize, chokes when
684 * the upper 8 bits of the file position are nonzero
685 * (i.e. files over 16mb in size).
687 if (filepos > (ULONG_MAX/256))
689 /* instead of multiplying filepos by 256, we divide
690 * filesize by 256.
692 toc[i] = filepos / (filesize >> 8);
694 else
696 toc[i] = filepos * 256 / filesize;
699 DEBUGF("Pos %d: %ld relpos: %ld filepos: %lx tocentry: %x\n",
700 i, pos, pos-last_pos, filepos, toc[i]);
702 last_pos = pos;
706 /* Use the template header and create a new one.
707 We ignore the Protection bit even if the rest of the stream is
708 protected. */
709 header = xing_header_template & ~(BITRATE_MASK|PROTECTION_MASK|PADDING_MASK);
710 header |= 8 << 12; /* This gives us plenty of space, 192..576 bytes */
712 if (!mp3headerinfo(&info, header))
713 return 0; /* invalid header */
715 if (num_frames == 0 && rec_time) {
716 /* estimate the number of frames based on the recording time */
717 if (rec_time <= ULONG_MAX / info.ft_den)
718 num_frames = rec_time * info.ft_den / info.ft_num;
719 else
720 num_frames = rec_time / info.ft_num * info.ft_den;
723 /* Clear the frame */
724 memset(buf, 0, MAX_XING_HEADER_SIZE);
726 /* Write the header to the buffer */
727 long2bytes(buf, header);
729 /* Calculate position of VBR header */
730 if (info.version == MPEG_VERSION1) {
731 if (info.channel_mode == 3) /* mono */
732 index = 21;
733 else
734 index = 36;
736 else {
737 if (info.channel_mode == 3) /* mono */
738 index = 13;
739 else
740 index = 21;
743 /* Create the Xing data */
744 memcpy(&buf[index], "Xing", 4);
745 long2bytes(&buf[index+4], (num_frames ? VBR_FRAMES_FLAG : 0)
746 | (filesize ? VBR_BYTES_FLAG : 0)
747 | (generate_toc ? VBR_TOC_FLAG : 0));
748 index += 8;
749 if(num_frames)
751 long2bytes(&buf[index], num_frames);
752 index += 4;
755 if(filesize)
757 long2bytes(&buf[index], filesize - startpos);
758 index += 4;
761 /* Copy the TOC */
762 memcpy(buf + index, toc, 100);
764 /* And some extra cool info */
765 memcpy(buf + index + 100, cooltext, sizeof(cooltext));
767 #ifdef DEBUG
768 for(i = 0;i < info.frame_size;i++)
770 if(i && !(i % 16))
771 DEBUGF("\n");
773 DEBUGF("%02x ", buf[i]);
775 #endif
777 return info.frame_size;
780 #endif