1 /* 2 * Copyright (c) 1999, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.media.sound; 27 28 import java.io.IOException; 29 import java.util.Objects; 30 import java.util.Vector; 31 32 import javax.sound.sampled.AudioFormat; 33 import javax.sound.sampled.AudioFormat.Encoding; 34 import javax.sound.sampled.AudioInputStream; 35 import javax.sound.sampled.AudioSystem; 36 import javax.sound.sampled.spi.FormatConversionProvider; 37 38 /** 39 * A-law encodes linear data, and decodes a-law data to linear data. 40 * 41 * @author Kara Kytle 42 */ 43 public final class AlawCodec extends FormatConversionProvider { 44 45 /* Tables used for A-law decoding */ 46 47 private static final byte[] ALAW_TABH = new byte[256]; 48 private static final byte[] ALAW_TABL = new byte[256]; 49 50 private static final short seg_end[] = { 51 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF 52 }; 53 54 /** 55 * Initializes the decode tables. 56 */ 57 static { 58 for (int i=0;i<256;i++) { 59 int input = i ^ 0x55; 60 int mantissa = (input & 0xf ) << 4; 61 int segment = (input & 0x70) >> 4; 62 int value = mantissa+8; 63 64 if(segment>=1) 65 value+=0x100; 66 if(segment>1) 67 value <<= (segment -1); 68 69 if( (input & 0x80)==0 ) 70 value = -value; 71 72 ALAW_TABL[i] = (byte)value; 73 ALAW_TABH[i] = (byte)(value>>8); 74 } 75 } 76 77 @Override 78 public AudioFormat.Encoding[] getSourceEncodings() { 79 return new Encoding[]{Encoding.ALAW, Encoding.PCM_SIGNED}; 80 } 81 82 @Override 83 public AudioFormat.Encoding[] getTargetEncodings() { 84 return getSourceEncodings(); 85 } 86 87 @Override 88 public AudioFormat.Encoding[] getTargetEncodings(AudioFormat sourceFormat){ 89 90 if( sourceFormat.getEncoding().equals( AudioFormat.Encoding.PCM_SIGNED )) { 91 92 if( sourceFormat.getSampleSizeInBits() == 16 ) { 93 94 AudioFormat.Encoding enc[] = new AudioFormat.Encoding[1]; 95 enc[0] = AudioFormat.Encoding.ALAW; 96 return enc; 97 98 } else { 99 return new AudioFormat.Encoding[0]; 100 } 101 } else if( sourceFormat.getEncoding().equals( AudioFormat.Encoding.ALAW ) ) { 102 103 if( sourceFormat.getSampleSizeInBits() == 8 ) { 104 105 AudioFormat.Encoding enc[] = new AudioFormat.Encoding[1]; 106 enc[0] = AudioFormat.Encoding.PCM_SIGNED; 107 return enc; 108 109 } else { 110 return new AudioFormat.Encoding[0]; 111 } 112 113 } else { 114 return new AudioFormat.Encoding[0]; 115 } 116 } 117 118 @Override 119 public AudioFormat[] getTargetFormats(AudioFormat.Encoding targetEncoding, AudioFormat sourceFormat){ 120 Objects.requireNonNull(sourceFormat); 121 if( (targetEncoding.equals( AudioFormat.Encoding.PCM_SIGNED ) && sourceFormat.getEncoding().equals( AudioFormat.Encoding.ALAW)) || 122 (targetEncoding.equals( AudioFormat.Encoding.ALAW) && sourceFormat.getEncoding().equals( AudioFormat.Encoding.PCM_SIGNED)) ) { 123 return getOutputFormats( sourceFormat ); 124 } else { 125 return new AudioFormat[0]; 126 } 127 } 128 129 @Override 130 public AudioInputStream getAudioInputStream(AudioFormat.Encoding targetEncoding, AudioInputStream sourceStream){ 131 AudioFormat sourceFormat = sourceStream.getFormat(); 132 AudioFormat.Encoding sourceEncoding = sourceFormat.getEncoding(); 133 134 if( !isConversionSupported(targetEncoding,sourceStream.getFormat()) ) { 135 throw new IllegalArgumentException("Unsupported conversion: " + sourceStream.getFormat().toString() + " to " + targetEncoding.toString()); 136 } 137 if( sourceEncoding.equals( targetEncoding ) ) { 138 return sourceStream; 139 } 140 AudioFormat targetFormat = null; 141 if( sourceEncoding.equals( AudioFormat.Encoding.ALAW ) && 142 targetEncoding.equals( AudioFormat.Encoding.PCM_SIGNED ) ) { 143 144 targetFormat = new AudioFormat( targetEncoding, 145 sourceFormat.getSampleRate(), 146 16, 147 sourceFormat.getChannels(), 148 2*sourceFormat.getChannels(), 149 sourceFormat.getSampleRate(), 150 sourceFormat.isBigEndian()); 151 152 } else if( sourceEncoding.equals( AudioFormat.Encoding.PCM_SIGNED ) && 153 targetEncoding.equals( AudioFormat.Encoding.ALAW ) ) { 154 155 targetFormat = new AudioFormat( targetEncoding, 156 sourceFormat.getSampleRate(), 157 8, 158 sourceFormat.getChannels(), 159 sourceFormat.getChannels(), 160 sourceFormat.getSampleRate(), 161 false); 162 } else { 163 throw new IllegalArgumentException("Unsupported conversion: " + sourceStream.getFormat().toString() + " to " + targetEncoding.toString()); 164 } 165 return getConvertedStream(targetFormat, sourceStream); 166 } 167 168 @Override 169 public AudioInputStream getAudioInputStream(AudioFormat targetFormat, AudioInputStream sourceStream){ 170 if (!isConversionSupported(targetFormat, sourceStream.getFormat())) 171 throw new IllegalArgumentException("Unsupported conversion: " 172 + sourceStream.getFormat().toString() + " to " 173 + targetFormat.toString()); 174 return getConvertedStream( targetFormat, sourceStream ); 175 } 176 177 /** 178 * Opens the codec with the specified parameters. 179 * @param stream stream from which data to be processed should be read 180 * @param outputFormat desired data format of the stream after processing 181 * @return stream from which processed data may be read 182 * @throws IllegalArgumentException if the format combination supplied is 183 * not supported. 184 */ 185 private AudioInputStream getConvertedStream(AudioFormat outputFormat, AudioInputStream stream) { 186 187 AudioInputStream cs = null; 188 AudioFormat inputFormat = stream.getFormat(); 189 190 if( inputFormat.matches(outputFormat) ) { 191 cs = stream; 192 } else { 193 cs = new AlawCodecStream(stream, outputFormat); 194 } 195 196 return cs; 197 } 198 199 /** 200 * Obtains the set of output formats supported by the codec 201 * given a particular input format. 202 * If no output formats are supported for this input format, 203 * returns an array of length 0. 204 * @return array of supported output formats. 205 */ 206 private AudioFormat[] getOutputFormats(AudioFormat inputFormat) { 207 208 209 Vector<AudioFormat> formats = new Vector<>(); 210 AudioFormat format; 211 212 if (inputFormat.getSampleSizeInBits() == 16 213 && AudioFormat.Encoding.PCM_SIGNED.equals(inputFormat.getEncoding())) { 214 format = new AudioFormat(AudioFormat.Encoding.ALAW, 215 inputFormat.getSampleRate(), 8, 216 inputFormat.getChannels(), 217 inputFormat.getChannels(), 218 inputFormat.getSampleRate(), false); 219 formats.addElement(format); 220 } 221 if (inputFormat.getSampleSizeInBits() == 8 222 && AudioFormat.Encoding.ALAW.equals(inputFormat.getEncoding())) { 223 format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 224 inputFormat.getSampleRate(), 16, 225 inputFormat.getChannels(), 226 inputFormat.getChannels() * 2, 227 inputFormat.getSampleRate(), false); 228 formats.addElement(format); 229 format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 230 inputFormat.getSampleRate(), 16, 231 inputFormat.getChannels(), 232 inputFormat.getChannels() * 2, 233 inputFormat.getSampleRate(), true); 234 formats.addElement(format); 235 } 236 237 AudioFormat[] formatArray = new AudioFormat[formats.size()]; 238 for (int i = 0; i < formatArray.length; i++) { 239 formatArray[i] = formats.elementAt(i); 240 } 241 return formatArray; 242 } 243 244 245 private final class AlawCodecStream extends AudioInputStream { 246 247 // tempBuffer required only for encoding (when encode is true) 248 private static final int tempBufferSize = 64; 249 private byte tempBuffer [] = null; 250 251 /** 252 * True to encode to a-law, false to decode to linear 253 */ 254 boolean encode = false; 255 256 AudioFormat encodeFormat; 257 AudioFormat decodeFormat; 258 259 byte tabByte1[] = null; 260 byte tabByte2[] = null; 261 int highByte = 0; 262 int lowByte = 1; 263 264 AlawCodecStream(AudioInputStream stream, AudioFormat outputFormat) { 265 266 super(stream, outputFormat, -1); 267 268 AudioFormat inputFormat = stream.getFormat(); 269 270 // throw an IllegalArgumentException if not ok 271 if ( ! (isConversionSupported(outputFormat, inputFormat)) ) { 272 273 throw new IllegalArgumentException("Unsupported conversion: " + inputFormat.toString() + " to " + outputFormat.toString()); 274 } 275 276 //$$fb 2002-07-18: fix for 4714846: JavaSound ULAW (8-bit) encoder erroneously depends on endian-ness 277 boolean PCMIsBigEndian; 278 279 // determine whether we are encoding or decoding 280 if (AudioFormat.Encoding.ALAW.equals(inputFormat.getEncoding())) { 281 encode = false; 282 encodeFormat = inputFormat; 283 decodeFormat = outputFormat; 284 PCMIsBigEndian = outputFormat.isBigEndian(); 285 } else { 286 encode = true; 287 encodeFormat = outputFormat; 288 decodeFormat = inputFormat; 289 PCMIsBigEndian = inputFormat.isBigEndian(); 290 tempBuffer = new byte[tempBufferSize]; 291 } 292 293 if (PCMIsBigEndian) { 294 tabByte1 = ALAW_TABH; 295 tabByte2 = ALAW_TABL; 296 highByte = 0; 297 lowByte = 1; 298 } else { 299 tabByte1 = ALAW_TABL; 300 tabByte2 = ALAW_TABH; 301 highByte = 1; 302 lowByte = 0; 303 } 304 305 // set the AudioInputStream length in frames if we know it 306 if (stream instanceof AudioInputStream) { 307 frameLength = stream.getFrameLength(); 308 } 309 310 // set framePos to zero 311 framePos = 0; 312 frameSize = inputFormat.getFrameSize(); 313 if( frameSize==AudioSystem.NOT_SPECIFIED ) { 314 frameSize=1; 315 } 316 } 317 318 319 /* 320 * $$jb 2/23/99 321 * Used to determine segment number in aLaw encoding 322 */ 323 private short search(short val, short table[], short size) { 324 for(short i = 0; i < size; i++) { 325 if (val <= table[i]) { return i; } 326 } 327 return size; 328 } 329 330 /** 331 * Note that this won't actually read anything; must read in 332 * two-byte units. 333 */ 334 @Override 335 public int read() throws IOException { 336 337 byte[] b = new byte[1]; 338 return read(b, 0, b.length); 339 } 340 341 @Override 342 public int read(byte[] b) throws IOException { 343 344 return read(b, 0, b.length); 345 } 346 347 @Override 348 public int read(byte[] b, int off, int len) throws IOException { 349 350 // don't read fractional frames 351 if( len%frameSize != 0 ) { 352 len -= (len%frameSize); 353 } 354 355 if (encode) { 356 357 short QUANT_MASK = 0xF; 358 short SEG_SHIFT = 4; 359 short mask; 360 short seg; 361 int adj; 362 int i; 363 364 short sample; 365 byte enc; 366 367 int readCount = 0; 368 int currentPos = off; 369 int readLeft = len*2; 370 int readLen = ( (readLeft>tempBufferSize) ? tempBufferSize : readLeft ); 371 372 while ((readCount = super.read(tempBuffer,0,readLen))>0) { 373 374 for (i = 0; i < readCount; i+=2) { 375 376 /* Get the sample from the tempBuffer */ 377 sample = (short)(( (tempBuffer[i + highByte]) << 8) & 0xFF00); 378 sample |= (short)( (tempBuffer[i + lowByte]) & 0xFF); 379 380 if(sample >= 0) { 381 mask = 0xD5; 382 } else { 383 mask = 0x55; 384 sample = (short)(-sample - 8); 385 } 386 /* Convert the scaled magnitude to segment number. */ 387 seg = search(sample, seg_end, (short) 8); 388 /* 389 * Combine the sign, segment, quantization bits 390 */ 391 if (seg >= 8) { /* out of range, return maximum value. */ 392 enc = (byte) (0x7F ^ mask); 393 } else { 394 enc = (byte) (seg << SEG_SHIFT); 395 if(seg < 2) { 396 enc |= (byte) ( (sample >> 4) & QUANT_MASK); 397 } else { 398 enc |= (byte) ( (sample >> (seg + 3)) & QUANT_MASK ); 399 } 400 enc ^= mask; 401 } 402 /* Now put the encoded sample where it belongs */ 403 b[currentPos] = enc; 404 currentPos++; 405 } 406 /* And update pointers and counters for next iteration */ 407 readLeft -= readCount; 408 readLen = ( (readLeft>tempBufferSize) ? tempBufferSize : readLeft ); 409 } 410 411 if( currentPos==off && readCount<0 ) { // EOF or error 412 return readCount; 413 } 414 415 return (currentPos - off); /* Number of bytes written to new buffer */ 416 417 } else { 418 419 int i; 420 int readLen = len/2; 421 int readOffset = off + len/2; 422 int readCount = super.read(b, readOffset, readLen); 423 424 for (i = off; i < (off + (readCount*2)); i+=2) { 425 b[i] = tabByte1[b[readOffset] & 0xFF]; 426 b[i+1] = tabByte2[b[readOffset] & 0xFF]; 427 readOffset++; 428 } 429 430 if( readCount<0 ) { // EOF or error 431 return readCount; 432 } 433 434 return (i - off); 435 } 436 } 437 438 @Override 439 public long skip(final long n) throws IOException { 440 // Implementation of this method assumes that we support 441 // encoding/decoding from/to 8/16 bits only 442 return encode ? super.skip(n * 2) / 2 : super.skip(n / 2) * 2; 443 } 444 } // end class AlawCodecStream 445 } // end class ALAW