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  
40  /**
41   *	A class that will work out which processors to instantiate to provide
42   *	with a complete conversion from one audio format to another.
43   *
44   *	@author David Dupplaw (dpd@ecs.soton.ac.uk)
45   *  @created 13 May 2013
46   */
47  public class AudioConverter extends AudioProcessor
48  {
49  	/** The input format */
50  	private AudioFormat inputFormat = null;
51  
52  	/** The calculated audio processor */
53  	private AudioProcessor processor = null;
54  
55  	/**
56  	 * 	Chainable constructor that takes the stream to chain and the
57  	 * 	output format to convert the stream to.
58  	 *	@param stream The stream to chain to
59  	 *	@param output The required output format
60  	 */
61  	public AudioConverter( final AudioStream stream, final AudioFormat output )
62  	{
63  		super( stream );
64  		this.inputFormat = stream.getFormat().clone();
65  		this.setFormat( output.clone() );
66  		this.processor = AudioConverter.calculateProcess( this.inputFormat, output );
67  	}
68  
69  	/**
70  	 * 	Constructor that takes the input and output formats.
71  	 *	@param input The input format
72  	 *	@param output The output format
73  	 */
74  	public AudioConverter( final AudioFormat input, final AudioFormat output )
75  	{
76  		this.inputFormat = input.clone();
77  		this.setFormat( output.clone() );
78  		this.processor = AudioConverter.calculateProcess( input, output );
79  	}
80  
81  	/**
82  	 * 	Calculates the chain of processors that will convert from one format to the
83  	 * 	other and will return the first in the chain. If there is no processing to be
84  	 * 	done the method will return null.
85  	 *
86  	 *	@param input The input format
87  	 *	@param output The output format.
88  	 *	@return The first processor in the stream.
89  	 */
90  	public static AudioProcessor calculateProcess( final AudioFormat input, final AudioFormat output )
91  	{
92  		// If the input and output formats are the same, then there's
93  		// no processing to do, so we return null.
94  		if( input.equals( output ) )
95  			return null;
96  
97  		AudioProcessor ap = null;
98  
99  		// Note that we construct this chain back-to-front. So, if
100 		// all the processors will be used, then the channel processor,
101 		// then sample rate, then bit depth processor.  This should be
102 		// the most efficient order.
103 
104 		// Check if the endian is the same.
105 		if( input.isBigEndian() != output.isBigEndian() )
106 			throw new IllegalArgumentException( "Cannot convert "+input+" to "+output+
107 					". There is no endian conversion implemented yet." );
108 
109 		// Check if the signedness is the same
110 		if( input.isSigned() != output.isSigned() )
111 			throw new IllegalArgumentException( "Cannot convert "+input+" to "+output+
112 					". There is no sign conversion implemented yet." );
113 
114 		// Check if the sample rates are different
115 		if( input.getSampleRateKHz() != output.getSampleRateKHz() )
116 			ap = AudioConverter.getProcessor( ap, new SampleRateConverter(
117 					SampleRateConverter.SampleRateConversionAlgorithm.LINEAR_INTERPOLATION,
118 						AudioConverter.getFormatSR( ap, output ) ) );
119 
120 		// Check if the bits are different
121 		if( input.getNBits() != output.getNBits() )
122 			ap = AudioConverter.getProcessor( ap, new BitDepthConverter(
123 					BitDepthConverter.BitDepthConversionAlgorithm.NEAREST,
124 						AudioConverter.getFormatBits( ap, output ) ) );
125 
126 		// Check if the channels are different
127 		if( input.getNumChannels() != output.getNumChannels() )
128 		{
129 			if( output.getNumChannels() == 1 )
130 					ap = AudioConverter.getProcessor( ap, new MultichannelToMonoProcessor() );
131 			else	throw new IllegalArgumentException( "Cannot convert "+input+" to "+output+
132 								". Unable to find an appropriate channel converter." );
133 		}
134 
135 		return ap;
136 	}
137 
138 	/**
139 	 * 	Updates the number of bits in the source processor's format to match the output format.
140 	 *
141 	 *	@param ap The source processor
142 	 *	@param output The output format
143 	 *	@return The fixed source format
144 	 */
145 	private static AudioFormat getFormatBits( final AudioProcessor ap , final AudioFormat output )
146 	{
147 		if( ap == null ) return output;
148 		final AudioFormat f = ap.getFormat().clone();
149 		f.setNBits( output.getNBits() );
150 		return f;
151 	}
152 
153 	/**
154 	 * 	Updates the sample rate in the source processor's format to match the output format.
155 	 *
156 	 *	@param ap The source processor
157 	 *	@param output The output format
158 	 *	@return The fixed source format
159 	 */
160 	private static AudioFormat getFormatSR( final AudioProcessor ap, final AudioFormat output )
161 	{
162 		if( ap == null ) return output;
163 		final AudioFormat f = ap.getFormat().clone();
164 		f.setSampleRateKHz( output.getSampleRateKHz() );
165 		return f;
166 	}
167 
168 	/**
169 	 *	Gets the top in the chain of processors (when constructing the chain in reverse order).
170 	 *	That is, the second processor will always be returned from this method. If the first
171 	 *	processor is not null, it will be made the source stream for the second processor.
172 	 *
173 	 *	@param ap The source processor
174 	 *	@param ap2 The destination processor
175 	 *	@return The destination processor
176 	 */
177 	private static AudioProcessor getProcessor( final AudioProcessor ap, final AudioProcessor ap2 )
178 	{
179 		if( ap == null )
180 			return ap2;
181 		ap2.setUnderlyingStream( ap );
182 		return ap2;
183 	}
184 
185 	@Override
186 	public SampleChunk process( final SampleChunk sample ) throws Exception
187 	{
188 		return this.processor.process( sample );
189 	}
190 
191 	/**
192 	 * 	Same as {@link #getFormat()} for this class - returns the output format.
193 	 *	@return The output format.
194 	 */
195 	public AudioFormat getOutputFormat()
196 	{
197 		return this.getFormat();
198 	}
199 }