Example #1
0
static void
_parse_wav_peak(ScanData s, Buffer *buf, uint32_t chunk_size, uint8_t big_endian)
{
  uint16_t channels  = 0;
  AV *peaklist = newAV();
  
  SV **entry = my_hv_fetch( info, "channels" );
  if ( entry != NULL ) {
    channels = SvIV(*entry);
  }
  
  // Skip version/timestamp
  buffer_consume(buf, 8);
  
  while ( channels-- ) {
    HV *peak = newHV();
    
    my_hv_store( peak, "value", newSVnv( big_endian ? buffer_get_float32(buf) : buffer_get_float32_le(buf) ) );
    my_hv_store( peak, "position", newSVuv( big_endian ? buffer_get_int(buf) : buffer_get_int_le(buf) ) );
    
    av_push( peaklist, newRV_noinc( (SV *)peak) );
  }
  
  my_hv_store( info, "peak", newRV_noinc( (SV *)peaklist ) );
}
Example #2
0
void
_parse_wav_fmt(Buffer *buf, uint32_t chunk_size, HV *info)
{
  uint16_t format = buffer_get_short_le(buf);
  
  my_hv_store( info, "format", newSVuv(format) );
  my_hv_store( info, "channels", newSVuv( buffer_get_short_le(buf) ) );
  my_hv_store( info, "samplerate", newSVuv( buffer_get_int_le(buf) ) );
  my_hv_store( info, "bitrate", newSVuv( buffer_get_int_le(buf) * 8 ) );
  my_hv_store( info, "block_align", newSVuv( buffer_get_short_le(buf) ) );
  my_hv_store( info, "bits_per_sample", newSVuv( buffer_get_short_le(buf) ) );
  
  if ( chunk_size > 16 ) {
    uint16_t extra_len = buffer_get_short_le(buf);
    
    // Bug 14462, a WAV file with only an 18-byte fmt chunk should ignore extra_len bytes
    if (extra_len && chunk_size > 18) {
      DEBUG_TRACE(" skipping extra_len bytes in fmt: %d\n", extra_len);
      buffer_consume(buf, extra_len);
    }
  }
}
Example #3
0
static void
_parse_wav_fmt(ScanData s, Buffer *buf, uint32_t chunk_size)
{
  uint16_t format = buffer_get_short_le(buf);
  
  //my_hv_store( info, "format", newSVuv(format) );
  
  s->streams[0].channels    = buffer_get_short_le(buf);
  s->streams[0].samplerate  = buffer_get_int_le(buf);
  s->streams[0].bitrate     = s->bitrate = buffer_get_int_le(buf) * 8;
  s->streams[0].block_align = buffer_get_short_le(buf);
  s->streams[0].bit_depth   = buffer_get_short_le(buf);
  
  if ( chunk_size > 16 ) {
    uint16_t extra_len = buffer_get_short_le(buf);
    
    // Bug 14462, a WAV file with only an 18-byte fmt chunk should ignore extra_len bytes
    if (extra_len && chunk_size > 18) {
      LOG_DEBUG(" skipping extra_len bytes in fmt: %d\n", extra_len);
      buffer_consume(buf, extra_len);
    }
  }
}
Example #4
0
int
image_bmp_read_header(image *im)
{
    int offset, palette_colors;

    buffer_consume(im->buf, 10);

    offset = buffer_get_int_le(im->buf);
    buffer_consume(im->buf, 4);
    im->width  = buffer_get_int_le(im->buf);
    im->height = buffer_get_int_le(im->buf);
    buffer_consume(im->buf, 2);
    im->bpp = buffer_get_short_le(im->buf);
    im->compression = buffer_get_int_le(im->buf);

    DEBUG_TRACE("BMP offset %d, width %d, height %d, bpp %d, compression %d\n",
                offset, im->width, im->height, im->bpp, im->compression);

    if (im->compression > 3) { // JPEG/PNG
        warn("Image::Scale unsupported BMP compression type: %d (%s)\n", im->compression, SvPVX(im->path));
        return 0;
    }

    // Negative height indicates a flipped image
    if (im->height < 0) {
        croak("flipped\n");
        im->flipped = 1;
        im->height = abs(im->height);
    }

    // Not used during reading, but lets output PNG be correct
    im->channels = 4;

    // Skip BMP size, resolution
    buffer_consume(im->buf, 12);

    palette_colors = buffer_get_int_le(im->buf);

    // Skip number of important colors
    buffer_consume(im->buf, 4);

    // < 16-bit always has a palette
    if (!palette_colors && im->bpp < 16) {
        switch (im->bpp) {
        case 8:
            palette_colors = 256;
            break;
        case 4:
            palette_colors = 16;
            break;
        case 1:
            palette_colors = 2;
            break;
        }
    }

    DEBUG_TRACE("palette_colors %d\n", palette_colors);
    if (palette_colors) {
        // Read palette
        int i;

        if (palette_colors > 256) {
            warn("Image::Scale cannot read BMP with palette > 256 colors (%s)\n", SvPVX(im->path));
            return 0;
        }

        New(0, im->palette, 1, palette);

        for (i = 0; i < palette_colors; i++) {
            int b = buffer_get_char(im->buf);
            int g = buffer_get_char(im->buf);
            int r = buffer_get_char(im->buf);
            buffer_consume(im->buf, 1);

            im->palette->colors[i] = COL(r, g, b);
            DEBUG_TRACE("palette %d = %08x\n", i, im->palette->colors[i]);
        }
    }
    else if (im->compression == BMP_BI_BITFIELDS) {
        int pos, bit, i;

        if (im->bpp == 16) {
            // Read 16-bit bitfield masks
            for (i = 0; i < 3; i++) {
                masks[i] = buffer_get_int_le(im->buf);

                // Determine shift value
                pos = 0;
                bit = masks[i] & -masks[i];
                while (bit) {
                    pos++;
                    bit >>= 1;
                }
                shifts[i] = pos - 1;

                // green can be 6 bits
                if (i == 1) {
                    if (masks[1] == 0x7e0)
                        ncolors[1] = (1 << 6) - 1;
                    else
                        ncolors[1] = (1 << 5) - 1;
                }

                DEBUG_TRACE("16bpp mask %d: %08x >> %d, ncolors %d\n", i, masks[i], shifts[i], ncolors[i]);
            }
        }
        else { // 32-bit bitfields
            // Read 32-bit bitfield masks
            for (i = 0; i < 3; i++) {
Example #5
0
                // green can be 6 bits
                if (i == 1) {
                    if (masks[1] == 0x7e0)
                        ncolors[1] = (1 << 6) - 1;
                    else
                        ncolors[1] = (1 << 5) - 1;
                }

                DEBUG_TRACE("16bpp mask %d: %08x >> %d, ncolors %d\n", i, masks[i], shifts[i], ncolors[i]);
            }
        }
        else { // 32-bit bitfields
            // Read 32-bit bitfield masks
            for (i = 0; i < 3; i++) {
                masks[i] = buffer_get_int_le(im->buf);

                // Determine shift value
                pos = 0;
                bit = masks[i] & -masks[i];
                while (bit) {
                    pos++;
                    bit >>= 1;
                }
                shifts[i] = pos - 1;

                DEBUG_TRACE("32bpp mask %d: %08x >> %d\n", i, masks[i], shifts[i]);
            }
        }
    }
Example #6
0
static void
_parse_wav(ScanData s, Buffer *buf)
{
  uint32_t offset = 12;
  
  s->type_name = "wav";
  mediascan_add_StreamData(s, 1);
  
  while ( offset < s->size - 8 ) {
    char chunk_id[5];
    uint32_t chunk_size;
    
    // Verify we have at least 8 bytes
    if ( !buffer_check_load(buf, s->fp, 8, BLOCK_SIZE) ) {
      return;
    }
    
    strncpy( chunk_id, (char *)buffer_ptr(buf), 4 );
    chunk_id[4] = '\0';
    buffer_consume(buf, 4);
    
    chunk_size = buffer_get_int_le(buf);
    
    // Adjust for padding
    if ( chunk_size % 2 ) {
      chunk_size++;
    }
    
    offset += 8;
    
    LOG_DEBUG("%s size %d\n", chunk_id, chunk_size);
    
    // Seek past data, everything else we parse
    // XXX: Are there other large chunks we should ignore?
    if ( !strcmp( chunk_id, "data" ) ) {      
      s->audio_offset = offset;
      s->audio_size   = chunk_size;
      
      // Calculate duration, unless we already know it (i.e. from 'fact')
      if ( !s->duration_ms ) {
        if (s->bitrate) {
          s->duration_ms = (chunk_size / (s->bitrate / 8.)) * 1000;
        }
      }
      
      // sanity check size, this is inside the data chunk code
      // to support setting audio_offset even when the data size is wrong
      if (chunk_size > s->size - offset) {
        LOG_DEBUG("data size > file_size, skipping\n");
        return;
      }
      
      // Seek past data if there are more chunks after it
      if ( s->size > offset + chunk_size ) {
        fseek(s->fp, offset + chunk_size, SEEK_SET);
      }
      
      buffer_clear(buf);
    }
    else if ( !strcmp( chunk_id, "id3 " ) || !strcmp( chunk_id, "ID3 " ) || !strcmp( chunk_id, "ID32" ) ) {
      // Read header to verify version
      unsigned char *bptr = buffer_ptr(buf);
      
      if (
        (bptr[0] == 'I' && bptr[1] == 'D' && bptr[2] == '3') &&
        bptr[3] < 0xff && bptr[4] < 0xff &&
        bptr[6] < 0x80 && bptr[7] < 0x80 && bptr[8] < 0x80 && bptr[9] < 0x80
      ) {        
        // Start parsing ID3 from offset
        //parse_id3(infile, file, info, tags, offset, file_size);
      }
      
      // Seek past ID3 and clear buffer
      fseek(s->fp, offset + chunk_size, SEEK_SET);
      buffer_clear(buf);
    }
    else {
      // sanity check size
      if (chunk_size > s->size - offset) {
        LOG_DEBUG("chunk_size > file_size, skipping\n");
        return;
      }
      
      // Make sure we have enough data
      if ( !buffer_check_load(buf, s->fp, chunk_size, BLOCK_SIZE) ) {
        return;
      }
      
      if ( !strcmp( chunk_id, "fmt " ) ) {
        _parse_wav_fmt(s, buf, chunk_size);
      }
      else if ( !strcmp( chunk_id, "LIST" ) ) {
        //_parse_wav_list(buf, chunk_size, tags);
      }
      else if ( !strcmp( chunk_id, "PEAK" ) ) {
        _parse_wav_peak(s, buf, chunk_size, 0);
      }
      else if ( !strcmp( chunk_id, "fact" ) ) {
        // A 4-byte fact chunk in a non-PCM wav is the number of samples
        // Use it to calculate duration
        if ( chunk_size == 4 ) {
          uint32_t num_samples = buffer_get_int_le(buf);
          if (s->streams[0].samplerate) {
            s->duration_ms = (num_samples * 1000) / s->streams[0].samplerate;
          }
        }
        else {
          // Unknown, skip it
          buffer_consume(buf, chunk_size);
        }
      }
      else {
        if ( 
             !strcmp(chunk_id, "SAUR") // Wavosour data chunk
          || !strcmp(chunk_id, "otom") // Wavosaur?
          || !strcmp(chunk_id, "PAD ") // Padding
        ) {
          // Known chunks to skip
        }
        else {
          // Warn about unknown chunks so we can investigate them
          LOG_DEBUG("Unhandled WAV chunk %s size %d (skipped)\n", chunk_id, chunk_size);
        }
        
        buffer_consume(buf, chunk_size);
      }
    }
    
    offset += chunk_size;
  }
}
Example #7
0
int
wav_scan(ScanData s)
{
  int ret = 1;
  Buffer buf;
  uint32_t chunk_size;
  
  buffer_init(&buf, BLOCK_SIZE);
  
  if ( !buffer_check_load(&buf, s->fp, 12, BLOCK_SIZE) ) {
    ret = 0;
    goto out;
  }
  
  if ( !strncmp( (char *)buffer_ptr(&buf), "RIFF", 4 ) ) {
    // We've got a RIFF file
    buffer_consume(&buf, 4);
  
    chunk_size = buffer_get_int_le(&buf);
    
    // Check format
    if ( strncmp( (char *)buffer_ptr(&buf), "WAVE", 4 ) ) {
      LOG_ERROR("Invalid WAV file: missing WAVE header: %s\n", s->path);
      ret = 0;
      goto out;
    }
    
    buffer_consume(&buf, 4);
    
    _parse_wav(s, &buf);
  }
  else if ( !strncmp( (char *)buffer_ptr(&buf), "FORM", 4 ) ) {
    // We've got an AIFF file
    char *bptr;
    
    buffer_consume(&buf, 4);
    
    chunk_size = buffer_get_int(&buf);
    
    // Check format
    bptr = buffer_ptr(&buf);
    if ( bptr[0] == 'A' && bptr[1] == 'I' && bptr[2] == 'F' && (bptr[3] == 'F' || bptr[3] == 'C') ) {
      buffer_consume(&buf, 4);

      //_parse_aiff(s, &buf);
    }
    else {
      LOG_ERROR("Invalid AIFF file: missing AIFF header: %s\n", s->path);
      ret = 0;
      goto out;
    }
  }
  else {
    LOG_ERROR("Invalid WAV file: missing RIFF header: %s\n", s->path);
    ret = 0;
    goto out;
  }
  
out:
  buffer_free(&buf);
  
  return ret;
}
Example #8
0
int
_wavpack_parse_old(wvpinfo *wvp)
{
  int ret = 1;
  char chunk_id[5];
  uint32_t chunk_size;
  WavpackHeader3 wphdr;
  WaveHeader3 wavhdr;
  unsigned char *bptr;
  uint32_t total_samples;
  uint32_t song_length_ms;
  
  Zero(&wavhdr, sizeof(wavhdr), char);
  Zero(&wphdr, sizeof(wphdr), char);
  
  DEBUG_TRACE("Parsing old WavPack version\n");
  
  // Verify RIFF header
  if ( strncmp( (char *)buffer_ptr(wvp->buf), "RIFF", 4 ) ) {
    PerlIO_printf(PerlIO_stderr(), "Invalid WavPack file: missing RIFF header: %s\n", wvp->file);
    ret = 0;
    goto out;
  }
  
  buffer_consume(wvp->buf, 4);
    
  chunk_size = buffer_get_int_le(wvp->buf);
    
  // Check format
  if ( strncmp( (char *)buffer_ptr(wvp->buf), "WAVE", 4 ) ) {
    PerlIO_printf(PerlIO_stderr(), "Invalid WavPack file: missing WAVE header: %s\n", wvp->file);
    ret = 0;
    goto out;
  }
  
  buffer_consume(wvp->buf, 4);
  
  wvp->file_offset += 12;
  
  // Verify we have at least 8 bytes
  if ( !_check_buf(wvp->infile, wvp->buf, 8, WAVPACK_BLOCK_SIZE) ) {
    ret = 0;
    goto out;
  }
  
  // loop through all chunks, read fmt, and break at data
  while ( buffer_len(wvp->buf) >= 8 ) {
    strncpy( chunk_id, (char *)buffer_ptr(wvp->buf), 4 );
    chunk_id[4] = '\0';
    buffer_consume(wvp->buf, 4);
    
    chunk_size = buffer_get_int_le(wvp->buf);
    
    wvp->file_offset += 8;
    
    // Adjust for padding
    if ( chunk_size % 2 ) {
      chunk_size++;
    }
    
    DEBUG_TRACE("  %s size %d\n", chunk_id, chunk_size);
    
    if ( !strcmp( chunk_id, "data" ) ) {
      break;
    }
    
    wvp->file_offset += chunk_size;
    
    if ( !strcmp( chunk_id, "fmt " ) ) {
      if ( !_check_buf(wvp->infile, wvp->buf, chunk_size, WAV_BLOCK_SIZE) ) {
        ret = 0;
        goto out;
      }
      
      if (chunk_size < sizeof(wavhdr)) {
        ret = 0;
        goto out;
      }
      
      // Read wav header
      wavhdr.FormatTag      = buffer_get_short_le(wvp->buf);
      wavhdr.NumChannels    = buffer_get_short_le(wvp->buf);
      wavhdr.SampleRate     = buffer_get_int_le(wvp->buf);
      wavhdr.BytesPerSecond = buffer_get_int_le(wvp->buf);
      wavhdr.BlockAlign     = buffer_get_short_le(wvp->buf);
      wavhdr.BitsPerSample  = buffer_get_short_le(wvp->buf);
      
      // Skip rest of fmt chunk if necessary
      if (chunk_size > 16) {
        _wavpack_skip(wvp, chunk_size - 16);
      }
    }
    else {
      // Skip it
      _wavpack_skip(wvp, chunk_size);
    }
    
    // Verify we have at least 8 bytes
    if ( !_check_buf(wvp->infile, wvp->buf, 8, WAVPACK_BLOCK_SIZE) ) {
      ret = 0;
      goto out;
    }
  }
  
  // Verify wav header, this code comes from unpack3.c
  if (
    wavhdr.FormatTag != 1 || !wavhdr.NumChannels || wavhdr.NumChannels > 2 ||
    !wavhdr.SampleRate || wavhdr.BitsPerSample < 16 || wavhdr.BitsPerSample > 24 ||
    wavhdr.BlockAlign / wavhdr.NumChannels > 3 || wavhdr.BlockAlign % wavhdr.NumChannels ||
    wavhdr.BlockAlign / wavhdr.NumChannels < (wavhdr.BitsPerSample + 7) / 8
  ) {
    ret = 0;
    goto out;
  }
  
  // chunk_size here is the size of the data chunk
  total_samples = chunk_size / wavhdr.NumChannels / ((wavhdr.BitsPerSample > 16) ? 3 : 2);
  
  // read WavpackHeader3 (differs for each version)
  bptr = buffer_ptr(wvp->buf);
  if ( bptr[0] != 'w' || bptr[1] != 'v' || bptr[2] != 'p' || bptr[3] != 'k' ) {
    PerlIO_printf(PerlIO_stderr(), "Invalid WavPack file: missing wvpk header: %s\n", wvp->file);
    ret = 0;
    goto out;
  }
  
  buffer_consume(wvp->buf, 4);
  
  wphdr.ckSize  = buffer_get_int_le(wvp->buf);
  wphdr.version = buffer_get_short_le(wvp->buf);
  
  if (wphdr.version >= 2) {
    wphdr.bits = buffer_get_short_le(wvp->buf);
  }
  
  if (wphdr.version == 3) {
    wphdr.flags         = buffer_get_short_le(wvp->buf);
    wphdr.shift         = buffer_get_short_le(wvp->buf);
    wphdr.total_samples = buffer_get_int_le(wvp->buf);
    
    total_samples = wphdr.total_samples;
  }
  
  DEBUG_TRACE("wvpk header @ %llu:\n", wvp->file_offset);
  DEBUG_TRACE("  size: %u\n", wphdr.ckSize);
  DEBUG_TRACE("  version: %d\n", wphdr.version);
  DEBUG_TRACE("  bits: 0x%x\n", wphdr.bits);
  DEBUG_TRACE("  flags: 0x%x\n", wphdr.flags);
  DEBUG_TRACE("  shift: 0x%x\n", wphdr.shift);
  DEBUG_TRACE("  total_samples: %d\n", wphdr.total_samples);
  
  my_hv_store( wvp->info, "encoder_version", newSVuv(wphdr.version) );
  my_hv_store( wvp->info, "bits_per_sample", newSVuv(wavhdr.BitsPerSample) );
  my_hv_store( wvp->info, "channels", newSVuv(wavhdr.NumChannels) );
  my_hv_store( wvp->info, "samplerate", newSVuv(wavhdr.SampleRate) );
  my_hv_store( wvp->info, "total_samples", newSVuv(total_samples) );
  
  song_length_ms = ((total_samples * 1.0) / wavhdr.SampleRate) * 1000;
  my_hv_store( wvp->info, "song_length_ms", newSVuv(song_length_ms) );
  my_hv_store( wvp->info, "bitrate", newSVuv( _bitrate(wvp->file_size - wvp->audio_offset, song_length_ms) ) );
  
out:
  return ret;
}
Example #9
0
int
_wavpack_parse_block(wvpinfo *wvp)
{
  unsigned char *bptr;
  uint16_t remaining;
  
  bptr = buffer_ptr(wvp->buf);
  
  // Verify wvpk signature
  if ( bptr[0] != 'w' || bptr[1] != 'v' || bptr[2] != 'p' || bptr[3] != 'k' ) {
    DEBUG_TRACE("Invalid wvpk header at %llu\n", wvp->file_offset);
    return 1;
  }
  
  buffer_consume(wvp->buf, 4);
  
  wvp->header->ckSize        = buffer_get_int_le(wvp->buf);
  wvp->header->version       = buffer_get_short_le(wvp->buf);
  wvp->header->track_no      = buffer_get_char(wvp->buf);
  wvp->header->index_no      = buffer_get_char(wvp->buf);
  wvp->header->total_samples = buffer_get_int_le(wvp->buf);
  wvp->header->block_index   = buffer_get_int_le(wvp->buf);
  wvp->header->block_samples = buffer_get_int_le(wvp->buf);
  wvp->header->flags         = buffer_get_int_le(wvp->buf);
  wvp->header->crc           = buffer_get_int_le(wvp->buf);
  
  DEBUG_TRACE("wvpk header @ %llu:\n", wvp->file_offset);
  DEBUG_TRACE("  size: %u\n", wvp->header->ckSize);
  DEBUG_TRACE("  version: 0x%x\n", wvp->header->version);
  DEBUG_TRACE("  track_no: 0x%x\n", wvp->header->track_no);
  DEBUG_TRACE("  index_no: 0x%x\n", wvp->header->index_no);
  DEBUG_TRACE("  total_samples: %u\n", wvp->header->total_samples);
  DEBUG_TRACE("  block_index: %u\n", wvp->header->block_index);
  DEBUG_TRACE("  block_samples: %u\n", wvp->header->block_samples);
  DEBUG_TRACE("  flags: 0x%x\n", wvp->header->flags);
  DEBUG_TRACE("  crc: 0x%x\n", wvp->header->crc);
  
  wvp->file_offset += 32;
  
  my_hv_store( wvp->info, "encoder_version", newSVuv(wvp->header->version) );
  
  if (wvp->header->version < 0x4) {
    // XXX old version and not handled by 'R' check above for old version
    PerlIO_printf(PerlIO_stderr(), "Unsupported old WavPack version: 0x%x\n", wvp->header->version);
    return 1;
  }
  
  // Read data from flags
  my_hv_store( wvp->info, "bits_per_sample", newSVuv( 8 * ((wvp->header->flags & 0x3) + 1) ) );

  // Encoding mode
  my_hv_store( wvp->info, (wvp->header->flags & 0x8) ? "hybrid" : "lossless", newSVuv(1) );
  
  {
    // samplerate, may be overridden by a later ID_SAMPLE_RATE metadata block
    uint32_t samplerate_index = (wvp->header->flags & 0x7800000) >> 23;
    if ( samplerate_index >= 0 && samplerate_index < 0xF ) {
      my_hv_store( wvp->info, "samplerate", newSVuv( wavpack_sample_rates[samplerate_index] ) );
    }
    else {
      // Default to 44.1 just in case
      my_hv_store( wvp->info, "samplerate", newSVuv(44100) );
    }
  }
  
  // Channels, may be overridden by a later ID_CHANNEL_INFO metadata block
  my_hv_store( wvp->info, "channels", newSVuv( (wvp->header->flags & 0x4) ? 1 : 2 ) );
  
  // Parse metadata sub-blocks
  remaining = wvp->header->ckSize - 24; // ckSize is 8 less than the block size
  
  // If block_samples is 0, we need to skip to the next block
  if ( !wvp->header->block_samples ) {
    wvp->file_offset += remaining;
    _wavpack_skip(wvp, remaining);
    return 0;
  }
  
  while (remaining > 0) {
    // Read sub-block header (2-4 bytes)
    unsigned char id;
    uint32_t size;
    
    DEBUG_TRACE("remaining: %d\n", remaining);
    
    if ( !_check_buf(wvp->infile, wvp->buf, 4, WAVPACK_BLOCK_SIZE) ) {
      return 0;
    }
    
    id = buffer_get_char(wvp->buf);
    remaining--;
    
    // Size is in words
    if (id & ID_LARGE) {
      // 24-bit large size
      id &= ~ID_LARGE;
      size = buffer_get_int24_le(wvp->buf) << 1;
      remaining -= 3;
      DEBUG_TRACE("  ID_LARGE, changed to %x\n", id);
    }
    else {
      // 8-bit size
      size = buffer_get_char(wvp->buf) << 1;
      remaining--;
    }
    
    if (id & ID_ODD_SIZE) {
      id &= ~ID_ODD_SIZE;
      size--;
      DEBUG_TRACE("  ID_ODD_SIZE, changed to %x\n", id);
    }
    
    if ( id == ID_WV_BITSTREAM || !size ) {
      // Found the bitstream, don't read any farther
      DEBUG_TRACE("  Sub-Chunk: WV_BITSTREAM (size %u)\n", size);
      break;
    }
    
    // We only care about 0x27 (ID_SAMPLE_RATE) and 0xd (ID_CHANNEL_INFO)    
    switch (id) {
    case ID_SAMPLE_RATE:
      DEBUG_TRACE("  Sub-Chunk: ID_SAMPLE_RATE (size: %u)\n", size);
      _wavpack_parse_sample_rate(wvp, size);
      break;
      
    case ID_CHANNEL_INFO:
      DEBUG_TRACE("  Sub-Chunk: ID_CHANNEL_INFO (size: %u)\n", size);
      _wavpack_parse_channel_info(wvp, size);
      break;
      
    default: 
      // Skip it
      DEBUG_TRACE("  Sub-Chunk: %x (size: %u) (skipped)\n", id, size);
      _wavpack_skip(wvp, size);
    }
    
    remaining -= size;
    
    // If size was odd, skip a byte
    if (size & 0x1) {
      if ( buffer_len(wvp->buf) ) {
        buffer_consume(wvp->buf, 1);
      }
      else {
        _wavpack_skip(wvp, 1);
      }
      
      remaining--;
    }
  }
  
  // Calculate bitrate
  if ( wvp->header->total_samples && wvp->file_size > 0 ) {
    SV **samplerate = my_hv_fetch( wvp->info, "samplerate" );
    if (samplerate != NULL) {      
      uint32_t song_length_ms = ((wvp->header->total_samples * 1.0) / SvIV(*samplerate)) * 1000;
      my_hv_store( wvp->info, "song_length_ms", newSVuv(song_length_ms) );
      my_hv_store( wvp->info, "bitrate", newSVuv( _bitrate(wvp->file_size - wvp->audio_offset, song_length_ms) ) );
      my_hv_store( wvp->info, "total_samples", newSVuv(wvp->header->total_samples) );
    }
  }
  
  return 1;
}
Example #10
0
void
_parse_wav(PerlIO *infile, Buffer *buf, char *file, uint32_t file_size, HV *info, HV *tags)
{
  uint32_t offset = 12;
  
  while ( offset < file_size - 8 ) {
    char chunk_id[5];
    uint32_t chunk_size;
    
    // Verify we have at least 8 bytes
    if ( !_check_buf(infile, buf, 8, WAV_BLOCK_SIZE) ) {
      return;
    }
    
    strncpy( chunk_id, (char *)buffer_ptr(buf), 4 );
    chunk_id[4] = '\0';
    buffer_consume(buf, 4);
    
    chunk_size = buffer_get_int_le(buf);
    
    // Adjust for padding
    if ( chunk_size % 2 ) {
      chunk_size++;
    }
    
    offset += 8;
    
    DEBUG_TRACE("%s size %d\n", chunk_id, chunk_size);
    
    // Seek past data, everything else we parse
    // XXX: Are there other large chunks we should ignore?
    if ( !strcmp( chunk_id, "data" ) ) {
      SV **bitrate;
      
      my_hv_store( info, "audio_offset", newSVuv(offset) );
      my_hv_store( info, "audio_size", newSVuv(chunk_size) );
      
      // Calculate duration, unless we already know it (i.e. from 'fact')
      if ( !my_hv_fetch( info, "song_length_ms" ) ) {
        bitrate = my_hv_fetch( info, "bitrate" );
        if (bitrate != NULL) {
          my_hv_store( info, "song_length_ms", newSVuv( (chunk_size / (SvIV(*bitrate) / 8.)) * 1000 ) );
        }
      }
      
      // sanity check size, this is inside the data chunk code
      // to support setting audio_offset even when the data size is wrong
      if (chunk_size > file_size - offset) {
        DEBUG_TRACE("data size > file_size, skipping\n");
        return;
      }
      
      // Seek past data if there are more chunks after it
      if ( file_size > offset + chunk_size ) {
        PerlIO_seek(infile, offset + chunk_size, SEEK_SET);
      }
      
      buffer_clear(buf);
    }
    else if ( !strcmp( chunk_id, "id3 " ) || !strcmp( chunk_id, "ID3 " ) || !strcmp( chunk_id, "ID32" ) ) {
      // Read header to verify version
      unsigned char *bptr = buffer_ptr(buf);
      
      if (
        (bptr[0] == 'I' && bptr[1] == 'D' && bptr[2] == '3') &&
        bptr[3] < 0xff && bptr[4] < 0xff &&
        bptr[6] < 0x80 && bptr[7] < 0x80 && bptr[8] < 0x80 && bptr[9] < 0x80
      ) {        
        // Start parsing ID3 from offset
        parse_id3(infile, file, info, tags, offset, file_size);
      }
      
      // Seek past ID3 and clear buffer
      PerlIO_seek(infile, offset + chunk_size, SEEK_SET);
      buffer_clear(buf);
    }
    else {
      // sanity check size
      if (chunk_size > file_size - offset) {
        DEBUG_TRACE("chunk_size > file_size, skipping\n");
        return;
      }
      
      // Make sure we have enough data
      if ( !_check_buf(infile, buf, chunk_size, WAV_BLOCK_SIZE) ) {
        return;
      }
      
      if ( !strcmp( chunk_id, "fmt " ) ) {
        _parse_wav_fmt(buf, chunk_size, info);
      }
      else if ( !strcmp( chunk_id, "LIST" ) ) {
        _parse_wav_list(buf, chunk_size, tags);
      }
      else if ( !strcmp( chunk_id, "PEAK" ) ) {
        _parse_wav_peak(buf, chunk_size, info, 0);
      }
      else if ( !strcmp( chunk_id, "fact" ) ) {
        // A 4-byte fact chunk in a non-PCM wav is the number of samples
        // Use it to calculate duration
        if ( chunk_size == 4 ) {
          uint32_t num_samples = buffer_get_int_le(buf);
          SV **samplerate = my_hv_fetch( info, "samplerate" );
          if (samplerate != NULL) {
            my_hv_store( info, "song_length_ms", newSVuv( (num_samples * 1000) / SvIV(*samplerate) ) );
          }
        }
        else {
          // Unknown, skip it
          buffer_consume(buf, chunk_size);
        }
      }
      else {
        if ( 
             !strcmp(chunk_id, "SAUR") // Wavosour data chunk
          || !strcmp(chunk_id, "otom") // Wavosaur?
          || !strcmp(chunk_id, "PAD ") // Padding
        ) {
          // Known chunks to skip
        }
        else {
          // Warn about unknown chunks so we can investigate them
          PerlIO_printf(PerlIO_stderr(), "Unhandled WAV chunk %s size %d (skipped)\n", chunk_id, chunk_size);
        }
        
        buffer_consume(buf, chunk_size);
      }
    }
    
    offset += chunk_size;
  }
}
Example #11
0
void
_parse_wav_list(Buffer *buf, uint32_t chunk_size, HV *tags)
{
  char type_id[5];
  uint32_t pos = 4;
  
  strncpy( type_id, (char *)buffer_ptr(buf), 4 );
  type_id[4] = '\0';
  buffer_consume(buf, 4);
  
  DEBUG_TRACE("  LIST type %s\n", type_id);
  
  if ( !strcmp( type_id, "adtl" ) ) {
    // XXX need test file
    PerlIO_printf(PerlIO_stderr(), "Unhandled LIST type adtl\n");
    buffer_consume(buf, chunk_size - 4);
  }
  else if ( !strcmp( type_id, "INFO" ) ) {
    while ( pos < chunk_size ) {
      uint32_t len;
      uint32_t nulls = 0;
      SV *key;
      SV *value;
      unsigned char *bptr;
      
      key = newSVpvn( buffer_ptr(buf), 4 );
      buffer_consume(buf, 4);
      pos += 4;
      
      len = buffer_get_int_le(buf);
      
      // Bug 12250, apparently some WAV files don't use the padding byte
      // so we can't read them.
      if ( len > chunk_size - pos ) {
        PerlIO_printf(PerlIO_stderr(), "Invalid data in WAV LIST INFO chunk (len %d > chunk_size - pos %d)\n", len, chunk_size - pos);
        break;
      }
      
      pos += 4 + len;
      
      // Bug 14946, Strip any nulls from the end of the value
      bptr = buffer_ptr(buf);
      while ( len && bptr[len - 1] == '\0' ) {
        len--;
        nulls++;
      }
          
      value = newSVpvn( buffer_ptr(buf), len );
      buffer_consume(buf, len + nulls);
      
      DEBUG_TRACE("    %s / %s (%d + %d nulls)\n", SvPVX(key), SvPVX(value), len, nulls);
      
      my_hv_store_ent( tags, key, value );
      SvREFCNT_dec(key);
      
      // Handle padding
      if ( (len + nulls) % 2 ) {
        buffer_consume(buf, 1);
        pos++;
      }
    }
  }
  else {
    PerlIO_printf(PerlIO_stderr(), "Unhandled LIST type %s\n", type_id);
    buffer_consume(buf, chunk_size - 4);
  }
}
Example #12
0
static int
get_wav_metadata(PerlIO *infile, char *file, HV *info, HV *tags)
{
  Buffer buf;
  off_t file_size;
  int err = 0;
  uint32_t chunk_size;
  
  file_size = _file_size(infile);
  
  buffer_init(&buf, WAV_BLOCK_SIZE);
  
  if ( !_check_buf(infile, &buf, 12, WAV_BLOCK_SIZE) ) {
    err = -1;
    goto out;
  }
  
  if ( !strncmp( (char *)buffer_ptr(&buf), "RIFF", 4 ) ) {
    // We've got a RIFF file
    buffer_consume(&buf, 4);
  
    chunk_size = buffer_get_int_le(&buf);
    
    // Check format
    if ( strncmp( (char *)buffer_ptr(&buf), "WAVE", 4 ) ) {
      PerlIO_printf(PerlIO_stderr(), "Invalid WAV file: missing WAVE header: %s\n", file);
      err = -1;
      goto out;
    }
    
    buffer_consume(&buf, 4);
    
    my_hv_store( info, "file_size", newSVuv(file_size) );
    
    _parse_wav(infile, &buf, file, file_size, info, tags);
  }
  else if ( !strncmp( (char *)buffer_ptr(&buf), "FORM", 4 ) ) {
    // We've got an AIFF file
    char *bptr;
    
    buffer_consume(&buf, 4);
    
    chunk_size = buffer_get_int(&buf);
    
    // Check format
    bptr = buffer_ptr(&buf);
    if ( bptr[0] == 'A' && bptr[1] == 'I' && bptr[2] == 'F' && (bptr[3] == 'F' || bptr[3] == 'C') ) {
      buffer_consume(&buf, 4);

      my_hv_store( info, "file_size", newSVuv(file_size) );

      _parse_aiff(infile, &buf, file, file_size, info, tags);
    }
    else {
      PerlIO_printf(PerlIO_stderr(), "Invalid AIFF file: missing AIFF header: %s\n", file);
      err = -1;
      goto out;
    }
  }
  else {
    PerlIO_printf(PerlIO_stderr(), "Invalid WAV file: missing RIFF header: %s\n", file);
    err = -1;
    goto out;
  }
  
out:
  buffer_free(&buf);

  if (err) return err;

  return 0;
}