Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 *      Xing VBR tagging for LAME.
 *
 *      Copyright (c) 1999 A.L. Faber
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/* $Id: VbrTag.c,v 1.20 2001/02/27 09:59:16 robert Exp $ */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include "machine.h"
#if defined(__riscos__) && defined(FPA10)
#include        "ymath.h"
#else
#include        <math.h>
#endif
#include "VbrTag.h"
#include "version.h"
#include "bitstream.h"
#include "VbrTag.h"
#include        <assert.h>

#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif


#ifdef _DEBUG
/*  #define DEBUG_VBRTAG */
#endif

/*
//    4 bytes for Header Tag
//    4 bytes for Header Flags
//  100 bytes for entry (NUMTOCENTRIES)
//    4 bytes for FRAME SIZE
//    4 bytes for STREAM_SIZE
//    4 bytes for VBR SCALE. a VBR quality indicator: 0=best 100=worst
//   20 bytes for LAME tag.  for example, "LAME3.12 (beta 6)"
// ___________
//  140 bytes
*/
#define VBRHEADERSIZE (NUMTOCENTRIES+4+4+4+4+4)

/* the size of the Xing header (MPEG1 and MPEG2) in kbps */
#define XING_BITRATE1 128
#define XING_BITRATE2  64
#define XING_BITRATE25 32



const static char       VBRTag[]={"Xing"};
const int SizeOfEmptyFrame[2][2]=
{
        {17,9},
        {32,17},
};


/***********************************************************************
 *  Robert Hegemann 2001-01-17
 ***********************************************************************/

void addVbr(VBR_seek_info_t * v, int bitrate)
{
    int i;

    v->sum += bitrate;
    v->seen ++;
    
    if (v->seen < v->want) {
        return;
    }

    if (v->pos < v->size) {
        v->bag[v->pos] = v->sum;
        v->pos ++;
        v->seen = 0;
    }
    if (v->pos == v->size) {
        for (i = 1; i < v->size; i += 2) {
            v->bag[i/2] = v->bag[i]; 
        }
        v->want *= 2;
        v->pos  /= 2;
    }
}

void Xing_seek_table(VBR_seek_info_t * v, unsigned char *t)
{
    int i, index;
    int seek_point;
    
    if (v->pos <= 0)
        return;
        
    for (i = 1; i < NUMTOCENTRIES; ++i) {
        float j = i/(float)NUMTOCENTRIES, act, sum;
        index = (int)(floor(j * v->pos));
        if (index > v->pos-1)
            index = v->pos-1;
        act = v->bag[index];
        sum = v->sum;
        seek_point = (int)(256. * act / sum);
        if (seek_point > 255)
            seek_point = 255;
        t[i] = seek_point;
    }
}

#if 0
void print_seeking(unsigned char *t)
{
    int i;
    
    printf("seeking table ");
    for (i = 0; i < NUMTOCENTRIES; ++i) {
        printf(" %d ", t[i]);
    }
    printf("\n");
}
#endif



/****************************************************************************
 * AddVbrFrame: Add VBR entry, used to fill the VBR the TOC entries
 * Paramters:
 *      nStreamPos: how many bytes did we write to the bitstream so far
 *                              (in Bytes NOT Bits)
 ****************************************************************************
*/
void AddVbrFrame(lame_global_flags *gfp)
{
    lame_internal_flags *gfc = gfp->internal_flags;

    int kbps = bitrate_table[gfp->version][gfc->bitrate_index];
    
    if (gfc->VBR_seek_table.bag == NULL) {
        gfc->VBR_seek_table.sum  = 0;
        gfc->VBR_seek_table.seen = 0;
        gfc->VBR_seek_table.want = 1;
        gfc->VBR_seek_table.pos  = 0;
        gfc->VBR_seek_table.bag  = malloc (400*sizeof(int));
        if (gfc->VBR_seek_table.bag != NULL) {
            gfc->VBR_seek_table.size = 400;
        }
        else {
            gfc->VBR_seek_table.size = 0;
            ERRORF (gfc,"Error: can't allocate VbrFrames buffer\n");
            return;
        }   
    }
    addVbr(&gfc->VBR_seek_table, kbps);
    gfp->nVbrNumFrames++;
}


/*-------------------------------------------------------------*/
static int ExtractI4(unsigned char *buf)
{
        int x;
        /* big endian extract */
        x = buf[0];
        x <<= 8;
        x |= buf[1];
        x <<= 8;
        x |= buf[2];
        x <<= 8;
        x |= buf[3];
        return x;
}

void CreateI4(unsigned char *buf, int nValue)
{
        /* big endian create */
        buf[0]=(nValue>>24)&0xff;
        buf[1]=(nValue>>16)&0xff;
        buf[2]=(nValue>> 8)&0xff;
        buf[3]=(nValue    )&0xff;
}


/*-------------------------------------------------------------*/
/* Same as GetVbrTag below, but only checks for the Xing tag.
   requires buf to contain only 40 bytes */
/*-------------------------------------------------------------*/
int CheckVbrTag(unsigned char *buf)
{
        int                     h_id, h_mode, h_sr_index;

        /* get selected MPEG header data */
        h_id       = (buf[1] >> 3) & 1;
        h_sr_index = (buf[2] >> 2) & 3;
        h_mode     = (buf[3] >> 6) & 3;

        /*  determine offset of header */
        if( h_id )
        {
                /* mpeg1 */
                if( h_mode != 3 )       buf+=(32+4);
                else                            buf+=(17+4);
        }
        else
        {
                /* mpeg2 */
                if( h_mode != 3 ) buf+=(17+4);
                else              buf+=(9+4);
        }

        if( buf[0] != VBRTag[0] ) return 0;    /* fail */
        if( buf[1] != VBRTag[1] ) return 0;    /* header not found*/
        if( buf[2] != VBRTag[2] ) return 0;
        if( buf[3] != VBRTag[3] ) return 0;
        return 1;
}

int GetVbrTag(VBRTAGDATA *pTagData,  unsigned char *buf)
{
        int                     i, head_flags;
        int                     h_bitrate,h_id, h_mode, h_sr_index;

        /* get Vbr header data */
        pTagData->flags = 0;

        /* get selected MPEG header data */
        h_id       = (buf[1] >> 3) & 1;
        h_sr_index = (buf[2] >> 2) & 3;
        h_mode     = (buf[3] >> 6) & 3;
        h_bitrate  = ((buf[2]>>4)&0xf);
        h_bitrate = bitrate_table[h_id][h_bitrate];


        /*  determine offset of header */
        if( h_id )
        {
                /* mpeg1 */
                if( h_mode != 3 )       buf+=(32+4);
                else                            buf+=(17+4);
        }
        else
        {
                /* mpeg2 */
                if( h_mode != 3 ) buf+=(17+4);
                else              buf+=(9+4);
        }

        if( buf[0] != VBRTag[0] ) return 0;    /* fail */
        if( buf[1] != VBRTag[1] ) return 0;    /* header not found*/
        if( buf[2] != VBRTag[2] ) return 0;
        if( buf[3] != VBRTag[3] ) return 0;

        buf+=4;

        pTagData->h_id = h_id;
        pTagData->samprate = samplerate_table[h_id][h_sr_index];

        if( h_id == 0 )
                pTagData->samprate >>= 1;

        head_flags = pTagData->flags = ExtractI4(buf); buf+=4;      /* get flags */

        if( head_flags & FRAMES_FLAG )
        {
                pTagData->frames   = ExtractI4(buf); buf+=4;
        }

        if( head_flags & BYTES_FLAG )
        {
                pTagData->bytes = ExtractI4(buf); buf+=4;
        }

        if( head_flags & TOC_FLAG )
        {
                if( pTagData->toc != NULL )
                {
                        for(i=0;i<NUMTOCENTRIES;i++)
                                pTagData->toc[i] = buf[i];
                }
                buf+=NUMTOCENTRIES;
        }

        pTagData->vbr_scale = -1;

        if( head_flags & VBR_SCALE_FLAG )
        {
                pTagData->vbr_scale = ExtractI4(buf); buf+=4;
        }

        pTagData->headersize = 
          ((h_id+1)*72000*h_bitrate) / pTagData->samprate;


#ifdef DEBUG_VBRTAG
        DEBUGF("\n\n********************* VBR TAG INFO *****************\n");
        DEBUGF("tag         :%s\n",VBRTag);
        DEBUGF("head_flags  :%d\n",head_flags);
        DEBUGF("bytes       :%d\n",pTagData->bytes);
        DEBUGF("frames      :%d\n",pTagData->frames);
        DEBUGF("VBR Scale   :%d\n",pTagData->vbr_scale);
        DEBUGF("toc:\n");
        if( pTagData->toc != NULL )
        {
                for(i=0;i<NUMTOCENTRIES;i++)
                {
                        if( (i%10) == 0 ) DEBUGF("\n");
                        DEBUGF(" %3d", (int)(pTagData->toc[i]));
                }
        }
        DEBUGF("\n***************** END OF VBR TAG INFO ***************\n");
#endif
        return 1;       /* success */
}


/****************************************************************************
 * InitVbrTag: Initializes the header, and write empty frame to stream
 * Paramters:
 *                              fpStream: pointer to output file stream
 *                              nMode   : Channel Mode: 0=STEREO 1=JS 2=DS 3=MONO
 ****************************************************************************
*/
int InitVbrTag(lame_global_flags *gfp)
{
        int nMode,SampIndex;
        lame_internal_flags *gfc = gfp->internal_flags;
#define MAXFRAMESIZE 576
        //      u_char pbtStreamBuffer[MAXFRAMESIZE];
        nMode = gfp->mode;
        SampIndex = gfc->samplerate_index;


        /* Clear Frame position array variables */
        gfp->pVbrFrames=NULL;
        gfp->nVbrNumFrames=0;
        gfp->nVbrFrameBufferSize=0;


        /* Clear stream buffer */
        //      memset(pbtStreamBuffer,0x00,sizeof(pbtStreamBuffer));



        /* Reserve the proper amount of bytes */
        if (nMode==3)
        {
                gfp->nZeroStreamSize=SizeOfEmptyFrame[gfp->version][1]+4;
        }
        else
        {
                gfp->nZeroStreamSize=SizeOfEmptyFrame[gfp->version][0]+4;
        }

        /*
        // Xing VBR pretends to be a 48kbs layer III frame.  (at 44.1kHz).
        // (at 48kHz they use 56kbs since 48kbs frame not big enough for
        // table of contents)
        // let's always embed Xing header inside a 64kbs layer III frame.
        // this gives us enough room for a LAME version string too.
        // size determined by sampling frequency (MPEG1)
        // 32kHz:    216 bytes@48kbs    288bytes@ 64kbs
        // 44.1kHz:  156 bytes          208bytes@64kbs     (+1 if padding = 1)
        // 48kHz:    144 bytes          192
        //
        // MPEG 2 values are the same since the framesize and samplerate
        // are each reduced by a factor of 2.
        */
        {
        int i,bitrate,tot;
        if (1==gfp->version) {
          bitrate = XING_BITRATE1;
        } else {
          if (gfp->out_samplerate < 16000 )
            bitrate = XING_BITRATE25;
          else
            bitrate = XING_BITRATE2;
        }
        gfp->TotalFrameSize= 
          ((gfp->version+1)*72000*bitrate) / gfp->out_samplerate;
        tot = (gfp->nZeroStreamSize+VBRHEADERSIZE);
        tot += 20;  /* extra 20 bytes for LAME & version string */

        assert(gfp->TotalFrameSize >= tot );
        assert(gfp->TotalFrameSize <= MAXFRAMESIZE );

        for (i=0; i<gfp->TotalFrameSize; ++i)
          add_dummy_byte(gfp,0);
        }

        /* Success */
        return 0;
}



/****************************************************************************
 * PutVbrTag: Write final VBR tag to the file
 * Paramters:
 *                              lpszFileName: filename of MP3 bit stream
 *                              nVbrScale       : encoder quality indicator (0..100)
 ****************************************************************************
*/
int PutVbrTag(lame_global_flags *gfp,FILE *fpStream,int nVbrScale)
{
        lame_internal_flags * gfc = gfp->internal_flags;

        long lFileSize;
        int nStreamIndex;
        char abyte,bbyte;
        u_char          btToc[NUMTOCENTRIES];
        u_char pbtStreamBuffer[MAXFRAMESIZE];
        char str1[80];
        unsigned char id3v2Header[10];
        size_t id3v2TagSize;

        if (gfc->VBR_seek_table.pos <= 0)
                return -1;


        /* Clear stream buffer */
        memset(pbtStreamBuffer,0x00,sizeof(pbtStreamBuffer));

        /* Seek to end of file*/
        fseek(fpStream,0,SEEK_END);

        /* Get file size */
        lFileSize=ftell(fpStream);

        /* Abort if file has zero length. Yes, it can happen :) */
        if (lFileSize==0)
                return -1;

        /*
         * The VBR tag may NOT be located at the beginning of the stream.
         * If an ID3 version 2 tag was added, then it must be skipped to write
         * the VBR tag data.
         */

        /* seek to the beginning of the stream */
        fseek(fpStream,0,SEEK_SET);
        /* read 10 bytes in case there's an ID3 version 2 header here */
        fread(id3v2Header,1,sizeof id3v2Header,fpStream);
        /* does the stream begin with the ID3 version 2 file identifier? */
        if (!strncmp((char *)id3v2Header,"ID3",3)) {
          /* the tag size (minus the 10-byte header) is encoded into four
           * bytes where the most significant bit is clear in each byte */
          id3v2TagSize=(((id3v2Header[6] & 0x7f)<<21)
            | ((id3v2Header[7] & 0x7f)<<14)
            | ((id3v2Header[8] & 0x7f)<<7)
            | (id3v2Header[9] & 0x7f))
            + sizeof id3v2Header;
        } else {
          /* no ID3 version 2 tag in this stream */
          id3v2TagSize=0;
        }

        /* Seek to first real frame */
        fseek(fpStream,id3v2TagSize+gfp->TotalFrameSize,SEEK_SET);

        /* Read the header (first valid frame) */
        fread(pbtStreamBuffer,4,1,fpStream);

        /* the default VBR header. 48 kbps layer III, no padding, no crc */
        /* but sampling freq, mode andy copyright/copy protection taken */
        /* from first valid frame */
        pbtStreamBuffer[0]=(u_char) 0xff;
        abyte = (pbtStreamBuffer[1] & (char) 0xf1);
        { int bitrate;
        if (1==gfp->version) {
          bitrate = XING_BITRATE1;
        } else {
          if (gfp->out_samplerate < 16000 )
            bitrate = XING_BITRATE25;
          else
            bitrate = XING_BITRATE2;
        }
          bbyte = 16*BitrateIndex(bitrate,gfp->version,gfp->out_samplerate);
        }

        /* Use as much of the info from the real frames in the
         * Xing header:  samplerate, channels, crc, etc...
         */ 
        if (gfp->version==1) {
          /* MPEG1 */
          pbtStreamBuffer[1]=abyte | (char) 0x0a;     /* was 0x0b; */
          abyte = pbtStreamBuffer[2] & (char) 0x0d;   /* AF keep also private bit */
          pbtStreamBuffer[2]=(char) bbyte | abyte;     /* 64kbs MPEG1 frame */
        }else{
          /* MPEG2 */
          pbtStreamBuffer[1]=abyte | (char) 0x02;     /* was 0x03; */
          abyte = pbtStreamBuffer[2] & (char) 0x0d;   /* AF keep also private bit */
          pbtStreamBuffer[2]=(char) bbyte | abyte;     /* 64kbs MPEG2 frame */
        }


        /*Seek to the beginning of the stream */
        fseek(fpStream,id3v2TagSize,SEEK_SET);

        /* Clear all TOC entries */
        memset(btToc,0,sizeof(btToc));

        Xing_seek_table (&gfc->VBR_seek_table, btToc);
        /* print_seeking (btToc); */

        /* Start writing the tag after the zero frame */
        nStreamIndex=gfp->nZeroStreamSize;

        /* Put Vbr tag */
        pbtStreamBuffer[nStreamIndex++]=VBRTag[0];
        pbtStreamBuffer[nStreamIndex++]=VBRTag[1];
        pbtStreamBuffer[nStreamIndex++]=VBRTag[2];
        pbtStreamBuffer[nStreamIndex++]=VBRTag[3];

        /* Put header flags */
        CreateI4(&pbtStreamBuffer[nStreamIndex],FRAMES_FLAG+BYTES_FLAG+TOC_FLAG+VBR_SCALE_FLAG);
        nStreamIndex+=4;

        /* Put Total Number of frames */
        CreateI4(&pbtStreamBuffer[nStreamIndex],gfp->nVbrNumFrames);
        nStreamIndex+=4;

        /* Put Total file size */
        CreateI4(&pbtStreamBuffer[nStreamIndex],(int)lFileSize);
        nStreamIndex+=4;

        /* Put TOC */
        memcpy(&pbtStreamBuffer[nStreamIndex],btToc,sizeof(btToc));
        nStreamIndex+=sizeof(btToc);

        /* Put VBR SCALE */
        CreateI4(&pbtStreamBuffer[nStreamIndex],nVbrScale);
        nStreamIndex+=4;

        /* Put LAME ID */
        sprintf ( str1, "LAME%s", get_lame_short_version () );
        strncpy ( (char*)pbtStreamBuffer + nStreamIndex, str1, 20 );
        nStreamIndex += 20;


#ifdef DEBUG_VBRTAG
{
        VBRTAGDATA TestHeader;
        GetVbrTag(&TestHeader,pbtStreamBuffer);
}
#endif

        /* Put it all to disk again */
        if (fwrite(pbtStreamBuffer,(unsigned int)gfp->TotalFrameSize,1,fpStream)!=1)
        {
                return -1;
        }
        /* Save to delete the frame buffer */
        free(gfp->pVbrFrames);
        gfp->pVbrFrames=NULL;

        return 0;       /* success */
}