View Javadoc

1   /**
2    * Copyright (c) 2011, The University of Southampton and the individual contributors.
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without modification,
6    * are permitted provided that the following conditions are met:
7    *
8    *   * 	Redistributions of source code must retain the above copyright notice,
9    * 	this list of conditions and the following disclaimer.
10   *
11   *   *	Redistributions in binary form must reproduce the above copyright notice,
12   * 	this list of conditions and the following disclaimer in the documentation
13   * 	and/or other materials provided with the distribution.
14   *
15   *   *	Neither the name of the University of Southampton nor the names of its
16   * 	contributors may be used to endorse or promote products derived from this
17   * 	software without specific prior written permission.
18   *
19   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26   * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29   */
30  /**
31   *
32   */
33  package org.openimaj.audio.conversion;
34  
35  import org.openimaj.audio.AudioFormat;
36  import org.openimaj.audio.AudioStream;
37  import org.openimaj.audio.SampleChunk;
38  import org.openimaj.audio.processor.AudioProcessor;
39  import org.openimaj.audio.samples.SampleBuffer;
40  import org.openimaj.audio.samples.SampleBufferFactory;
41  import org.openimaj.math.util.Interpolation;
42  
43  /**
44   * 	A sample rate conversion audio processing class. There is an enumerator
45   * 	within the class that is publically available for determining the
46   * 	algorithm for sample rate conversion. This defaults to
47   * 	{@link SampleRateConversionAlgorithm#LINEAR_INTERPOLATION}.
48   * 	<p>
49   * 	To use the class, instantiate using the default constructor or the
50   * 	chainable constructor. Both of these constructors take the algorithm for
51   * 	sample rate conversion as well as the output format. The output format
52   * 	must have the same number of bits and same number of channels as the
53   * 	expected input otherwise the {@link #process(SampleChunk)} method
54   * 	will throw {@link IllegalArgumentException}. The input format for the samples
55   * 	is expected to be provided as part of the {@link SampleChunk}.
56   * 	<p>
57   * 	The class itself checks whether the output format and the input format
58   * 	are the same (in which case the sample does not need to be resampled).
59   * 	That means the algorithm implementation does not need to do this.
60   *
61   *  @author David Dupplaw (dpd@ecs.soton.ac.uk)
62   *
63   *	@created 18 Jun 2012
64   */
65  public class SampleRateConverter extends AudioProcessor
66  {
67  	/**
68  	 * 	An enumerator of the different sample rate conversion algorithms
69  	 * 	available in this sample rate converter.
70  	 *
71  	 *  @author David Dupplaw (dpd@ecs.soton.ac.uk)
72  	 *
73  	 *	@created 18 Jun 2012
74  	 */
75  	public enum SampleRateConversionAlgorithm
76  	{
77  		/**
78  		 * 	Performs linear interpolation between samples where the sample rate
79  		 * 	in the output format is greater than the sample rate of the input
80  		 * 	format. If the sample rate is less, then the nearest value
81  		 * 	of the input samples is used.
82  		 */
83  		LINEAR_INTERPOLATION
84  		{
85  			@Override
86              public SampleChunk process( final SampleChunk s, final AudioFormat output)
87              {
88  				final AudioFormat input = s.getFormat();
89  
90  				// Check to see if the input and output are the same
91  				if( input.getSampleRateKHz() == output.getSampleRateKHz() )
92  					return s;
93  
94  				// Work out the size of the output sample chunk
95  				final double scalar = input.getSampleRateKHz() / output.getSampleRateKHz();
96  				final SampleBuffer sbin = s.getSampleBuffer();
97  				final double size = sbin.size() / scalar;
98  
99  				if( this.sbout == null || this.sbout.size() != (int)size )
100 				{
101 					this.sbout = SampleBufferFactory.createSampleBuffer(
102 						output, (int)size );
103 					this.sbout.setFormat( output );
104 				}
105 
106 				// If the input format has a greater sample rate than the
107 				// output format - down sampling (scalar > 1)
108 				if( scalar > 1 )
109 				{
110 					for( int i = 0; i < this.sbout.size(); i++ )
111 						this.sbout.set( i, sbin.get( (int)(i * scalar) ) );
112 					return this.sbout.getSampleChunk();
113 				}
114 				// If the input format has a sample rate less than that
115 				// of the output - up sampling (scalar < 1)
116 				else
117 				{
118 					// Linear interpolate each sample value
119 					for( int i = 0; i < this.sbout.size()-1; i++ )
120 					{
121 						final int inputSampleX = (int)(i * scalar);
122 						this.sbout.set( i, Interpolation.lerp( (float)(i*scalar),
123 								inputSampleX, sbin.get(inputSampleX),
124 								inputSampleX+1, sbin.get(inputSampleX+1) ) );
125 					}
126 					this.sbout.set( this.sbout.size()-1, sbin.get(sbin.size()-1) );
127 					return this.sbout.getSampleChunk();
128 				}
129             }
130 		};
131 
132 		protected SampleBuffer sbout = null;
133 
134 		/**
135 		 * 	Process a sample chunk and output a sample chunk in the given
136 		 * 	output format.
137 		 *
138 		 *  @param s The input sample chunk
139 		 *  @param output The output format
140 		 *  @return A resampled sample chunk.
141 		 */
142 		public abstract SampleChunk process( SampleChunk s, AudioFormat output );
143 	}
144 
145 	/**
146 	 * Sample rate conversion defaults to
147 	 * {@link SampleRateConversionAlgorithm#LINEAR_INTERPOLATION}
148 	 */
149 	private SampleRateConversionAlgorithm sampleConverter =
150 		SampleRateConversionAlgorithm.LINEAR_INTERPOLATION;
151 
152 	/** The output format to which sample chunks will be converted */
153 	private AudioFormat outputFormat = null;
154 
155 	/**
156 	 * 	Default constructor that takes the input conversion
157 	 *  @param converter The converter to use
158 	 *  @param outputFormat The output format to convert to
159 	 */
160 	public SampleRateConverter( final SampleRateConversionAlgorithm converter,
161 			final AudioFormat outputFormat )
162     {
163 		this.sampleConverter = converter;
164 		this.outputFormat = outputFormat;
165 		this.setFormat( outputFormat );
166     }
167 
168 	/**
169 	 * 	Chainable constructor.
170 	 *
171 	 *  @param as The audio stream to process
172 	 *  @param converter The converter to use
173 	 *  @param outputFormat The output format to convert to
174 	 */
175 	public SampleRateConverter( final AudioStream as, final SampleRateConversionAlgorithm converter,
176 			final AudioFormat outputFormat )
177 	{
178 		super( as );
179 		this.sampleConverter = converter;
180 		this.outputFormat = outputFormat;
181 		this.setFormat( outputFormat );
182 	}
183 
184 	/**
185 	 *	{@inheritDoc}
186 	 * 	@see org.openimaj.audio.processor.AudioProcessor#process(org.openimaj.audio.SampleChunk)
187 	 */
188 	@Override
189 	public SampleChunk process( final SampleChunk sample ) throws Exception
190 	{
191 		if( sample.getFormat().getNBits() != this.outputFormat.getNBits() )
192 			throw new IllegalArgumentException( "The number of bits in the " +
193 					"output format is not the same as the sample chunk. Use a " +
194 					"resampling conversion first before using the sample-rate " +
195 					"converter." );
196 
197 		if( sample.getFormat().getNumChannels() != this.outputFormat.getNumChannels() )
198 			throw new IllegalArgumentException( "The number of channels in the " +
199 					"output format is not the same as the sample chunk. Use a " +
200 					"channel converter first before using the sample-rate " +
201 					"converter." );
202 
203 		if( sample.getFormat().getSampleRateKHz() == this.outputFormat.getSampleRateKHz() )
204 			return sample;
205 
206 		final SampleChunk sc = this.sampleConverter.process( sample, this.outputFormat );
207 		sc.setStartTimecode( sample.getStartTimecode() );
208 		return sc;
209 	}
210 }