001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030/** 031 * 032 */ 033package org.openimaj.audio.conversion; 034 035import org.openimaj.audio.AudioFormat; 036import org.openimaj.audio.AudioStream; 037import org.openimaj.audio.SampleChunk; 038import org.openimaj.audio.processor.AudioProcessor; 039 040/** 041 * A class that will work out which processors to instantiate to provide 042 * with a complete conversion from one audio format to another. 043 * 044 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 045 * @created 13 May 2013 046 */ 047public class AudioConverter extends AudioProcessor 048{ 049 /** The input format */ 050 private AudioFormat inputFormat = null; 051 052 /** The calculated audio processor */ 053 private AudioProcessor processor = null; 054 055 /** 056 * Chainable constructor that takes the stream to chain and the 057 * output format to convert the stream to. 058 * @param stream The stream to chain to 059 * @param output The required output format 060 */ 061 public AudioConverter( final AudioStream stream, final AudioFormat output ) 062 { 063 super( stream ); 064 this.inputFormat = stream.getFormat().clone(); 065 this.setFormat( output.clone() ); 066 this.processor = AudioConverter.calculateProcess( this.inputFormat, output ); 067 } 068 069 /** 070 * Constructor that takes the input and output formats. 071 * @param input The input format 072 * @param output The output format 073 */ 074 public AudioConverter( final AudioFormat input, final AudioFormat output ) 075 { 076 this.inputFormat = input.clone(); 077 this.setFormat( output.clone() ); 078 this.processor = AudioConverter.calculateProcess( input, output ); 079 } 080 081 /** 082 * Calculates the chain of processors that will convert from one format to the 083 * other and will return the first in the chain. If there is no processing to be 084 * done the method will return null. 085 * 086 * @param input The input format 087 * @param output The output format. 088 * @return The first processor in the stream. 089 */ 090 public static AudioProcessor calculateProcess( final AudioFormat input, final AudioFormat output ) 091 { 092 // If the input and output formats are the same, then there's 093 // no processing to do, so we return null. 094 if( input.equals( output ) ) 095 return null; 096 097 AudioProcessor ap = null; 098 099 // 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}