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