1 /* 2 * Copyright (c) 1999, 2015, 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.AudioInputStream; 34 import javax.sound.sampled.AudioSystem; 35 36 /** 37 * Converts among signed/unsigned and little/big endianness of sampled. 38 * 39 * @author Jan Borgersen 40 */ 41 public final class PCMtoPCMCodec extends SunCodec { 42 43 private static final AudioFormat.Encoding[] inputEncodings = { 44 AudioFormat.Encoding.PCM_SIGNED, 45 AudioFormat.Encoding.PCM_UNSIGNED, 46 }; 47 48 private static final AudioFormat.Encoding[] outputEncodings = { 49 AudioFormat.Encoding.PCM_SIGNED, 50 AudioFormat.Encoding.PCM_UNSIGNED, 51 }; 52 53 /** 54 * Constructs a new PCMtoPCM codec object. 55 */ 56 public PCMtoPCMCodec() { 57 58 super( inputEncodings, outputEncodings); 59 } 60 61 @Override 62 public AudioFormat.Encoding[] getTargetEncodings(AudioFormat sourceFormat) { 63 64 final int sampleSize = sourceFormat.getSampleSizeInBits(); 65 AudioFormat.Encoding encoding = sourceFormat.getEncoding(); 66 if (sampleSize == 8) { 67 if (encoding.equals(AudioFormat.Encoding.PCM_SIGNED)) { 68 return new AudioFormat.Encoding[]{ 69 AudioFormat.Encoding.PCM_UNSIGNED 70 }; 71 } 72 if (encoding.equals(AudioFormat.Encoding.PCM_UNSIGNED)) { 73 return new AudioFormat.Encoding[]{ 74 AudioFormat.Encoding.PCM_SIGNED 75 }; 76 } 77 } else if (sampleSize == 16) { 78 if (encoding.equals(AudioFormat.Encoding.PCM_SIGNED) 79 || encoding.equals(AudioFormat.Encoding.PCM_UNSIGNED)) { 80 return new AudioFormat.Encoding[]{ 81 AudioFormat.Encoding.PCM_UNSIGNED, 82 AudioFormat.Encoding.PCM_SIGNED 83 }; 84 } 85 } 86 return new AudioFormat.Encoding[0]; 87 } 88 89 @Override 90 public AudioFormat[] getTargetFormats(AudioFormat.Encoding targetEncoding, AudioFormat sourceFormat){ 91 Objects.requireNonNull(targetEncoding); 92 93 // filter out targetEncoding from the old getOutputFormats( sourceFormat ) method 94 95 AudioFormat[] formats = getOutputFormats( sourceFormat ); 96 Vector<AudioFormat> newFormats = new Vector<>(); 97 for(int i=0; i<formats.length; i++ ) { 98 if( formats[i].getEncoding().equals( targetEncoding ) ) { 99 newFormats.addElement( formats[i] ); 100 } 101 } 102 103 AudioFormat[] formatArray = new AudioFormat[newFormats.size()]; 104 105 for (int i = 0; i < formatArray.length; i++) { 106 formatArray[i] = newFormats.elementAt(i); 107 } 108 109 return formatArray; 110 } 111 112 @Override 113 public AudioInputStream getAudioInputStream(AudioFormat.Encoding targetEncoding, AudioInputStream sourceStream) { 114 115 if( isConversionSupported(targetEncoding, sourceStream.getFormat()) ) { 116 117 AudioFormat sourceFormat = sourceStream.getFormat(); 118 AudioFormat targetFormat = new AudioFormat( targetEncoding, 119 sourceFormat.getSampleRate(), 120 sourceFormat.getSampleSizeInBits(), 121 sourceFormat.getChannels(), 122 sourceFormat.getFrameSize(), 123 sourceFormat.getFrameRate(), 124 sourceFormat.isBigEndian() ); 125 126 return getConvertedStream(targetFormat, sourceStream); 127 128 } else { 129 throw new IllegalArgumentException("Unsupported conversion: " + sourceStream.getFormat().toString() + " to " + targetEncoding.toString() ); 130 } 131 132 } 133 134 @Override 135 public AudioInputStream getAudioInputStream(AudioFormat targetFormat, AudioInputStream sourceStream){ 136 if (!isConversionSupported(targetFormat, sourceStream.getFormat())) 137 throw new IllegalArgumentException("Unsupported conversion: " 138 + sourceStream.getFormat().toString() + " to " 139 + targetFormat.toString()); 140 return getConvertedStream( targetFormat, sourceStream ); 141 } 142 143 /** 144 * Opens the codec with the specified parameters. 145 * @param stream stream from which data to be processed should be read 146 * @param outputFormat desired data format of the stream after processing 147 * @return stream from which processed data may be read 148 * @throws IllegalArgumentException if the format combination supplied is 149 * not supported. 150 */ 151 private AudioInputStream getConvertedStream(AudioFormat outputFormat, AudioInputStream stream) { 152 153 AudioInputStream cs = null; 154 155 AudioFormat inputFormat = stream.getFormat(); 156 157 if( inputFormat.matches(outputFormat) ) { 158 159 cs = stream; 160 } else { 161 162 cs = new PCMtoPCMCodecStream(stream, outputFormat); 163 } 164 return cs; 165 } 166 167 /** 168 * Obtains the set of output formats supported by the codec 169 * given a particular input format. 170 * If no output formats are supported for this input format, 171 * returns an array of length 0. 172 * @return array of supported output formats. 173 */ 174 private AudioFormat[] getOutputFormats(AudioFormat inputFormat) { 175 176 Vector<AudioFormat> formats = new Vector<>(); 177 AudioFormat format; 178 179 int sampleSize = inputFormat.getSampleSizeInBits(); 180 boolean isBigEndian = inputFormat.isBigEndian(); 181 182 183 if ( sampleSize==8 ) { 184 if ( AudioFormat.Encoding.PCM_SIGNED.equals(inputFormat.getEncoding()) ) { 185 186 format = new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, 187 inputFormat.getSampleRate(), 188 inputFormat.getSampleSizeInBits(), 189 inputFormat.getChannels(), 190 inputFormat.getFrameSize(), 191 inputFormat.getFrameRate(), 192 false ); 193 formats.addElement(format); 194 } 195 196 if ( AudioFormat.Encoding.PCM_UNSIGNED.equals(inputFormat.getEncoding()) ) { 197 198 format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 199 inputFormat.getSampleRate(), 200 inputFormat.getSampleSizeInBits(), 201 inputFormat.getChannels(), 202 inputFormat.getFrameSize(), 203 inputFormat.getFrameRate(), 204 false ); 205 formats.addElement(format); 206 } 207 208 } else if ( sampleSize==16 ) { 209 210 if ( AudioFormat.Encoding.PCM_SIGNED.equals(inputFormat.getEncoding()) && isBigEndian ) { 211 212 format = new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, 213 inputFormat.getSampleRate(), 214 inputFormat.getSampleSizeInBits(), 215 inputFormat.getChannels(), 216 inputFormat.getFrameSize(), 217 inputFormat.getFrameRate(), 218 true ); 219 formats.addElement(format); 220 format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 221 inputFormat.getSampleRate(), 222 inputFormat.getSampleSizeInBits(), 223 inputFormat.getChannels(), 224 inputFormat.getFrameSize(), 225 inputFormat.getFrameRate(), 226 false ); 227 formats.addElement(format); 228 format = new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, 229 inputFormat.getSampleRate(), 230 inputFormat.getSampleSizeInBits(), 231 inputFormat.getChannels(), 232 inputFormat.getFrameSize(), 233 inputFormat.getFrameRate(), 234 false ); 235 formats.addElement(format); 236 } 237 238 if ( AudioFormat.Encoding.PCM_UNSIGNED.equals(inputFormat.getEncoding()) && isBigEndian ) { 239 240 format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 241 inputFormat.getSampleRate(), 242 inputFormat.getSampleSizeInBits(), 243 inputFormat.getChannels(), 244 inputFormat.getFrameSize(), 245 inputFormat.getFrameRate(), 246 true ); 247 formats.addElement(format); 248 format = new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, 249 inputFormat.getSampleRate(), 250 inputFormat.getSampleSizeInBits(), 251 inputFormat.getChannels(), 252 inputFormat.getFrameSize(), 253 inputFormat.getFrameRate(), 254 false ); 255 formats.addElement(format); 256 format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 257 inputFormat.getSampleRate(), 258 inputFormat.getSampleSizeInBits(), 259 inputFormat.getChannels(), 260 inputFormat.getFrameSize(), 261 inputFormat.getFrameRate(), 262 false ); 263 formats.addElement(format); 264 } 265 266 if ( AudioFormat.Encoding.PCM_SIGNED.equals(inputFormat.getEncoding()) && !isBigEndian ) { 267 268 format = new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, 269 inputFormat.getSampleRate(), 270 inputFormat.getSampleSizeInBits(), 271 inputFormat.getChannels(), 272 inputFormat.getFrameSize(), 273 inputFormat.getFrameRate(), 274 false ); 275 formats.addElement(format); 276 format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 277 inputFormat.getSampleRate(), 278 inputFormat.getSampleSizeInBits(), 279 inputFormat.getChannels(), 280 inputFormat.getFrameSize(), 281 inputFormat.getFrameRate(), 282 true ); 283 formats.addElement(format); 284 format = new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, 285 inputFormat.getSampleRate(), 286 inputFormat.getSampleSizeInBits(), 287 inputFormat.getChannels(), 288 inputFormat.getFrameSize(), 289 inputFormat.getFrameRate(), 290 true ); 291 formats.addElement(format); 292 } 293 294 if ( AudioFormat.Encoding.PCM_UNSIGNED.equals(inputFormat.getEncoding()) && !isBigEndian ) { 295 296 format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 297 inputFormat.getSampleRate(), 298 inputFormat.getSampleSizeInBits(), 299 inputFormat.getChannels(), 300 inputFormat.getFrameSize(), 301 inputFormat.getFrameRate(), 302 false ); 303 formats.addElement(format); 304 format = new AudioFormat(AudioFormat.Encoding.PCM_UNSIGNED, 305 inputFormat.getSampleRate(), 306 inputFormat.getSampleSizeInBits(), 307 inputFormat.getChannels(), 308 inputFormat.getFrameSize(), 309 inputFormat.getFrameRate(), 310 true ); 311 formats.addElement(format); 312 format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 313 inputFormat.getSampleRate(), 314 inputFormat.getSampleSizeInBits(), 315 inputFormat.getChannels(), 316 inputFormat.getFrameSize(), 317 inputFormat.getFrameRate(), 318 true ); 319 formats.addElement(format); 320 } 321 } 322 AudioFormat[] formatArray; 323 324 synchronized(formats) { 325 326 formatArray = new AudioFormat[formats.size()]; 327 328 for (int i = 0; i < formatArray.length; i++) { 329 330 formatArray[i] = formats.elementAt(i); 331 } 332 } 333 334 return formatArray; 335 } 336 337 class PCMtoPCMCodecStream extends AudioInputStream { 338 339 private final int PCM_SWITCH_SIGNED_8BIT = 1; 340 private final int PCM_SWITCH_ENDIAN = 2; 341 private final int PCM_SWITCH_SIGNED_LE = 3; 342 private final int PCM_SWITCH_SIGNED_BE = 4; 343 private final int PCM_UNSIGNED_LE2SIGNED_BE = 5; 344 private final int PCM_SIGNED_LE2UNSIGNED_BE = 6; 345 private final int PCM_UNSIGNED_BE2SIGNED_LE = 7; 346 private final int PCM_SIGNED_BE2UNSIGNED_LE = 8; 347 348 private final int sampleSizeInBytes; 349 private int conversionType = 0; 350 351 352 PCMtoPCMCodecStream(AudioInputStream stream, AudioFormat outputFormat) { 353 354 super(stream, outputFormat, -1); 355 356 int sampleSizeInBits = 0; 357 AudioFormat.Encoding inputEncoding = null; 358 AudioFormat.Encoding outputEncoding = null; 359 boolean inputIsBigEndian; 360 boolean outputIsBigEndian; 361 362 AudioFormat inputFormat = stream.getFormat(); 363 364 // throw an IllegalArgumentException if not ok 365 if ( ! (isConversionSupported(inputFormat, outputFormat)) ) { 366 367 throw new IllegalArgumentException("Unsupported conversion: " + inputFormat.toString() + " to " + outputFormat.toString()); 368 } 369 370 inputEncoding = inputFormat.getEncoding(); 371 outputEncoding = outputFormat.getEncoding(); 372 inputIsBigEndian = inputFormat.isBigEndian(); 373 outputIsBigEndian = outputFormat.isBigEndian(); 374 sampleSizeInBits = inputFormat.getSampleSizeInBits(); 375 sampleSizeInBytes = sampleSizeInBits/8; 376 377 // determine conversion to perform 378 379 if( sampleSizeInBits==8 ) { 380 if( AudioFormat.Encoding.PCM_UNSIGNED.equals(inputEncoding) && 381 AudioFormat.Encoding.PCM_SIGNED.equals(outputEncoding) ) { 382 conversionType = PCM_SWITCH_SIGNED_8BIT; 383 if(Printer.debug) Printer.debug("PCMtoPCMCodecStream: conversionType = PCM_SWITCH_SIGNED_8BIT"); 384 385 } else if( AudioFormat.Encoding.PCM_SIGNED.equals(inputEncoding) && 386 AudioFormat.Encoding.PCM_UNSIGNED.equals(outputEncoding) ) { 387 conversionType = PCM_SWITCH_SIGNED_8BIT; 388 if(Printer.debug) Printer.debug("PCMtoPCMCodecStream: conversionType = PCM_SWITCH_SIGNED_8BIT"); 389 } 390 } else { 391 392 if( inputEncoding.equals(outputEncoding) && (inputIsBigEndian != outputIsBigEndian) ) { 393 394 conversionType = PCM_SWITCH_ENDIAN; 395 if(Printer.debug) Printer.debug("PCMtoPCMCodecStream: conversionType = PCM_SWITCH_ENDIAN"); 396 397 398 } else if (AudioFormat.Encoding.PCM_UNSIGNED.equals(inputEncoding) && !inputIsBigEndian && 399 AudioFormat.Encoding.PCM_SIGNED.equals(outputEncoding) && outputIsBigEndian) { 400 401 conversionType = PCM_UNSIGNED_LE2SIGNED_BE; 402 if(Printer.debug) Printer.debug("PCMtoPCMCodecStream: conversionType = PCM_UNSIGNED_LE2SIGNED_BE"); 403 404 } else if (AudioFormat.Encoding.PCM_SIGNED.equals(inputEncoding) && !inputIsBigEndian && 405 AudioFormat.Encoding.PCM_UNSIGNED.equals(outputEncoding) && outputIsBigEndian) { 406 407 conversionType = PCM_SIGNED_LE2UNSIGNED_BE; 408 if(Printer.debug) Printer.debug("PCMtoPCMCodecStream: conversionType = PCM_SIGNED_LE2UNSIGNED_BE"); 409 410 } else if (AudioFormat.Encoding.PCM_UNSIGNED.equals(inputEncoding) && inputIsBigEndian && 411 AudioFormat.Encoding.PCM_SIGNED.equals(outputEncoding) && !outputIsBigEndian) { 412 413 conversionType = PCM_UNSIGNED_BE2SIGNED_LE; 414 if(Printer.debug) Printer.debug("PCMtoPCMCodecStream: conversionType = PCM_UNSIGNED_BE2SIGNED_LE"); 415 416 } else if (AudioFormat.Encoding.PCM_SIGNED.equals(inputEncoding) && inputIsBigEndian && 417 AudioFormat.Encoding.PCM_UNSIGNED.equals(outputEncoding) && !outputIsBigEndian) { 418 419 conversionType = PCM_SIGNED_BE2UNSIGNED_LE; 420 if(Printer.debug) Printer.debug("PCMtoPCMCodecStream: conversionType = PCM_SIGNED_BE2UNSIGNED_LE"); 421 422 } 423 } 424 425 // set the audio stream length in frames if we know it 426 427 frameSize = inputFormat.getFrameSize(); 428 if( frameSize == AudioSystem.NOT_SPECIFIED ) { 429 frameSize=1; 430 } 431 if( stream instanceof AudioInputStream ) { 432 frameLength = stream.getFrameLength(); 433 } else { 434 frameLength = AudioSystem.NOT_SPECIFIED; 435 } 436 437 // set framePos to zero 438 framePos = 0; 439 440 } 441 442 /** 443 * Note that this only works for sign conversions. 444 * Other conversions require a read of at least 2 bytes. 445 */ 446 @Override 447 public int read() throws IOException { 448 449 // $$jb: do we want to implement this function? 450 451 int temp; 452 byte tempbyte; 453 454 if( frameSize==1 ) { 455 if( conversionType == PCM_SWITCH_SIGNED_8BIT ) { 456 temp = super.read(); 457 458 if( temp < 0 ) return temp; // EOF or error 459 460 tempbyte = (byte) (temp & 0xf); 461 tempbyte = (tempbyte >= 0) ? (byte)(0x80 | tempbyte) : (byte)(0x7F & tempbyte); 462 temp = (int) tempbyte & 0xf; 463 464 return temp; 465 466 } else { 467 // $$jb: what to return here? 468 throw new IOException("cannot read a single byte if frame size > 1"); 469 } 470 } else { 471 throw new IOException("cannot read a single byte if frame size > 1"); 472 } 473 } 474 475 @Override 476 public int read(byte[] b) throws IOException { 477 478 return read(b, 0, b.length); 479 } 480 481 @Override 482 public int read(byte[] b, int off, int len) throws IOException { 483 484 485 int i; 486 487 // don't read fractional frames 488 if ( len%frameSize != 0 ) { 489 len -= (len%frameSize); 490 } 491 // don't read past our own set length 492 if( (frameLength!=AudioSystem.NOT_SPECIFIED) && ( (len/frameSize) >(frameLength-framePos)) ) { 493 len = (int)(frameLength-framePos) * frameSize; 494 } 495 496 int readCount = super.read(b, off, len); 497 byte tempByte; 498 499 if(readCount<0) { // EOF or error 500 return readCount; 501 } 502 503 // now do the conversions 504 505 switch( conversionType ) { 506 507 case PCM_SWITCH_SIGNED_8BIT: 508 switchSigned8bit(b,off,len,readCount); 509 break; 510 511 case PCM_SWITCH_ENDIAN: 512 switchEndian(b,off,len,readCount); 513 break; 514 515 case PCM_SWITCH_SIGNED_LE: 516 switchSignedLE(b,off,len,readCount); 517 break; 518 519 case PCM_SWITCH_SIGNED_BE: 520 switchSignedBE(b,off,len,readCount); 521 break; 522 523 case PCM_UNSIGNED_LE2SIGNED_BE: 524 case PCM_SIGNED_LE2UNSIGNED_BE: 525 switchSignedLE(b,off,len,readCount); 526 switchEndian(b,off,len,readCount); 527 break; 528 529 case PCM_UNSIGNED_BE2SIGNED_LE: 530 case PCM_SIGNED_BE2UNSIGNED_LE: 531 switchSignedBE(b,off,len,readCount); 532 switchEndian(b,off,len,readCount); 533 break; 534 535 default: 536 // do nothing 537 } 538 539 // we've done the conversion, just return the readCount 540 return readCount; 541 542 } 543 544 private void switchSigned8bit(byte[] b, int off, int len, int readCount) { 545 546 for(int i=off; i < (off+readCount); i++) { 547 b[i] = (b[i] >= 0) ? (byte)(0x80 | b[i]) : (byte)(0x7F & b[i]); 548 } 549 } 550 551 private void switchSignedBE(byte[] b, int off, int len, int readCount) { 552 553 for(int i=off; i < (off+readCount); i+= sampleSizeInBytes ) { 554 b[i] = (b[i] >= 0) ? (byte)(0x80 | b[i]) : (byte)(0x7F & b[i]); 555 } 556 } 557 558 private void switchSignedLE(byte[] b, int off, int len, int readCount) { 559 560 for(int i=(off+sampleSizeInBytes-1); i < (off+readCount); i+= sampleSizeInBytes ) { 561 b[i] = (b[i] >= 0) ? (byte)(0x80 | b[i]) : (byte)(0x7F & b[i]); 562 } 563 } 564 565 private void switchEndian(byte[] b, int off, int len, int readCount) { 566 567 if(sampleSizeInBytes == 2) { 568 for(int i=off; i < (off+readCount); i += sampleSizeInBytes ) { 569 byte temp; 570 temp = b[i]; 571 b[i] = b[i+1]; 572 b[i+1] = temp; 573 } 574 } 575 } 576 } // end class PCMtoPCMCodecStream 577 } // end class PCMtoPCMCodec