| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | /* | 
| 2 |  |  |  |  |  |  | * This program is free software; you can redistribute it and/or modify | 
| 3 |  |  |  |  |  |  | * it under the terms of the GNU General Public License as published by | 
| 4 |  |  |  |  |  |  | * the Free Software Foundation; either version 2 of the License, or | 
| 5 |  |  |  |  |  |  | * (at your option) any later version. | 
| 6 |  |  |  |  |  |  | * | 
| 7 |  |  |  |  |  |  | * This program is distributed in the hope that it will be useful, | 
| 8 |  |  |  |  |  |  | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
| 9 |  |  |  |  |  |  | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
| 10 |  |  |  |  |  |  | * GNU General Public License for more details. | 
| 11 |  |  |  |  |  |  | * | 
| 12 |  |  |  |  |  |  | * You should have received a copy of the GNU General Public License | 
| 13 |  |  |  |  |  |  | * along with this program; if not, write to the Free Software | 
| 14 |  |  |  |  |  |  | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | 
| 15 |  |  |  |  |  |  | */ | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | #include "ogg.h" | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | int | 
| 20 | 19 |  |  |  |  |  | get_ogg_metadata(PerlIO *infile, char *file, HV *info, HV *tags) | 
| 21 |  |  |  |  |  |  | { | 
| 22 | 19 |  |  |  |  |  | return _ogg_parse(infile, file, info, tags, 0); | 
| 23 |  |  |  |  |  |  | } | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | int | 
| 26 | 22 |  |  |  |  |  | _ogg_parse(PerlIO *infile, char *file, HV *info, HV *tags, uint8_t seeking) | 
| 27 |  |  |  |  |  |  | { | 
| 28 |  |  |  |  |  |  | Buffer ogg_buf, vorbis_buf; | 
| 29 |  |  |  |  |  |  | unsigned char *bptr; | 
| 30 |  |  |  |  |  |  | unsigned int buf_size; | 
| 31 |  |  |  |  |  |  |  | 
| 32 | 22 |  |  |  |  |  | unsigned int id3_size = 0; // size of leading ID3 data | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | off_t file_size;           // total file size | 
| 35 |  |  |  |  |  |  | off_t audio_size;          // total size of audio without tags | 
| 36 | 22 |  |  |  |  |  | off_t audio_offset = 0;    // offset to audio | 
| 37 |  |  |  |  |  |  |  | 
| 38 |  |  |  |  |  |  | unsigned char ogghdr[28]; | 
| 39 |  |  |  |  |  |  | char header_type; | 
| 40 |  |  |  |  |  |  | int serialno; | 
| 41 |  |  |  |  |  |  | int final_serialno; | 
| 42 |  |  |  |  |  |  | int pagenum; | 
| 43 |  |  |  |  |  |  | uint8_t num_segments; | 
| 44 |  |  |  |  |  |  | int pagelen; | 
| 45 | 22 |  |  |  |  |  | int page = 0; | 
| 46 | 22 |  |  |  |  |  | int packets = 0; | 
| 47 | 22 |  |  |  |  |  | int streams = 0; | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | unsigned char vorbishdr[23]; | 
| 50 |  |  |  |  |  |  | unsigned char channels; | 
| 51 | 22 |  |  |  |  |  | unsigned int blocksize_0 = 0; | 
| 52 |  |  |  |  |  |  | unsigned int avg_buf_size; | 
| 53 | 22 |  |  |  |  |  | unsigned int samplerate = 0; | 
| 54 | 22 |  |  |  |  |  | unsigned int bitrate_nominal = 0; | 
| 55 | 22 |  |  |  |  |  | uint64_t granule_pos = 0; | 
| 56 |  |  |  |  |  |  |  | 
| 57 | 22 |  |  |  |  |  | unsigned char vorbis_type = 0; | 
| 58 |  |  |  |  |  |  |  | 
| 59 |  |  |  |  |  |  | int i; | 
| 60 | 22 |  |  |  |  |  | int err = 0; | 
| 61 |  |  |  |  |  |  |  | 
| 62 | 22 |  |  |  |  |  | buffer_init(&ogg_buf, OGG_BLOCK_SIZE); | 
| 63 | 22 |  |  |  |  |  | buffer_init(&vorbis_buf, 0); | 
| 64 |  |  |  |  |  |  |  | 
| 65 | 22 |  |  |  |  |  | file_size = _file_size(infile); | 
| 66 | 22 |  |  |  |  |  | my_hv_store( info, "file_size", newSVuv(file_size) ); | 
| 67 |  |  |  |  |  |  |  | 
| 68 | 22 | 50 |  |  |  |  | if ( !_check_buf(infile, &ogg_buf, 10, OGG_BLOCK_SIZE) ) { | 
| 69 | 0 |  |  |  |  |  | err = -1; | 
| 70 | 0 |  |  |  |  |  | goto out; | 
| 71 |  |  |  |  |  |  | } | 
| 72 |  |  |  |  |  |  |  | 
| 73 |  |  |  |  |  |  | // Skip ID3 tags if any | 
| 74 | 22 |  |  |  |  |  | bptr = (unsigned char *)buffer_ptr(&ogg_buf); | 
| 75 | 22 | 50 |  |  |  |  | if ( | 
| 76 | 0 | 0 |  |  |  |  | (bptr[0] == 'I' && bptr[1] == 'D' && bptr[2] == '3') && | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 77 | 0 | 0 |  |  |  |  | bptr[3] < 0xff && bptr[4] < 0xff && | 
|  |  | 0 |  |  |  |  |  | 
| 78 | 0 | 0 |  |  |  |  | bptr[6] < 0x80 && bptr[7] < 0x80 && bptr[8] < 0x80 && bptr[9] < 0x80 | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | ) { | 
| 80 |  |  |  |  |  |  | /* found an ID3 header... */ | 
| 81 | 0 |  |  |  |  |  | id3_size = 10 + (bptr[6]<<21) + (bptr[7]<<14) + (bptr[8]<<7) + bptr[9]; | 
| 82 |  |  |  |  |  |  |  | 
| 83 | 0 | 0 |  |  |  |  | if (bptr[5] & 0x10) { | 
| 84 |  |  |  |  |  |  | // footer present | 
| 85 | 0 |  |  |  |  |  | id3_size += 10; | 
| 86 |  |  |  |  |  |  | } | 
| 87 |  |  |  |  |  |  |  | 
| 88 | 0 |  |  |  |  |  | buffer_clear(&ogg_buf); | 
| 89 |  |  |  |  |  |  |  | 
| 90 | 0 |  |  |  |  |  | audio_offset += id3_size; | 
| 91 |  |  |  |  |  |  |  | 
| 92 |  |  |  |  |  |  | DEBUG_TRACE("Skipping ID3v2 tag of size %d\n", id3_size); | 
| 93 |  |  |  |  |  |  |  | 
| 94 | 0 |  |  |  |  |  | PerlIO_seek(infile, id3_size, SEEK_SET); | 
| 95 |  |  |  |  |  |  | } | 
| 96 |  |  |  |  |  |  |  | 
| 97 |  |  |  |  |  |  | while (1) { | 
| 98 |  |  |  |  |  |  | // Grab 28-byte Ogg header | 
| 99 | 165 | 50 |  |  |  |  | if ( !_check_buf(infile, &ogg_buf, 28, OGG_BLOCK_SIZE) ) { | 
| 100 | 0 |  |  |  |  |  | err = -1; | 
| 101 | 0 |  |  |  |  |  | goto out; | 
| 102 |  |  |  |  |  |  | } | 
| 103 |  |  |  |  |  |  |  | 
| 104 | 165 |  |  |  |  |  | buffer_get(&ogg_buf, ogghdr, 28); | 
| 105 |  |  |  |  |  |  |  | 
| 106 | 165 |  |  |  |  |  | audio_offset += 28; | 
| 107 |  |  |  |  |  |  |  | 
| 108 |  |  |  |  |  |  | // check that the first four bytes are 'OggS' | 
| 109 | 165 | 50 |  |  |  |  | if ( ogghdr[0] != 'O' || ogghdr[1] != 'g' || ogghdr[2] != 'g' || ogghdr[3] != 'S' ) { | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 110 | 0 |  |  |  |  |  | PerlIO_printf(PerlIO_stderr(), "Not an Ogg file (bad OggS header): %s\n", file); | 
| 111 | 0 |  |  |  |  |  | goto out; | 
| 112 |  |  |  |  |  |  | } | 
| 113 |  |  |  |  |  |  |  | 
| 114 |  |  |  |  |  |  | // Header type flag | 
| 115 | 165 |  |  |  |  |  | header_type = ogghdr[5]; | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | // Absolute granule position, used to find the first audio page | 
| 118 | 165 |  |  |  |  |  | bptr = ogghdr + 6; | 
| 119 | 165 |  |  |  |  |  | granule_pos = (uint64_t)CONVERT_INT32LE(bptr); | 
| 120 | 165 |  |  |  |  |  | bptr += 4; | 
| 121 | 165 |  |  |  |  |  | granule_pos |= (uint64_t)CONVERT_INT32LE(bptr) << 32; | 
| 122 |  |  |  |  |  |  |  | 
| 123 |  |  |  |  |  |  | // Stream serial number | 
| 124 | 165 |  |  |  |  |  | serialno = CONVERT_INT32LE((ogghdr+14)); | 
| 125 |  |  |  |  |  |  |  | 
| 126 |  |  |  |  |  |  | // Count start-of-stream pages | 
| 127 | 165 | 100 |  |  |  |  | if ( header_type & 0x02 ) { | 
| 128 | 22 |  |  |  |  |  | streams++; | 
| 129 |  |  |  |  |  |  | } | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  | // Keep track of packet count | 
| 132 | 165 | 100 |  |  |  |  | if ( !(header_type & 0x01) ) { | 
| 133 | 65 |  |  |  |  |  | packets++; | 
| 134 |  |  |  |  |  |  | } | 
| 135 |  |  |  |  |  |  |  | 
| 136 |  |  |  |  |  |  | // stop processing if we reach the 3rd packet and have no data | 
| 137 | 165 | 100 |  |  |  |  | if (packets > 2 * streams && !buffer_len(&vorbis_buf) ) { | 
|  |  | 50 |  |  |  |  |  | 
| 138 | 0 |  |  |  |  |  | break; | 
| 139 |  |  |  |  |  |  | } | 
| 140 |  |  |  |  |  |  |  | 
| 141 |  |  |  |  |  |  | // Page seq number | 
| 142 | 165 |  |  |  |  |  | pagenum = CONVERT_INT32LE((ogghdr+18)); | 
| 143 |  |  |  |  |  |  |  | 
| 144 | 165 | 50 |  |  |  |  | if (page >= 0 && page == pagenum) { | 
|  |  | 100 |  |  |  |  |  | 
| 145 | 164 |  |  |  |  |  | page++; | 
| 146 |  |  |  |  |  |  | } | 
| 147 |  |  |  |  |  |  | else { | 
| 148 | 1 |  |  |  |  |  | page = -1; | 
| 149 |  |  |  |  |  |  | DEBUG_TRACE("Missing page(s) in Ogg file: %s\n", file); | 
| 150 |  |  |  |  |  |  | } | 
| 151 |  |  |  |  |  |  |  | 
| 152 |  |  |  |  |  |  | DEBUG_TRACE("OggS page %d / packet %d at %d\n", pagenum, packets, (int)(audio_offset - 28)); | 
| 153 |  |  |  |  |  |  | DEBUG_TRACE("  granule_pos: %llu\n", granule_pos); | 
| 154 |  |  |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | // If the granule_pos > 0, we have reached the end of headers and | 
| 156 |  |  |  |  |  |  | // this is the first audio page | 
| 157 | 165 | 100 |  |  |  |  | if (granule_pos > 0 && granule_pos != -1) { | 
|  |  | 100 |  |  |  |  |  | 
| 158 |  |  |  |  |  |  | // If seeking, don't waste time on comments | 
| 159 | 22 | 100 |  |  |  |  | if (seeking) { | 
| 160 | 3 |  |  |  |  |  | break; | 
| 161 |  |  |  |  |  |  | } | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  | // Parse comments, but only if we have any extra data in the buffer | 
| 164 | 19 | 100 |  |  |  |  | if ( buffer_len(&vorbis_buf) > 0 ) { | 
| 165 | 18 |  |  |  |  |  | _parse_vorbis_comments(infile, &vorbis_buf, tags, 1); | 
| 166 |  |  |  |  |  |  | DEBUG_TRACE("  parsed vorbis comments\n"); | 
| 167 |  |  |  |  |  |  | } | 
| 168 |  |  |  |  |  |  |  | 
| 169 | 19 |  |  |  |  |  | buffer_clear(&vorbis_buf); | 
| 170 |  |  |  |  |  |  |  | 
| 171 | 19 |  |  |  |  |  | break; | 
| 172 |  |  |  |  |  |  | } | 
| 173 |  |  |  |  |  |  |  | 
| 174 |  |  |  |  |  |  | // Number of page segments | 
| 175 | 143 |  |  |  |  |  | num_segments = ogghdr[26]; | 
| 176 |  |  |  |  |  |  |  | 
| 177 |  |  |  |  |  |  | // Calculate total page size | 
| 178 | 143 |  |  |  |  |  | pagelen = ogghdr[27]; | 
| 179 | 143 | 100 |  |  |  |  | if (num_segments > 1) { | 
| 180 |  |  |  |  |  |  | int i; | 
| 181 |  |  |  |  |  |  |  | 
| 182 | 117 | 50 |  |  |  |  | if ( !_check_buf(infile, &ogg_buf, num_segments, OGG_BLOCK_SIZE) ) { | 
| 183 | 0 |  |  |  |  |  | err = -1; | 
| 184 | 0 |  |  |  |  |  | goto out; | 
| 185 |  |  |  |  |  |  | } | 
| 186 |  |  |  |  |  |  |  | 
| 187 | 2035 | 100 |  |  |  |  | for( i = 0; i < num_segments - 1; i++ ) { | 
| 188 |  |  |  |  |  |  | u_char x; | 
| 189 | 1918 |  |  |  |  |  | x = buffer_get_char(&ogg_buf); | 
| 190 | 1918 |  |  |  |  |  | pagelen += x; | 
| 191 |  |  |  |  |  |  | } | 
| 192 |  |  |  |  |  |  |  | 
| 193 | 117 |  |  |  |  |  | audio_offset += num_segments - 1; | 
| 194 |  |  |  |  |  |  | } | 
| 195 |  |  |  |  |  |  |  | 
| 196 | 143 | 50 |  |  |  |  | if ( !_check_buf(infile, &ogg_buf, pagelen, OGG_BLOCK_SIZE) ) { | 
| 197 | 0 |  |  |  |  |  | err = -1; | 
| 198 | 0 |  |  |  |  |  | goto out; | 
| 199 |  |  |  |  |  |  | } | 
| 200 |  |  |  |  |  |  |  | 
| 201 |  |  |  |  |  |  | // Still don't have enough data, must have reached the end of the file | 
| 202 | 143 | 50 |  |  |  |  | if ( buffer_len(&ogg_buf) < pagelen ) { | 
| 203 | 0 |  |  |  |  |  | PerlIO_printf(PerlIO_stderr(), "Premature end of file: %s\n", file); | 
| 204 |  |  |  |  |  |  |  | 
| 205 | 0 |  |  |  |  |  | err = -1; | 
| 206 | 0 |  |  |  |  |  | goto out; | 
| 207 |  |  |  |  |  |  | } | 
| 208 |  |  |  |  |  |  |  | 
| 209 | 143 |  |  |  |  |  | audio_offset += pagelen; | 
| 210 |  |  |  |  |  |  |  | 
| 211 |  |  |  |  |  |  | // Copy page into vorbis buffer | 
| 212 | 143 |  |  |  |  |  | buffer_append( &vorbis_buf, buffer_ptr(&ogg_buf), pagelen ); | 
| 213 |  |  |  |  |  |  | DEBUG_TRACE("  Read %d into vorbis buffer\n", pagelen); | 
| 214 |  |  |  |  |  |  |  | 
| 215 |  |  |  |  |  |  | // Process vorbis packet | 
| 216 | 143 | 100 |  |  |  |  | if ( !vorbis_type ) { | 
| 217 | 43 |  |  |  |  |  | vorbis_type = buffer_get_char(&vorbis_buf); | 
| 218 |  |  |  |  |  |  | // Verify 'vorbis' string | 
| 219 | 43 | 50 |  |  |  |  | if ( strncmp( buffer_ptr(&vorbis_buf), "vorbis", 6 ) ) { | 
| 220 | 0 |  |  |  |  |  | PerlIO_printf(PerlIO_stderr(), "Not a Vorbis file (bad vorbis header): %s\n", file); | 
| 221 | 0 |  |  |  |  |  | goto out; | 
| 222 |  |  |  |  |  |  | } | 
| 223 | 43 |  |  |  |  |  | buffer_consume( &vorbis_buf, 6 ); | 
| 224 |  |  |  |  |  |  |  | 
| 225 |  |  |  |  |  |  | DEBUG_TRACE("  Found vorbis packet type %d\n", vorbis_type); | 
| 226 |  |  |  |  |  |  | } | 
| 227 |  |  |  |  |  |  |  | 
| 228 | 143 | 100 |  |  |  |  | if (vorbis_type == 1) { | 
| 229 |  |  |  |  |  |  | // Parse info | 
| 230 |  |  |  |  |  |  | // Grab 23-byte Vorbis header | 
| 231 | 22 | 50 |  |  |  |  | if ( buffer_len(&vorbis_buf) < 23 ) { | 
| 232 | 0 |  |  |  |  |  | PerlIO_printf(PerlIO_stderr(), "Not a Vorbis file (bad vorbis header): %s\n", file); | 
| 233 | 0 |  |  |  |  |  | goto out; | 
| 234 |  |  |  |  |  |  | } | 
| 235 |  |  |  |  |  |  |  | 
| 236 | 22 |  |  |  |  |  | buffer_get(&vorbis_buf, vorbishdr, 23); | 
| 237 |  |  |  |  |  |  |  | 
| 238 | 22 |  |  |  |  |  | my_hv_store( info, "version", newSViv( CONVERT_INT32LE(vorbishdr) ) ); | 
| 239 |  |  |  |  |  |  |  | 
| 240 | 22 |  |  |  |  |  | channels = vorbishdr[4]; | 
| 241 | 22 |  |  |  |  |  | my_hv_store( info, "channels", newSViv(channels) ); | 
| 242 | 22 |  |  |  |  |  | my_hv_store( info, "stereo", newSViv( channels == 2 ? 1 : 0 ) ); | 
| 243 |  |  |  |  |  |  |  | 
| 244 | 22 |  |  |  |  |  | samplerate = CONVERT_INT32LE((vorbishdr+5)); | 
| 245 | 22 |  |  |  |  |  | my_hv_store( info, "samplerate", newSViv(samplerate) ); | 
| 246 | 22 |  |  |  |  |  | my_hv_store( info, "bitrate_upper", newSViv( CONVERT_INT32LE((vorbishdr+9)) ) ); | 
| 247 |  |  |  |  |  |  |  | 
| 248 | 22 |  |  |  |  |  | bitrate_nominal = CONVERT_INT32LE((vorbishdr+13)); | 
| 249 | 22 |  |  |  |  |  | my_hv_store( info, "bitrate_nominal", newSViv(bitrate_nominal) ); | 
| 250 | 22 |  |  |  |  |  | my_hv_store( info, "bitrate_lower", newSViv( CONVERT_INT32LE((vorbishdr+17)) ) ); | 
| 251 |  |  |  |  |  |  |  | 
| 252 | 22 |  |  |  |  |  | blocksize_0 = 2 << ((vorbishdr[21] & 0xF0) >> 4); | 
| 253 | 22 |  |  |  |  |  | my_hv_store( info, "blocksize_0", newSViv( blocksize_0 ) ); | 
| 254 | 22 |  |  |  |  |  | my_hv_store( info, "blocksize_1", newSViv( 2 << (vorbishdr[21] & 0x0F) ) ); | 
| 255 |  |  |  |  |  |  |  | 
| 256 |  |  |  |  |  |  | DEBUG_TRACE("  parsed vorbis info header\n"); | 
| 257 |  |  |  |  |  |  |  | 
| 258 | 22 |  |  |  |  |  | buffer_clear(&vorbis_buf); | 
| 259 | 22 |  |  |  |  |  | vorbis_type = 0; | 
| 260 |  |  |  |  |  |  | } | 
| 261 |  |  |  |  |  |  |  | 
| 262 |  |  |  |  |  |  | // Skip rest of this page | 
| 263 | 143 |  |  |  |  |  | buffer_consume( &ogg_buf, pagelen ); | 
| 264 | 143 |  |  |  |  |  | } | 
| 265 |  |  |  |  |  |  |  | 
| 266 | 22 |  |  |  |  |  | buffer_clear(&ogg_buf); | 
| 267 |  |  |  |  |  |  |  | 
| 268 |  |  |  |  |  |  | // audio_offset is 28 less because we read the Ogg header | 
| 269 | 22 |  |  |  |  |  | audio_offset -= 28; | 
| 270 |  |  |  |  |  |  |  | 
| 271 |  |  |  |  |  |  | // from the first packet past the comments | 
| 272 | 22 |  |  |  |  |  | my_hv_store( info, "audio_offset", newSViv(audio_offset) ); | 
| 273 |  |  |  |  |  |  |  | 
| 274 | 22 |  |  |  |  |  | audio_size = file_size - audio_offset; | 
| 275 | 22 |  |  |  |  |  | my_hv_store( info, "audio_size", newSVuv(audio_size) ); | 
| 276 |  |  |  |  |  |  |  | 
| 277 | 22 |  |  |  |  |  | my_hv_store( info, "serial_number", newSVuv(serialno) ); | 
| 278 |  |  |  |  |  |  |  | 
| 279 |  |  |  |  |  |  | // calculate average bitrate and duration | 
| 280 | 22 |  |  |  |  |  | avg_buf_size = blocksize_0 * 2; | 
| 281 | 22 | 100 |  |  |  |  | if ( file_size > avg_buf_size ) { | 
| 282 |  |  |  |  |  |  | DEBUG_TRACE("Seeking to %d to calculate bitrate/duration\n", (int)(file_size - avg_buf_size)); | 
| 283 | 17 |  |  |  |  |  | PerlIO_seek(infile, file_size - avg_buf_size, SEEK_SET); | 
| 284 |  |  |  |  |  |  | } | 
| 285 |  |  |  |  |  |  | else { | 
| 286 |  |  |  |  |  |  | DEBUG_TRACE("Seeking to %d to calculate bitrate/duration\n", (int)audio_offset); | 
| 287 | 5 |  |  |  |  |  | PerlIO_seek(infile, audio_offset, SEEK_SET); | 
| 288 |  |  |  |  |  |  | } | 
| 289 |  |  |  |  |  |  |  | 
| 290 | 22 | 50 |  |  |  |  | if ( PerlIO_read(infile, buffer_append_space(&ogg_buf, avg_buf_size), avg_buf_size) == 0 ) { | 
| 291 | 0 | 0 |  |  |  |  | if ( PerlIO_error(infile) ) { | 
| 292 | 0 |  |  |  |  |  | PerlIO_printf(PerlIO_stderr(), "Error reading: %s\n", strerror(errno)); | 
| 293 |  |  |  |  |  |  | } | 
| 294 |  |  |  |  |  |  | else { | 
| 295 | 0 |  |  |  |  |  | PerlIO_printf(PerlIO_stderr(), "File too small. Probably corrupted.\n"); | 
| 296 |  |  |  |  |  |  | } | 
| 297 |  |  |  |  |  |  |  | 
| 298 | 0 |  |  |  |  |  | err = -1; | 
| 299 | 0 |  |  |  |  |  | goto out; | 
| 300 |  |  |  |  |  |  | } | 
| 301 |  |  |  |  |  |  |  | 
| 302 |  |  |  |  |  |  | // Find sync | 
| 303 | 22 |  |  |  |  |  | bptr = (unsigned char *)buffer_ptr(&ogg_buf); | 
| 304 | 22 |  |  |  |  |  | buf_size = buffer_len(&ogg_buf); | 
| 305 | 41910 | 50 |  |  |  |  | while ( | 
| 306 |  |  |  |  |  |  | buf_size >= 14 | 
| 307 | 41910 | 100 |  |  |  |  | && (bptr[0] != 'O' || bptr[1] != 'g' || bptr[2] != 'g' || bptr[3] != 'S') | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 308 |  |  |  |  |  |  | ) { | 
| 309 | 41890 |  |  |  |  |  | bptr++; | 
| 310 | 41890 |  |  |  |  |  | buf_size--; | 
| 311 |  |  |  |  |  |  |  | 
| 312 | 41890 | 100 |  |  |  |  | if ( buf_size < 14 ) { | 
| 313 |  |  |  |  |  |  | // Give up, use less accurate bitrate for length | 
| 314 |  |  |  |  |  |  | DEBUG_TRACE("buf_size %d, using less accurate bitrate for length\n", buf_size); | 
| 315 |  |  |  |  |  |  |  | 
| 316 | 2 |  |  |  |  |  | my_hv_store( info, "song_length_ms", newSVpvf( "%d", (int)((audio_size * 8) / bitrate_nominal) * 1000) ); | 
| 317 | 2 |  |  |  |  |  | my_hv_store( info, "bitrate_average", newSViv(bitrate_nominal) ); | 
| 318 |  |  |  |  |  |  |  | 
| 319 | 2 |  |  |  |  |  | goto out; | 
| 320 |  |  |  |  |  |  | } | 
| 321 |  |  |  |  |  |  | } | 
| 322 | 20 |  |  |  |  |  | bptr += 6; | 
| 323 |  |  |  |  |  |  |  | 
| 324 |  |  |  |  |  |  | // Get absolute granule value | 
| 325 | 20 |  |  |  |  |  | granule_pos = (uint64_t)CONVERT_INT32LE(bptr); | 
| 326 | 20 |  |  |  |  |  | bptr += 4; | 
| 327 | 20 |  |  |  |  |  | granule_pos |= (uint64_t)CONVERT_INT32LE(bptr) << 32; | 
| 328 | 20 |  |  |  |  |  | bptr += 4; | 
| 329 |  |  |  |  |  |  |  | 
| 330 |  |  |  |  |  |  | // Get serial number of this page, if the serial doesn't match the beginning of the file | 
| 331 |  |  |  |  |  |  | // we have changed logical bitstreams and can't use the granule_pos for bitrate | 
| 332 | 20 |  |  |  |  |  | final_serialno = CONVERT_INT32LE((bptr)); | 
| 333 |  |  |  |  |  |  |  | 
| 334 | 33 | 100 |  |  |  |  | if ( granule_pos && samplerate && serialno == final_serialno ) { | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | // XXX: needs to adjust for initial granule value if file does not start at 0 samples | 
| 336 | 13 |  |  |  |  |  | int length = (int)((granule_pos * 1.0 / samplerate) * 1000); | 
| 337 | 13 |  |  |  |  |  | my_hv_store( info, "song_length_ms", newSVuv(length) ); | 
| 338 | 13 |  |  |  |  |  | my_hv_store( info, "bitrate_average", newSVuv( _bitrate(audio_size, length) ) ); | 
| 339 |  |  |  |  |  |  |  | 
| 340 |  |  |  |  |  |  | DEBUG_TRACE("Using granule_pos %llu / samplerate %d to calculate bitrate/duration\n", granule_pos, samplerate); | 
| 341 |  |  |  |  |  |  | } | 
| 342 |  |  |  |  |  |  | else { | 
| 343 |  |  |  |  |  |  | // Use nominal bitrate | 
| 344 | 7 |  |  |  |  |  | my_hv_store( info, "song_length_ms", newSVpvf( "%d", (int)((audio_size * 8) / bitrate_nominal) * 1000) ); | 
| 345 | 7 |  |  |  |  |  | my_hv_store( info, "bitrate_average", newSVuv(bitrate_nominal) ); | 
| 346 |  |  |  |  |  |  |  | 
| 347 |  |  |  |  |  |  | DEBUG_TRACE("Using nominal bitrate for average\n"); | 
| 348 |  |  |  |  |  |  | } | 
| 349 |  |  |  |  |  |  |  | 
| 350 |  |  |  |  |  |  | out: | 
| 351 | 22 |  |  |  |  |  | buffer_free(&ogg_buf); | 
| 352 | 22 |  |  |  |  |  | buffer_free(&vorbis_buf); | 
| 353 |  |  |  |  |  |  |  | 
| 354 | 22 | 50 |  |  |  |  | if (err) return err; | 
| 355 |  |  |  |  |  |  |  | 
| 356 | 22 |  |  |  |  |  | return 0; | 
| 357 |  |  |  |  |  |  | } | 
| 358 |  |  |  |  |  |  |  | 
| 359 |  |  |  |  |  |  | void | 
| 360 | 25 |  |  |  |  |  | _parse_vorbis_comments(PerlIO *infile, Buffer *vorbis_buf, HV *tags, int has_framing) | 
| 361 |  |  |  |  |  |  | { | 
| 362 |  |  |  |  |  |  | unsigned int len; | 
| 363 |  |  |  |  |  |  | unsigned int num_comments; | 
| 364 |  |  |  |  |  |  | char *tmp; | 
| 365 |  |  |  |  |  |  | char *bptr; | 
| 366 |  |  |  |  |  |  | SV *vendor; | 
| 367 |  |  |  |  |  |  |  | 
| 368 |  |  |  |  |  |  | // Vendor string | 
| 369 | 25 |  |  |  |  |  | len = buffer_get_int_le(vorbis_buf); | 
| 370 | 25 |  |  |  |  |  | vendor = newSVpvn( buffer_ptr(vorbis_buf), len ); | 
| 371 | 25 |  |  |  |  |  | sv_utf8_decode(vendor); | 
| 372 | 25 |  |  |  |  |  | my_hv_store( tags, "VENDOR", vendor ); | 
| 373 | 25 |  |  |  |  |  | buffer_consume(vorbis_buf, len); | 
| 374 |  |  |  |  |  |  |  | 
| 375 |  |  |  |  |  |  | // Number of comments | 
| 376 | 25 |  |  |  |  |  | num_comments = buffer_get_int_le(vorbis_buf); | 
| 377 |  |  |  |  |  |  |  | 
| 378 | 289 | 100 |  |  |  |  | while (num_comments--) { | 
| 379 | 265 |  |  |  |  |  | len = buffer_get_int_le(vorbis_buf); | 
| 380 |  |  |  |  |  |  |  | 
| 381 |  |  |  |  |  |  | // Sanity check length | 
| 382 | 265 | 100 |  |  |  |  | if ( len > buffer_len(vorbis_buf) ) { | 
| 383 |  |  |  |  |  |  | DEBUG_TRACE("invalid Vorbis comment length: %u\n", len); | 
| 384 | 1 |  |  |  |  |  | return; | 
| 385 |  |  |  |  |  |  | } | 
| 386 |  |  |  |  |  |  |  | 
| 387 | 264 |  |  |  |  |  | bptr = buffer_ptr(vorbis_buf); | 
| 388 |  |  |  |  |  |  |  | 
| 389 | 264 | 100 |  |  |  |  | if ( | 
| 390 |  |  |  |  |  |  | #ifdef _MSC_VER | 
| 391 |  |  |  |  |  |  | !strnicmp(bptr, "METADATA_BLOCK_PICTURE=", 23) | 
| 392 |  |  |  |  |  |  | #else | 
| 393 | 264 |  |  |  |  |  | !strncasecmp(bptr, "METADATA_BLOCK_PICTURE=", 23) | 
| 394 |  |  |  |  |  |  | #endif | 
| 395 |  |  |  |  |  |  | ) { | 
| 396 |  |  |  |  |  |  | // parse METADATA_BLOCK_PICTURE according to http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE | 
| 397 |  |  |  |  |  |  | AV *pictures; | 
| 398 |  |  |  |  |  |  | HV *picture; | 
| 399 |  |  |  |  |  |  | Buffer pic_buf; | 
| 400 |  |  |  |  |  |  | uint32_t pic_length; | 
| 401 |  |  |  |  |  |  |  | 
| 402 | 4 |  |  |  |  |  | buffer_consume(vorbis_buf, 23); | 
| 403 |  |  |  |  |  |  |  | 
| 404 |  |  |  |  |  |  | // Copy picture into new buffer and base64 decode it | 
| 405 | 4 |  |  |  |  |  | buffer_init(&pic_buf, len - 23); | 
| 406 | 4 |  |  |  |  |  | buffer_append( &pic_buf, buffer_ptr(vorbis_buf), len - 23 ); | 
| 407 | 4 |  |  |  |  |  | buffer_consume(vorbis_buf, len - 23); | 
| 408 |  |  |  |  |  |  |  | 
| 409 | 4 |  |  |  |  |  | _decode_base64( buffer_ptr(&pic_buf) ); | 
| 410 |  |  |  |  |  |  |  | 
| 411 | 4 |  |  |  |  |  | picture = _decode_flac_picture(infile, &pic_buf, &pic_length); | 
| 412 | 4 | 50 |  |  |  |  | if ( !picture ) { | 
| 413 | 0 |  |  |  |  |  | PerlIO_printf(PerlIO_stderr(), "Invalid Vorbis METADATA_BLOCK_PICTURE comment\n"); | 
| 414 |  |  |  |  |  |  | } | 
| 415 |  |  |  |  |  |  | else { | 
| 416 |  |  |  |  |  |  | DEBUG_TRACE("  found picture of length %d\n", pic_length); | 
| 417 |  |  |  |  |  |  |  | 
| 418 | 4 | 100 |  |  |  |  | if ( my_hv_exists(tags, "ALLPICTURES") ) { | 
| 419 | 2 |  |  |  |  |  | SV **entry = my_hv_fetch(tags, "ALLPICTURES"); | 
| 420 | 2 | 50 |  |  |  |  | if (entry != NULL) { | 
| 421 | 2 |  |  |  |  |  | pictures = (AV *)SvRV(*entry); | 
| 422 | 2 |  |  |  |  |  | av_push( pictures, newRV_noinc( (SV *)picture ) ); | 
| 423 |  |  |  |  |  |  | } | 
| 424 |  |  |  |  |  |  | } | 
| 425 |  |  |  |  |  |  | else { | 
| 426 | 2 |  |  |  |  |  | pictures = newAV(); | 
| 427 |  |  |  |  |  |  |  | 
| 428 | 2 |  |  |  |  |  | av_push( pictures, newRV_noinc( (SV *)picture ) ); | 
| 429 |  |  |  |  |  |  |  | 
| 430 | 2 |  |  |  |  |  | my_hv_store( tags, "ALLPICTURES", newRV_noinc( (SV *)pictures ) ); | 
| 431 |  |  |  |  |  |  | } | 
| 432 |  |  |  |  |  |  | } | 
| 433 |  |  |  |  |  |  |  | 
| 434 | 4 |  |  |  |  |  | buffer_free(&pic_buf); | 
| 435 |  |  |  |  |  |  | } | 
| 436 | 260 | 100 |  |  |  |  | else if ( | 
| 437 |  |  |  |  |  |  | #ifdef _MSC_VER | 
| 438 |  |  |  |  |  |  | !strnicmp(bptr, "COVERART=", 9) | 
| 439 |  |  |  |  |  |  | #else | 
| 440 | 260 |  |  |  |  |  | !strncasecmp(bptr, "COVERART=", 9) | 
| 441 |  |  |  |  |  |  | #endif | 
| 442 |  |  |  |  |  |  | ) { | 
| 443 |  |  |  |  |  |  | // decode COVERART into ALLPICTURES | 
| 444 |  |  |  |  |  |  | AV *pictures; | 
| 445 | 4 |  |  |  |  |  | HV *picture = newHV(); | 
| 446 |  |  |  |  |  |  |  | 
| 447 |  |  |  |  |  |  | // Fill in recommended default values for most of the picture hash | 
| 448 | 4 |  |  |  |  |  | my_hv_store( picture, "color_index", newSVuv(0) ); | 
| 449 | 4 |  |  |  |  |  | my_hv_store( picture, "depth", newSVuv(0) ); | 
| 450 | 4 |  |  |  |  |  | my_hv_store( picture, "description", newSVpvn("", 0) ); | 
| 451 | 4 |  |  |  |  |  | my_hv_store( picture, "height", newSVuv(0) ); | 
| 452 | 4 |  |  |  |  |  | my_hv_store( picture, "width", newSVuv(0) ); | 
| 453 | 4 |  |  |  |  |  | my_hv_store( picture, "mime_type", newSVpvn("image/", 6) ); // As recommended, real mime should be in COVERARTMIME | 
| 454 | 4 |  |  |  |  |  | my_hv_store( picture, "picture_type", newSVuv(0) ); // Other | 
| 455 |  |  |  |  |  |  |  | 
| 456 | 4 | 100 |  |  |  |  | if ( _env_true("AUDIO_SCAN_NO_ARTWORK") ) { | 
| 457 | 1 |  |  |  |  |  | my_hv_store( picture, "image_data", newSVuv(len - 9) ); | 
| 458 | 1 |  |  |  |  |  | buffer_consume(vorbis_buf, len); | 
| 459 |  |  |  |  |  |  | } | 
| 460 |  |  |  |  |  |  | else { | 
| 461 |  |  |  |  |  |  | int pic_length; | 
| 462 |  |  |  |  |  |  |  | 
| 463 | 3 |  |  |  |  |  | buffer_consume(vorbis_buf, 9); | 
| 464 | 3 |  |  |  |  |  | pic_length = _decode_base64( buffer_ptr(vorbis_buf) ); | 
| 465 |  |  |  |  |  |  | DEBUG_TRACE("  found picture of length %d\n", pic_length); | 
| 466 |  |  |  |  |  |  |  | 
| 467 | 3 |  |  |  |  |  | my_hv_store( picture, "image_data", newSVpvn( buffer_ptr(vorbis_buf), pic_length ) ); | 
| 468 | 3 |  |  |  |  |  | buffer_consume(vorbis_buf, len - 9); | 
| 469 |  |  |  |  |  |  | } | 
| 470 |  |  |  |  |  |  |  | 
| 471 | 4 | 50 |  |  |  |  | if ( my_hv_exists(tags, "ALLPICTURES") ) { | 
| 472 | 0 |  |  |  |  |  | SV **entry = my_hv_fetch(tags, "ALLPICTURES"); | 
| 473 | 0 | 0 |  |  |  |  | if (entry != NULL) { | 
| 474 | 0 |  |  |  |  |  | pictures = (AV *)SvRV(*entry); | 
| 475 | 0 |  |  |  |  |  | av_push( pictures, newRV_noinc( (SV *)picture ) ); | 
| 476 |  |  |  |  |  |  | } | 
| 477 |  |  |  |  |  |  | } | 
| 478 |  |  |  |  |  |  | else { | 
| 479 | 4 |  |  |  |  |  | pictures = newAV(); | 
| 480 |  |  |  |  |  |  |  | 
| 481 | 4 |  |  |  |  |  | av_push( pictures, newRV_noinc( (SV *)picture ) ); | 
| 482 |  |  |  |  |  |  |  | 
| 483 | 4 |  |  |  |  |  | my_hv_store( tags, "ALLPICTURES", newRV_noinc( (SV *)pictures ) ); | 
| 484 |  |  |  |  |  |  | } | 
| 485 |  |  |  |  |  |  | } | 
| 486 |  |  |  |  |  |  | else { | 
| 487 | 256 |  |  |  |  |  | New(0, tmp, (int)len + 1, char); | 
| 488 | 256 |  |  |  |  |  | buffer_get(vorbis_buf, tmp, len); | 
| 489 | 256 |  |  |  |  |  | tmp[len] = '\0'; | 
| 490 |  |  |  |  |  |  |  | 
| 491 | 256 |  |  |  |  |  | _split_vorbis_comment( tmp, tags ); | 
| 492 |  |  |  |  |  |  |  | 
| 493 | 256 |  |  |  |  |  | Safefree(tmp); | 
| 494 |  |  |  |  |  |  | } | 
| 495 |  |  |  |  |  |  | } | 
| 496 |  |  |  |  |  |  |  | 
| 497 | 24 | 100 |  |  |  |  | if (has_framing) { | 
| 498 |  |  |  |  |  |  | // Skip framing byte (Ogg only) | 
| 499 | 18 |  |  |  |  |  | buffer_consume(vorbis_buf, 1); | 
| 500 |  |  |  |  |  |  | } | 
| 501 |  |  |  |  |  |  | } | 
| 502 |  |  |  |  |  |  |  | 
| 503 |  |  |  |  |  |  | static int | 
| 504 | 3 |  |  |  |  |  | ogg_find_frame(PerlIO *infile, char *file, int offset) | 
| 505 |  |  |  |  |  |  | { | 
| 506 | 3 |  |  |  |  |  | int frame_offset = -1; | 
| 507 |  |  |  |  |  |  | uint32_t samplerate; | 
| 508 |  |  |  |  |  |  | uint32_t song_length_ms; | 
| 509 |  |  |  |  |  |  | uint64_t target_sample; | 
| 510 |  |  |  |  |  |  |  | 
| 511 |  |  |  |  |  |  | // We need to read all metadata first to get some data we need to calculate | 
| 512 | 3 |  |  |  |  |  | HV *info = newHV(); | 
| 513 | 3 |  |  |  |  |  | HV *tags = newHV(); | 
| 514 | 3 | 50 |  |  |  |  | if ( _ogg_parse(infile, file, info, tags, 1) != 0 ) { | 
| 515 | 0 |  |  |  |  |  | goto out; | 
| 516 |  |  |  |  |  |  | } | 
| 517 |  |  |  |  |  |  |  | 
| 518 | 3 | 50 |  |  |  |  | song_length_ms = SvIV( *(my_hv_fetch( info, "song_length_ms" )) ); | 
| 519 | 3 | 50 |  |  |  |  | if (offset >= song_length_ms) { | 
| 520 | 0 |  |  |  |  |  | goto out; | 
| 521 |  |  |  |  |  |  | } | 
| 522 |  |  |  |  |  |  |  | 
| 523 | 3 | 50 |  |  |  |  | samplerate = SvIV( *(my_hv_fetch( info, "samplerate" )) ); | 
| 524 |  |  |  |  |  |  |  | 
| 525 |  |  |  |  |  |  | // Determine target sample we're looking for | 
| 526 | 3 |  |  |  |  |  | target_sample = ((offset - 1) / 10) * (samplerate / 100); | 
| 527 |  |  |  |  |  |  | DEBUG_TRACE("Looking for target sample %llu\n", target_sample); | 
| 528 |  |  |  |  |  |  |  | 
| 529 | 3 |  |  |  |  |  | frame_offset = _ogg_binary_search_sample(infile, file, info, target_sample); | 
| 530 |  |  |  |  |  |  |  | 
| 531 |  |  |  |  |  |  | out: | 
| 532 |  |  |  |  |  |  | // Don't leak | 
| 533 | 3 |  |  |  |  |  | SvREFCNT_dec(info); | 
| 534 | 3 |  |  |  |  |  | SvREFCNT_dec(tags); | 
| 535 |  |  |  |  |  |  |  | 
| 536 | 3 |  |  |  |  |  | return frame_offset; | 
| 537 |  |  |  |  |  |  | } | 
| 538 |  |  |  |  |  |  |  | 
| 539 |  |  |  |  |  |  | int | 
| 540 | 3 |  |  |  |  |  | _ogg_binary_search_sample(PerlIO *infile, char *file, HV *info, uint64_t target_sample) | 
| 541 |  |  |  |  |  |  | { | 
| 542 |  |  |  |  |  |  | Buffer buf; | 
| 543 |  |  |  |  |  |  | unsigned char *bptr; | 
| 544 |  |  |  |  |  |  | unsigned int buf_size; | 
| 545 | 3 |  |  |  |  |  | int frame_offset = -1; | 
| 546 | 3 |  |  |  |  |  | int prev_frame_offset = -1; | 
| 547 | 3 |  |  |  |  |  | uint64_t granule_pos = 0; | 
| 548 | 3 |  |  |  |  |  | uint64_t prev_granule_pos = 0; | 
| 549 |  |  |  |  |  |  | uint32_t cur_serialno; | 
| 550 |  |  |  |  |  |  | off_t low; | 
| 551 |  |  |  |  |  |  | off_t high; | 
| 552 |  |  |  |  |  |  | off_t mid; | 
| 553 |  |  |  |  |  |  | int i; | 
| 554 |  |  |  |  |  |  |  | 
| 555 | 3 | 50 |  |  |  |  | off_t audio_offset = SvIV( *(my_hv_fetch( info, "audio_offset" )) ); | 
| 556 | 3 | 50 |  |  |  |  | off_t file_size    = SvIV( *(my_hv_fetch( info, "file_size" )) ); | 
| 557 | 3 | 50 |  |  |  |  | uint32_t serialno  = SvIV( *(my_hv_fetch( info, "serial_number" )) ); | 
| 558 |  |  |  |  |  |  |  | 
| 559 |  |  |  |  |  |  | // Binary search the entire file | 
| 560 | 3 |  |  |  |  |  | low  = audio_offset; | 
| 561 | 3 |  |  |  |  |  | high = file_size; | 
| 562 |  |  |  |  |  |  |  | 
| 563 |  |  |  |  |  |  | // We need enough for at least 2 packets | 
| 564 | 3 |  |  |  |  |  | buffer_init(&buf, OGG_BLOCK_SIZE * 2); | 
| 565 |  |  |  |  |  |  |  | 
| 566 | 28 | 50 |  |  |  |  | while (low <= high) { | 
| 567 |  |  |  |  |  |  | off_t packet_offset; | 
| 568 |  |  |  |  |  |  |  | 
| 569 | 28 |  |  |  |  |  | mid = low + ((high - low) / 2); | 
| 570 |  |  |  |  |  |  |  | 
| 571 |  |  |  |  |  |  | DEBUG_TRACE("  Searching for sample %llu between %d and %d (mid %d)\n", target_sample, (int)low, (int)high, (int)mid); | 
| 572 |  |  |  |  |  |  |  | 
| 573 | 28 | 50 |  |  |  |  | if (mid > file_size - 28) { | 
| 574 |  |  |  |  |  |  | DEBUG_TRACE("  Reached end of file, aborting\n"); | 
| 575 | 0 |  |  |  |  |  | frame_offset = -1; | 
| 576 | 0 |  |  |  |  |  | goto out; | 
| 577 |  |  |  |  |  |  | } | 
| 578 |  |  |  |  |  |  |  | 
| 579 | 28 | 50 |  |  |  |  | if ( (PerlIO_seek(infile, mid, SEEK_SET)) == -1 ) { | 
| 580 | 0 |  |  |  |  |  | frame_offset = -1; | 
| 581 | 0 |  |  |  |  |  | goto out; | 
| 582 |  |  |  |  |  |  | } | 
| 583 |  |  |  |  |  |  |  | 
| 584 | 28 | 50 |  |  |  |  | if ( !_check_buf(infile, &buf, 28, OGG_BLOCK_SIZE * 2) ) { | 
| 585 | 0 |  |  |  |  |  | frame_offset = -1; | 
| 586 | 0 |  |  |  |  |  | goto out; | 
| 587 |  |  |  |  |  |  | } | 
| 588 |  |  |  |  |  |  |  | 
| 589 | 28 |  |  |  |  |  | bptr = buffer_ptr(&buf); | 
| 590 | 28 |  |  |  |  |  | buf_size = buffer_len(&buf); | 
| 591 |  |  |  |  |  |  |  | 
| 592 |  |  |  |  |  |  | // Find all packets within this buffer, we need at least 2 packets | 
| 593 |  |  |  |  |  |  | // to figure out what samples we have | 
| 594 | 56 | 50 |  |  |  |  | while (buf_size >= 4) { | 
| 595 |  |  |  |  |  |  | // Save info from previous packet | 
| 596 | 56 |  |  |  |  |  | prev_frame_offset = frame_offset; | 
| 597 | 56 |  |  |  |  |  | prev_granule_pos  = granule_pos; | 
| 598 |  |  |  |  |  |  |  | 
| 599 | 205128 | 50 |  |  |  |  | while ( | 
| 600 |  |  |  |  |  |  | buf_size >= 4 | 
| 601 | 205128 | 100 |  |  |  |  | && | 
| 602 | 717 | 100 |  |  |  |  | (bptr[0] != 'O' || bptr[1] != 'g' || bptr[2] != 'g' || bptr[3] != 'S') | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 603 |  |  |  |  |  |  | ) { | 
| 604 | 205072 |  |  |  |  |  | bptr++; | 
| 605 | 205072 |  |  |  |  |  | buf_size--; | 
| 606 |  |  |  |  |  |  | } | 
| 607 |  |  |  |  |  |  |  | 
| 608 | 56 | 50 |  |  |  |  | if (buf_size < 4) { | 
| 609 |  |  |  |  |  |  | // No more packets found in buffer | 
| 610 | 0 |  |  |  |  |  | break; | 
| 611 |  |  |  |  |  |  | } | 
| 612 |  |  |  |  |  |  |  | 
| 613 |  |  |  |  |  |  | // Remember how far into the buffer this packet is | 
| 614 | 56 |  |  |  |  |  | packet_offset = buffer_len(&buf) - buf_size; | 
| 615 |  |  |  |  |  |  |  | 
| 616 | 56 |  |  |  |  |  | frame_offset = mid + packet_offset; | 
| 617 |  |  |  |  |  |  |  | 
| 618 |  |  |  |  |  |  | // Make sure we have at least the Ogg header | 
| 619 | 56 | 50 |  |  |  |  | if ( !_check_buf(infile, &buf, 28, 28) ) { | 
| 620 | 0 |  |  |  |  |  | frame_offset = -1; | 
| 621 | 0 |  |  |  |  |  | goto out; | 
| 622 |  |  |  |  |  |  | } | 
| 623 |  |  |  |  |  |  |  | 
| 624 |  |  |  |  |  |  | // Read granule_pos for this packet | 
| 625 | 56 |  |  |  |  |  | bptr = buffer_ptr(&buf); | 
| 626 | 56 |  |  |  |  |  | bptr += packet_offset + 6; | 
| 627 | 56 |  |  |  |  |  | granule_pos = (uint64_t)CONVERT_INT32LE(bptr); | 
| 628 | 56 |  |  |  |  |  | bptr += 4; | 
| 629 | 56 |  |  |  |  |  | granule_pos |= (uint64_t)CONVERT_INT32LE(bptr) << 32; | 
| 630 | 56 |  |  |  |  |  | bptr += 4; | 
| 631 | 56 |  |  |  |  |  | buf_size -= 14; | 
| 632 |  |  |  |  |  |  |  | 
| 633 |  |  |  |  |  |  | // Also read serial number, if this ever changes within a file it is a chained | 
| 634 |  |  |  |  |  |  | // file and we can't seek | 
| 635 | 56 |  |  |  |  |  | cur_serialno = CONVERT_INT32LE(bptr); | 
| 636 |  |  |  |  |  |  |  | 
| 637 | 56 | 50 |  |  |  |  | if (serialno != cur_serialno) { | 
| 638 |  |  |  |  |  |  | DEBUG_TRACE("  serial number changed to %x, aborting seek\n", cur_serialno); | 
| 639 | 0 |  |  |  |  |  | frame_offset = -1; | 
| 640 | 0 |  |  |  |  |  | goto out; | 
| 641 |  |  |  |  |  |  | } | 
| 642 |  |  |  |  |  |  |  | 
| 643 |  |  |  |  |  |  | DEBUG_TRACE("  frame offset: %d, prev_frame_offset: %d, granule_pos: %llu, prev_granule_pos %llu\n", | 
| 644 |  |  |  |  |  |  | frame_offset, prev_frame_offset, granule_pos, prev_granule_pos | 
| 645 |  |  |  |  |  |  | ); | 
| 646 |  |  |  |  |  |  |  | 
| 647 |  |  |  |  |  |  | // Break out after reading 2 packets | 
| 648 | 56 | 50 |  |  |  |  | if (granule_pos && prev_granule_pos) { | 
|  |  | 100 |  |  |  |  |  | 
| 649 | 28 |  |  |  |  |  | break; | 
| 650 |  |  |  |  |  |  | } | 
| 651 |  |  |  |  |  |  | } | 
| 652 |  |  |  |  |  |  |  | 
| 653 |  |  |  |  |  |  | // Now, we know the first (prev_granule_pos + 1) and last (granule_pos) samples | 
| 654 |  |  |  |  |  |  | // in the packet starting at frame_offset | 
| 655 |  |  |  |  |  |  |  | 
| 656 | 28 | 100 |  |  |  |  | if ((prev_granule_pos + 1) <= target_sample && granule_pos >= target_sample) { | 
|  |  | 50 |  |  |  |  |  | 
| 657 |  |  |  |  |  |  | // found frame | 
| 658 |  |  |  |  |  |  | DEBUG_TRACE("  found frame at %d\n", frame_offset); | 
| 659 | 2 |  |  |  |  |  | goto out; | 
| 660 |  |  |  |  |  |  | } | 
| 661 |  |  |  |  |  |  |  | 
| 662 | 26 | 50 |  |  |  |  | if (target_sample < (prev_granule_pos + 1)) { | 
| 663 |  |  |  |  |  |  | // Special case when very first frame has the sample | 
| 664 | 26 | 100 |  |  |  |  | if (prev_frame_offset == audio_offset) { | 
| 665 |  |  |  |  |  |  | DEBUG_TRACE("  first frame has target sample\n"); | 
| 666 | 1 |  |  |  |  |  | frame_offset = prev_frame_offset; | 
| 667 | 1 |  |  |  |  |  | break; | 
| 668 |  |  |  |  |  |  | } | 
| 669 |  |  |  |  |  |  |  | 
| 670 | 25 |  |  |  |  |  | high = mid - 1; | 
| 671 |  |  |  |  |  |  | DEBUG_TRACE("  high = %d\n", (int)high); | 
| 672 |  |  |  |  |  |  | } | 
| 673 |  |  |  |  |  |  | else { | 
| 674 | 0 |  |  |  |  |  | low = mid + 1; | 
| 675 |  |  |  |  |  |  | DEBUG_TRACE("  low = %d\n", (int)low); | 
| 676 |  |  |  |  |  |  | } | 
| 677 |  |  |  |  |  |  |  | 
| 678 |  |  |  |  |  |  | // XXX this can be pretty inefficient in some cases | 
| 679 |  |  |  |  |  |  |  | 
| 680 |  |  |  |  |  |  | // Reset and binary search again | 
| 681 | 25 |  |  |  |  |  | buffer_clear(&buf); | 
| 682 |  |  |  |  |  |  |  | 
| 683 | 25 |  |  |  |  |  | frame_offset = -1; | 
| 684 | 25 |  |  |  |  |  | granule_pos = 0; | 
| 685 |  |  |  |  |  |  | } | 
| 686 |  |  |  |  |  |  |  | 
| 687 |  |  |  |  |  |  | out: | 
| 688 | 3 |  |  |  |  |  | buffer_free(&buf); | 
| 689 |  |  |  |  |  |  |  | 
| 690 | 3 |  |  |  |  |  | return frame_offset; | 
| 691 |  |  |  |  |  |  | } | 
| 692 |  |  |  |  |  |  |  |