aboutsummaryrefslogblamecommitdiffstats
path: root/src/pcm_utils.c
blob: 2851bbdf99f0b98bca90bea1066269e1b02031dc (plain) (tree)


















                                                                            




                      
                   

                   
                   
                 
 

                                                                        
 


                                                   


                                                     

                                
                                     
                                  
                                                                


                      
                                      
                                  
                                                                 
                 
                      

                                      
                                  
                                                                 
                 
                       
                

                                                                           

                                   

 








                                                                 




                                                             
                           
        




                                                                   



                                             







                                                                            



                                             



                                                                    
         

 








                                                                               
 
                          

                           
                             

                                          
                  










                                                                              
                            


                                                         
                                    








                                      

                                                      


                                    
                                         
                                                   



                                                               
                                                            

                                                                                 


                                                 
                      

                                   
                                         
                                                   




                                                               
                                                            



                                                                                 
                                                            

                                                                                



                                                 

                                        
         

                                                      
 
        
 
                                                                                
 













                                                                          









                                                      
         





                                                                          



                                      
 
                                                       

                                                                        

                                   

                                                   
                                



                                            
         
 













                                                                         
         


                               



                                                       
             



                                                                  






                                                                             

                                        
                       
        
                                                      

                                                                        

                                   














                                                                                
 
                                                                   
                                               


                                                                              

                        
                        

                                                                             
                                         

                                                                  

                                   
                             
         
        

                                                               
               








                                                                         
        

                                                        
 
                                                                               
 
 



                                                                           

                                        

                                              

                        


                                                             



                                                                              
        
                                                                                 


                                                                   
                                 





                                                                                  


                                                              

                                    
              

                                                                             


                                                                     

         




















                                                                                  

                                                               
                                                       


                                                  
                         
                                                                                  

                                                                                        
                                          



                                               



                                                  
                         

                                                                       
                                          
                                    

                                                     
                         





                                                                                      
                                      

         

                                                
                                          
                                           

                                                 
                                                                     


                               
                                                                                    
                                                 

                                           





                                                                                     
                




                                                                               
                                 
               
                 




                                                                  
                 

                      
                 




                                                                  
                 
                      

                 




                                                                  

                      
                                                     
                
                                                                                      

                                   
 
               




                                                                             

                                                                          

                                              
        


                                                         

                                                                             







                                                                             

                                    
 


 
/* the Music Player Daemon (MPD)
 * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu)
 * This project's homepage is: http://www.musicpd.org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


#include "pcm_utils.h"

#include "mpd_types.h"
#include "log.h"

#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <assert.h>
#include <time.h>

void pcm_convertToMpdFixed(AudioFormat * inFormat, char * inBuffer, int 
		samples, char * outBuffer, int fracBits)
{
	mpd_sint8 * in8 = (mpd_sint8 *)inBuffer;
	mpd_sint16 * in16 = (mpd_sint16 *)inBuffer;
	mpd_sint32 * in32 = (mpd_sint32 *)inBuffer;
	mpd_fixed_t * out = (mpd_fixed_t *)outBuffer;
	int shift;

	switch(inFormat->bits) {
	case 8:
		shift = fracBits - 8;
		while(samples--) {
			*out++ = (mpd_fixed_t)(*in8++) << shift;
		}
		break;
	case 16:
		shift = fracBits - 16;
		while(samples--) {
			*out++ = (mpd_fixed_t)(*in16++) << shift;
		}
		break;
	case 32:
		shift = 32 - fracBits;
		while(samples--) {
			*out++ = (mpd_fixed_t)(*in32++) >> shift;
		}
		break; 
	default:
		ERROR("%i bit samples are not supported for conversion!\n",
				inFormat->bits);
		exit(EXIT_FAILURE);
	}
}

/* this is stolen from mpg321! */
inline mpd_uint32 prng(mpd_uint32 state) {
	return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL;
}
/* end of stolen stuff from mpg321 */

void pcm_convertToIntWithDither(int bits, 
		mpd_fixed_t *buffer, int samples, int fracBits)
{
	static mpd_uint32 ditherRandom[2] = {0,0};
	const mpd_fixed_t mask = ~(~0L << (fracBits - bits));
	const mpd_fixed_t half = 1L << (fracBits - bits - 1);
	const mpd_fixed_t max = (1L << (fracBits)) - 1;
	const mpd_fixed_t min = ~0L << (fracBits);
	mpd_fixed_t sample;
	
	/* need to split in two cases to avoid negative shifting */
	if(bits>fracBits) {
		/* left shift - no need to dither */
		while(samples--) {
			sample = *buffer;
			if(sample>max)
				sample = max;
			else if(sample<min) 
				sample = min;
			*buffer++ = sample << (bits - fracBits - 1);
		}
	}
	else {
		/* right shift - add 1 bit triangular dither */
		while(samples--) {
			sample = *buffer + half + (ditherRandom[0] & mask) -
				(ditherRandom[1] & mask);
			if(sample>max)
				sample = max;
			else if(sample<min)
				sample = min;
			*buffer++ = sample >> (fracBits - bits + 1);
			ditherRandom[1] = ditherRandom[0] >> 1;
			ditherRandom[0] = prng(ditherRandom[0]);
		}
	}
}

struct {
	mpd_uint32 delay;
	mpd_uint32 inRate;
	mpd_uint32 outRate;
} convSampleRateData = {0,0,0};

void pcm_convertSampleRate(
		AudioFormat * inFormat, mpd_fixed_t * inBuffer, int inFrames, 
		AudioFormat *outFormat, mpd_fixed_t *outBuffer, int outFrames) 
{
	static int inRate;
	static int outRate;
	static int shift;
	static int rateShift;
	static mpd_fixed_t oldSampleL = 0;
	static mpd_fixed_t oldSampleR = 0;
	int delay;
	
	/* recalculate static data if samplerate has changed */
	if(inFormat->sampleRate != convSampleRateData.inRate || 
			outFormat->sampleRate != convSampleRateData.outRate) {
		/* set new sample rate info and reset delay */
		convSampleRateData.inRate = inFormat->sampleRate;
		convSampleRateData.outRate = outFormat->sampleRate;
		convSampleRateData.delay = 0;
		/* calculate the rates to use in calculations... */
		inRate = inFormat->sampleRate;
		outRate = outFormat->sampleRate;
		rateShift=0;
		shift = 16; /* worst case shift */
		/* ...reduce them to minimize shifting */
		while(!(inRate & 1) && !(outRate & 1)) {
			rateShift++;
			shift--;
			inRate >>= 1;
			outRate >>= 1;
		}
		oldSampleL = 0;
		oldSampleR = 0;
	}

	/* compute */
	
	delay = convSampleRateData.delay >> rateShift;
	switch(inFormat->channels) {
	case 1:
		while(inFrames--) {
			delay += outRate;
			/* calculate new samples */
			while(delay >= inRate) {
				mpd_sint32 raise;
				delay -= inRate;
				raise = *inBuffer - oldSampleL;
				*outBuffer++ = oldSampleL + 
					((((raise>>shift) * (outRate - delay)) / 
					 outRate) << shift);
			}
			oldSampleL = *inBuffer++;
		}
		break;
	case 2:
		while(inFrames--) {
			delay += outRate;
			/* calculate new samples */
			while(delay >= inRate) {
				mpd_sint32 raise;
				delay -= inRate;
				/* left channel */
				raise = *inBuffer - oldSampleL;
				*outBuffer++ = oldSampleL + 
					((((raise>>shift) * (outRate - delay)) / 
					 outRate) << shift);
				/* right channel */
				raise = inBuffer[1] - oldSampleR;
				*outBuffer++ = oldSampleR + 
					((((raise>>shift) * (outRate - delay)) /
					 outRate) << shift);
			}
			oldSampleL = *inBuffer++;
			oldSampleR = *inBuffer++;
		}
		/* exit(EXIT_FAILURE);*/
		break;
	}

	convSampleRateData.delay = delay << rateShift;
}
	

/****** exported procedures ***************************************************/

void pcm_changeBufferEndianness(char * buffer, int bufferSize, int bits) {
	
ERROR("pcm_changeBufferEndianess\n");
	switch(bits) {
	case 16:
		while(bufferSize) {
			mpd_uint8 temp = *buffer;
			*buffer = *(buffer+1);
			*(buffer+1) = temp;
			bufferSize-=2;
		}
		break;
	case 32:
		/* I'm not sure if this code is correct */
		while(bufferSize) {
			mpd_uint8 temp = *buffer;
			mpd_uint8 temp1 = *(buffer+1);
			*buffer = *(buffer+3);
			*(buffer+1) = *(buffer+2);
			*(buffer+2) = temp1;
			*(buffer+3) = temp;
			bufferSize-=4;
		}
		break;
	}
}

void pcm_volumeChange(char * buffer, int bufferSize, AudioFormat * format,
		int volume)
{
	mpd_fixed_t * buffer32 = (mpd_fixed_t *)buffer;
	int samples = bufferSize >> 2;
	static int iScale;
	static int shift;
	static int currentVolume = -1;

	if(format->bits!=32 || format->fracBits == 0) {
		ERROR("Only 32 bit mpd_fixed_t samples are supported in"
				" pcm_volumeChange!\n");
		exit(EXIT_FAILURE);
	}

	/* take care of full and no volume cases */
	if(volume>=1024) return;
	
	if(volume<=0) {
		memset(buffer,0,bufferSize);
		return;
	}

	/* recalculate if volume has changed */
	if(volume != currentVolume) {
		currentVolume = volume;
		iScale = volume;
		shift = 10;
		
		/* Minimize values to get the precision loss as small as 
		 * possible in the integer calculations. Make iScale less
		 * then 5 bits. This results in a volume change precision
		 * of approx. 0.5dB */
		while((iScale>31 || !(iScale & 1)) && shift) {
			iScale >>= 1;
			shift--;
		}
	}

	/* change the volume */
	if(iScale == 1)
		while(samples--) {
			*buffer32 = *buffer32 >> shift;
			buffer32++;
		}
	else 
		while(samples--) {
			*buffer32 = (*buffer32 >> shift) * iScale;
			buffer32++;
		}
}

void pcm_add(char * buffer1, char * buffer2, size_t bufferSize1, 
		size_t bufferSize2, int vol1, int vol2, AudioFormat * format)
{
	mpd_fixed_t * buffer32_1 = (mpd_fixed_t *)buffer1;
	mpd_fixed_t * buffer32_2 = (mpd_fixed_t *)buffer2;
	int samples1 = bufferSize1 >> 2;
	int samples2 = bufferSize2 >> 2;
	int shift = 10;
	
	if(format->bits!=32 || format->fracBits==0 ) {
		ERROR("Only 32 bit mpd_fixed_t samples are supported in"
				" pcm_add!\n");
		exit(EXIT_FAILURE);
	}
	
	/* take care of zero volume cases */
	if(vol2<=0) {
		return;
	}
	if(vol1<=0) {
		if(bufferSize1>bufferSize2) {
			memcpy(buffer1, buffer2, bufferSize2);
			memset(buffer1+bufferSize2, 0, bufferSize1-bufferSize2);
		}
		else {
			memcpy(buffer1, buffer2, bufferSize1);
		}		
		return;
	}

	/* lower multiplicator to minimize audio resolution loss */
	/* TODO? force a maximum shift size? */
	while((vol1>31 || !(vol1 & 1)) && (vol2>31 || !(vol2 & 1)) && shift) {
		vol1 >>= 1;
		vol2 >>= 1;
		shift--;
	}
			
	/* scale and add samples */
	/* no check for overflow needed - we trust our headroom is enough */ 
	while(samples1-- && samples2--) {
		mpd_fixed_t temp = (*buffer32_1 >> shift) * vol1 +
				(*buffer32_2 >> shift) * vol2;
		*buffer32_1 = temp;
		buffer32_1++;
		buffer32_2++;
	}
	
	/* take care of case where buffer2 > buffer1 */
	if(samples2) memcpy(buffer32_1,buffer32_2,samples2<<2);
	return;

}

void pcm_mix(char * buffer1, char * buffer2, size_t bufferSize1, 
		size_t bufferSize2, AudioFormat * format, float portion1)
{
	int vol1;
	float s = sin(M_PI_2*portion1);
	s*=s;
	
	vol1 = s*1024+0.5;
	vol1 = vol1>1024 ? 1024 : ( vol1<0 ? 0 : vol1 );

	pcm_add(buffer1,buffer2,bufferSize1,bufferSize2,vol1,1024-vol1,format);
}


void pcm_convertAudioFormat(AudioFormat * inFormat, char * inBuffer, size_t
		inSize, AudioFormat * outFormat, char * outBuffer)
{
	static char *convBuffer = NULL;
	static int convBufferLength = 0;
	static char *convSampleBuffer = NULL;
	static int convSampleBufferLength = 0;
	char * dataConv;
	int dataLen;
	int fracBits;
	const int inSamples = (inSize << 3) / inFormat->bits;
	const int inFrames = inSamples / inFormat->channels;
	const size_t outSize = pcm_sizeOfOutputBufferForAudioFormatConversion(
			inFormat, inSize, outFormat);
	const int outSamples = (outSize << 3) / outFormat->bits;
	const int outFrames = outSamples / outFormat->channels;
	
	/* make sure convBuffer is big enough for 2 channels of 32 bit samples */
	dataLen = inFrames << 3;
	if(dataLen > convBufferLength) {
		convBuffer = (char *) realloc(convBuffer, dataLen);
		if(!convBuffer) {
			ERROR("Could not allocate more memory for convBuffer!\n");
			exit(EXIT_FAILURE);
		}
		convBufferLength = dataLen;
	}

	/* make sure dataConv points to mpd_fixed_t samples */
	if(inFormat->fracBits && inFormat->bits==32) {
		fracBits = inFormat->fracBits;
		dataConv = inBuffer;
	}
	else {
		/* convert to same fracBits as output or use 28 as default */
		fracBits = outFormat->fracBits ? outFormat->fracBits : 28;
		dataConv = convBuffer;
		pcm_convertToMpdFixed(inFormat, inBuffer, inSamples, 
				dataConv, fracBits);
	}
	
	/****** convert sample rate ******/

	if(inFormat->sampleRate != outFormat->sampleRate) {
		/* check size of buffer */
		dataLen = outFrames << 3;
		if(dataLen > convSampleBufferLength) {
			convSampleBuffer = (char *) 
				realloc(convSampleBuffer, dataLen);
			if(!convSampleBuffer) {
				ERROR("Could not allocate memory for " 
						"convSampleBuffer!\n");
				exit(EXIT_FAILURE);
			}
			convSampleBufferLength = dataLen;
		}
		/* convert samples */
		pcm_convertSampleRate(inFormat, (mpd_fixed_t*)dataConv, inFrames, 
			outFormat, (mpd_fixed_t*)convSampleBuffer, outFrames);
		dataConv = convSampleBuffer;
	}

	/****** convert between mono and stereo samples ******/
	
	if(inFormat->channels != outFormat->channels) {
		switch(inFormat->channels) {
		/* convert from 1 -> 2 channels */
		case 1:
			{
			/* in reverse order to allow for same in and out buffer */
			mpd_fixed_t *in = ((mpd_fixed_t *)dataConv) + outFrames - 1;
			mpd_fixed_t *out = ((mpd_fixed_t *)convBuffer) + outSamples - 1;
			int f = outFrames;
			while(f--) {
				*out-- = *in;
				*out-- = *in--;
			}
			}
			break;
		/* convert from 2 -> 1 channels */
		case 2:
			{
			mpd_fixed_t *in = ((mpd_fixed_t *)dataConv);
			mpd_fixed_t *out = ((mpd_fixed_t *)convBuffer);
			int f = outFrames;
			while(f--) {
				*out = (*in++)>>1;
				*out++ += (*in++)>>1;
			}
			}
			break;
		default:
			ERROR("only 1 or 2 channels are supported for conversion!\n");
			exit(EXIT_FAILURE);
		}
		dataConv = convBuffer;
	}

	/****** convert to output format ******/

	/* if outformat is mpd_fixed_t */ 
	if(outFormat->fracBits==fracBits) {
		if(outFormat->bits==32) {
			if(outBuffer != dataConv)
				memcpy(outBuffer, dataConv, outSize);
			return;
		}
		else {
			ERROR("%i bit mpd_fixed_t not supported for conversion!\n", 
				outFormat->bits);
			exit(EXIT_FAILURE);
		}
	} 
	else if(outFormat->fracBits) {
		/* TODO case when in and out have different fracBits */
		ERROR("Conversion between different fracBits not supported yet!\n"); 
		exit(EXIT_FAILURE);
	}			
		
	/* convert to regular integer while adding dither and checking range */
	pcm_convertToIntWithDither(outFormat->bits, 
			(mpd_fixed_t *)dataConv, outSamples, fracBits);
	
	/* copy to output buffer*/
	switch(outFormat->bits) {
	case 8:
		{
			mpd_fixed_t *in = (mpd_fixed_t *)dataConv;
			mpd_sint8 * out = (mpd_sint8 *)outBuffer;
			int s = outSamples;
			while(s--) 
				*out++ = *in++;
		}
		break;
	case 16:
		{
			mpd_fixed_t *in = (mpd_fixed_t *)dataConv;
			mpd_sint16 *out = (mpd_sint16 *)outBuffer;
			int s = outSamples;
			while(s--)
				*out++ = *in++;
		}
		break;
	case 32:
		{
			mpd_fixed_t *in = (mpd_fixed_t *)dataConv;
			mpd_sint32 *out = (mpd_sint32 *)outBuffer;
			int s = outSamples;
			while(s--)
				*out++ = *in++;
		}
		break;
	case 24: /* TODO! how do we store 24 bit? */ 
	default:
		ERROR("%i bits are not supported for conversion!\n", outFormat->bits);
		exit(EXIT_FAILURE);
	}

	return;
}

size_t pcm_sizeOfOutputBufferForAudioFormatConversion(AudioFormat * inFormat,
		size_t inSize, AudioFormat * outFormat)
{
	const int inShift = (inFormat->bits * inFormat->channels) >> 3;
	const int outShift = (outFormat->bits * outFormat->channels) >> 3;
	const int inFrames = inSize / inShift;
	mpd_uint32 outFrames;
	
	if(inFormat->sampleRate == outFormat->sampleRate)
		outFrames = inFrames;
	else {
		/* The previous delay from the sample rate conversion affect 
		 * the size of the output */
		mpd_uint32 delay = convSampleRateData.delay;
		if(inFormat->sampleRate != convSampleRateData.inRate || 
			outFormat->sampleRate != convSampleRateData.outRate) 
		{
			delay = 0;
		}
		outFrames = (inFrames * (mpd_uint32)(outFormat->sampleRate) 
				+ delay) / inFormat->sampleRate;
	}
	return outFrames * outShift;
}