shithub: opus-tools

ref: 1e445a1071f009ebcf554c98f9a97261863ef769
dir: /src/flac.c/

View raw version
#if defined(HAVE_CONFIG_H)
# include <config.h>
#endif
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <locale.h>
#include "flac.h"
#include "opus_header.h"

#if defined(HAVE_LIBFLAC)

/*Callback to read more data for the FLAC decoder.*/
static FLAC__StreamDecoderReadStatus read_callback(
   const FLAC__StreamDecoder *decoder,FLAC__byte buffer[],size_t *bytes,
   void *client_data){
  flacfile *flac;
  (void)decoder;
  flac=(flacfile *)client_data;
  if(*bytes>0){
    int bufpos;
    int buflen;
    bufpos=flac->bufpos;
    buflen=flac->buflen;
    if(bufpos<buflen){
      size_t bytes_to_copy;
      /*If we haven't consumed all the data we used for file ID yet, consume
        some more.*/
      bytes_to_copy=buflen-bufpos;
      bytes_to_copy=*bytes<bytes_to_copy?*bytes:bytes_to_copy;
      memcpy(buffer,flac->oldbuf,bytes_to_copy);
      flac->bufpos+=bytes_to_copy;
      *bytes=bytes_to_copy;
    }else{
      /*Otherwise just read from the file.*/
      *bytes=fread(buffer,sizeof(*buffer),*bytes,flac->f);
    }
    /*This pretty much comes from the FLAC documentation, except that we only
      check ferror() if we didn't read any bytes at all.*/
    return *bytes==0?ferror(flac->f)?
       FLAC__STREAM_DECODER_READ_STATUS_ABORT:
       FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM:
       FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
  }
  return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
}

/*Callback to test the stream for EOF.*/
static FLAC__bool eof_callback(const FLAC__StreamDecoder *decoder,
   void *client_data){
  flacfile *flac;
  (void)decoder;
  flac=(flacfile *)client_data;
  return feof(flac->f)?true:false;
}

/*A version of strncasecmp() that is guaranteed to only ignore the case of
  ASCII characters.*/
int flac_strncasecmp(const char *_a,const char *_b,int _n){
  int i;
  for(i=0;i<_n;i++){
    int a;
    int b;
    int d;
    a=_a[i];
    b=_b[i];
    if(a>='a'&&a<='z')a-='a'-'A';
    if(b>='a'&&b<='z')b-='a'-'A';
    d=a-b;
    if(d)return d;
  }
  return 0;
}

/*Callback to process a metadata packet.*/
static void metadata_callback(const FLAC__StreamDecoder *decoder,
   const FLAC__StreamMetadata *metadata,void *client_data){
  flacfile *flac;
  oe_enc_opt *inopt;
  (void)decoder;
  flac=(flacfile *)client_data;
  inopt=flac->inopt;
  switch(metadata->type){
    case FLAC__METADATA_TYPE_STREAMINFO:
      flac->max_blocksize=metadata->data.stream_info.max_blocksize;
      inopt->rate=metadata->data.stream_info.sample_rate;
      inopt->channels=flac->channels=metadata->data.stream_info.channels;
      inopt->samplesize=metadata->data.stream_info.bits_per_sample;
      inopt->total_samples_per_channel=
         metadata->data.stream_info.total_samples;
      flac->block_buf=malloc(
         flac->max_blocksize*flac->channels*sizeof(*flac->block_buf));
      flac->block_buf_pos=0;
      flac->block_buf_len=0;
      break;
    case FLAC__METADATA_TYPE_VORBIS_COMMENT:
      {
        FLAC__StreamMetadata_VorbisComment_Entry *comments;
        FLAC__uint32 num_comments;
        FLAC__uint32 i;
        double reference_loudness;
        double album_gain;
        double track_gain;
        double gain;
        int saw_album_gain;
        int saw_track_gain;
        char *saved_locale;
        if(!inopt->copy_comments)break;
        num_comments=metadata->data.vorbis_comment.num_comments;
        comments=metadata->data.vorbis_comment.comments;
        saw_album_gain=saw_track_gain=0;
        album_gain=track_gain=0;
        /*The default reference loudness for ReplayGain is 89.0 dB*/
        reference_loudness=89;
        /*The code below uses strtod for the gain tags, so make sure the locale is C*/
        saved_locale=setlocale(LC_NUMERIC,"C");
        for(i=0;i<num_comments;i++){
          char *entry;
          char *end;
          entry=(char *)comments[i].entry;
          /*Check for ReplayGain tags.
            Parse the ones we have R128 equivalents for, and skip the others.*/
          if(flac_strncasecmp(entry,"REPLAYGAIN_REFERENCE_LOUDNESS=",30)==0){
            gain=strtod(entry+30,&end);
            if(end<=entry+30){
              fprintf(stderr,_("WARNING: Invalid ReplayGain tag: %s\n"),entry);
            }
            else reference_loudness=gain;
            continue;
          }
          if(flac_strncasecmp(entry,"REPLAYGAIN_ALBUM_GAIN=",22)==0){
            gain=strtod(entry+22,&end);
            if(end<=entry+22){
              fprintf(stderr,_("WARNING: Invalid ReplayGain tag: %s\n"),entry);
            }
            else{
              album_gain=gain;
              saw_album_gain=1;
            }
            continue;
          }
          if(flac_strncasecmp(entry,"REPLAYGAIN_TRACK_GAIN=",22)==0){
            gain=strtod(entry+22,&end);
            if(end<entry+22){
              fprintf(stderr,_("WARNING: Invalid ReplayGain tag: %s\n"),entry);
            }
            else{
              track_gain=gain;
              saw_track_gain=1;
            }
            continue;
          }
          if(flac_strncasecmp(entry,"REPLAYGAIN_ALBUM_PEAK=",22)==0
             ||flac_strncasecmp(entry,"REPLAYGAIN_TRACK_PEAK=",22)==0){
            continue;
          }
          if(!strchr(entry,'=')){
            fprintf(stderr,_("WARNING: Invalid comment: %s\n"),entry);
            fprintf(stderr,
               _("Discarding comment not in the form name=value\n"));
            continue;
          }
          comment_add(&inopt->comments,&inopt->comments_length,NULL,entry);
        }
        setlocale(LC_NUMERIC,saved_locale);
        /*Set the header gain to the album gain after converting to the R128
          reference level.*/
        if(saw_album_gain){
          gain=256*(album_gain+(84-reference_loudness))+0.5;
          inopt->gain=gain<-32768?-32768:gain<32767?(int)floor(gain):32767;
        }
        /*If there was a track gain, then add an equivalent R128 tag for that.*/
        if(saw_track_gain){
          char track_gain_buf[7];
          int track_gain_val;
          gain=256*(track_gain-album_gain)+0.5;
          track_gain_val=gain<-32768?-32768:gain<32767?(int)floor(gain):32767;
          sprintf(track_gain_buf,"%i",track_gain_val);
          comment_add(&inopt->comments,&inopt->comments_length,
             "R128_TRACK_GAIN=",track_gain_buf);
        }
      }
      break;
    default:
      break;
  }
}

/*Callback to process an audio frame.*/
static FLAC__StreamDecoderWriteStatus write_callback(
   const FLAC__StreamDecoder *decoder,const FLAC__Frame *frame,
   const FLAC__int32 *const buffer[],void *client_data){
  flacfile *flac;
  int channels;
  opus_int32 blocksize;
  int bits_per_sample;
  float scale;
  const int *channel_permute;
  float *block_buf;
  int ci;
  opus_int32 si;
  (void)decoder;
  flac=(flacfile *)client_data;
  /*We do not allow the number of channels to change.*/
  channels=frame->header.channels;
  if(channels!=flac->channels){
    return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
  }
  /*We do not allow block sizes larger than the declared maximum.*/
  blocksize=frame->header.blocksize;
  if(blocksize>flac->max_blocksize){
    return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
  }
  /*We do allow the bits per sample to change, though this will confound Opus's
    silence detection.*/
  bits_per_sample=frame->header.bits_per_sample;
  speex_assert(bits_per_sample>0&&bits_per_sample<=32);
  scale=(0x80000000U>>(bits_per_sample-1))*(1.0F/0x80000000U);
  channel_permute=flac->channel_permute;
  block_buf=flac->block_buf;
  for(ci=0;ci<channels;ci++){
    const FLAC__int32 *channel_buf;
    channel_buf=buffer[channel_permute[ci]];
    for(si=0;si<blocksize;si++){
      /*There's a loss of precision here for 32-bit samples, but libFLAC
        doesn't currently support more than 24.*/
      block_buf[si*channels+ci]=scale*(float)channel_buf[si];
    }
  }
  flac->block_buf_pos=0;
  flac->block_buf_len=blocksize;
  return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}

/*Dummy error callback (required by libFLAC).*/
void error_callback(const FLAC__StreamDecoder *decoder,
   FLAC__StreamDecoderErrorStatus status,void *client_data){
  (void)decoder;
  (void)status;
  (void)client_data;
}

int flac_id(unsigned char *buf,int len){
  /*Something screwed up.*/
  if(len<4)return 0;
  /*Not FLAC.*/
  if(memcmp(buf,"fLaC",4))return 0;
  /*Looks like FLAC.*/
  return 1;
}

int oggflac_id(unsigned char *buf,int len){
  /*Something screwed up.*/
  if(len<33)return 0;
  /*Not Ogg.*/
  if(memcmp(buf,"OggS",4))return 0;
  /*Not FLAC.*/
  if(memcmp(buf+28,"\177FLAC",5))return 0;
  /*Looks like OggFLAC.*/
  return 1;
}

/*Read more data for the encoder.*/
long flac_read(void *client_data,float *buffer,int samples){
  flacfile *flac;
  int channels;
  float *block_buf;
  long ret;
  flac=(flacfile *)client_data;
  channels=flac->channels;
  block_buf=flac->block_buf;
  ret=0;
  /*Keep reading until we get all the samples or hit an error/EOF.
    Short reads are not allowed.*/
  while(samples>0){
    opus_int32 block_buf_pos;
    opus_int32 block_buf_len;
    size_t samples_to_copy;
    block_buf_pos=flac->block_buf_pos;
    block_buf_len=flac->block_buf_len;
    if(block_buf_pos>=block_buf_len){
      /*Read the next frame from the stream.*/
      if(!FLAC__stream_decoder_process_single(flac->decoder))return ret;
      block_buf_pos=flac->block_buf_pos;
      block_buf_len=flac->block_buf_len;
      /*If we didn't get another block, we hit EOF.
        FLAC__stream_decoder_process_single still returns successfully in this
        case.*/
      if(block_buf_pos>=block_buf_len)return ret;
    }
    block_buf_len-=block_buf_pos;
    samples_to_copy=samples<block_buf_len?samples:block_buf_len;
    memcpy(buffer,block_buf+block_buf_pos*channels,
       samples_to_copy*channels*sizeof(*buffer));
    flac->block_buf_pos+=samples_to_copy;
    ret+=samples_to_copy;
    buffer+=samples_to_copy*channels;
    samples-=samples_to_copy;
  }
  return ret;
}

int flac_open(FILE *in,oe_enc_opt *opt,unsigned char *oldbuf,int buflen){
  flacfile *flac;
  /*Ok. At this point, we know we have a FLAC or an OggFLAC file.
    Set up the FLAC decoder.*/
  flac=malloc(sizeof(*flac));
  flac->decoder=FLAC__stream_decoder_new();
  FLAC__stream_decoder_set_md5_checking(flac->decoder,false);
  /*We get STREAMINFO packets by default, but not VORBIS_COMMENT.*/
  FLAC__stream_decoder_set_metadata_respond(flac->decoder,
     FLAC__METADATA_TYPE_VORBIS_COMMENT);
  flac->inopt=opt;
  flac->f=in;
  flac->oldbuf=malloc(buflen*sizeof(*flac->oldbuf));
  memcpy(flac->oldbuf,oldbuf,buflen*sizeof(*flac->oldbuf));
  flac->bufpos=0;
  flac->buflen=buflen;
  flac->block_buf=NULL;
  if((*(flac_id(oldbuf,buflen)?
     FLAC__stream_decoder_init_stream:FLAC__stream_decoder_init_ogg_stream))(
        flac->decoder,read_callback,NULL,NULL,NULL,eof_callback,
        write_callback,metadata_callback,error_callback,flac)==
     FLAC__STREAM_DECODER_INIT_STATUS_OK){
    /*Decode until we get the file length, sample rate, the number of channels,
      and the Vorbis comments (if any).*/
    if(FLAC__stream_decoder_process_until_end_of_metadata(flac->decoder)){
      opt->read_samples=flac_read;
      opt->readdata=flac;
      /*FLAC supports 1 to 8 channels only.*/
      speex_assert(flac->channels>0&&flac->channels<=8);
      /*It uses the same channel mappings as WAV.*/
      flac->channel_permute=wav_permute_matrix[flac->channels-1];
      return 1;
    }
  }
  flac_close(flac);
  fprintf(stderr,_("ERROR: Could not open FLAC stream.\n"));
  return 0;
}

void flac_close(void *client_data){
  flacfile *flac;
  flac=(flacfile *)client_data;
  free(flac->block_buf);
  free(flac->oldbuf);
  FLAC__stream_decoder_delete(flac->decoder);
  free(flac);
}

#else

/*FLAC support is disabled.*/

int flac_id(unsigned char *buf,int len){
  (void)buf;
  (void)len;
  return 0;
}

int oggflac_id(unsigned char *buf,int len){
  (void)buf;
  (void)len;
  return 0;
}

int flac_open(FILE *in,oe_enc_opt *opt,unsigned char *oldbuf,int buflen){
  (void)in;
  (void)opt;
  (void)oldbuf;
  (void)buflen;
  return 0;
}

void flac_close(void *client_data){
  (void)client_data;
}

#endif