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 }