1 /*
   2  * Copyright (c) 2008, 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 package com.sun.media.sound;
  26 
  27 import java.io.IOException;
  28 import java.io.InputStream;
  29 import java.util.ArrayList;
  30 import java.util.Arrays;
  31 import java.util.Objects;
  32 
  33 import javax.sound.sampled.AudioFormat;
  34 import javax.sound.sampled.AudioInputStream;
  35 import javax.sound.sampled.AudioSystem;
  36 import javax.sound.sampled.AudioFormat.Encoding;
  37 import javax.sound.sampled.spi.FormatConversionProvider;
  38 
  39 /**
  40  * This class is used to convert between 8,16,24,32 bit signed/unsigned
  41  * big/litle endian fixed/floating stereo/mono/multi-channel audio streams and
  42  * perform sample-rate conversion if needed.
  43  *
  44  * @author Karl Helgason
  45  */
  46 public final class AudioFloatFormatConverter extends FormatConversionProvider {
  47 
  48     private static class AudioFloatFormatConverterInputStream extends
  49             InputStream {
  50         private final AudioFloatConverter converter;
  51 
  52         private final AudioFloatInputStream stream;
  53 
  54         private float[] readfloatbuffer;
  55 
  56         private final int fsize;
  57 
  58         AudioFloatFormatConverterInputStream(AudioFormat targetFormat,
  59                 AudioFloatInputStream stream) {
  60             this.stream = stream;
  61             converter = AudioFloatConverter.getConverter(targetFormat);
  62             fsize = ((targetFormat.getSampleSizeInBits() + 7) / 8);
  63         }
  64 
  65         public int read() throws IOException {
  66             byte[] b = new byte[1];
  67             int ret = read(b);
  68             if (ret < 0)
  69                 return ret;
  70             return b[0] & 0xFF;
  71         }
  72 
  73         public int read(byte[] b, int off, int len) throws IOException {
  74 
  75             int flen = len / fsize;
  76             if (readfloatbuffer == null || readfloatbuffer.length < flen)
  77                 readfloatbuffer = new float[flen];
  78             int ret = stream.read(readfloatbuffer, 0, flen);
  79             if (ret < 0)
  80                 return ret;
  81             converter.toByteArray(readfloatbuffer, 0, ret, b, off);
  82             return ret * fsize;
  83         }
  84 
  85         public int available() throws IOException {
  86             int ret = stream.available();
  87             if (ret < 0)
  88                 return ret;
  89             return ret * fsize;
  90         }
  91 
  92         public void close() throws IOException {
  93             stream.close();
  94         }
  95 
  96         public synchronized void mark(int readlimit) {
  97             stream.mark(readlimit * fsize);
  98         }
  99 
 100         public boolean markSupported() {
 101             return stream.markSupported();
 102         }
 103 
 104         public synchronized void reset() throws IOException {
 105             stream.reset();
 106         }
 107 
 108         public long skip(long n) throws IOException {
 109             long ret = stream.skip(n / fsize);
 110             if (ret < 0)
 111                 return ret;
 112             return ret * fsize;
 113         }
 114 
 115     }
 116 
 117     private static class AudioFloatInputStreamChannelMixer extends
 118             AudioFloatInputStream {
 119 
 120         private final int targetChannels;
 121 
 122         private final int sourceChannels;
 123 
 124         private final AudioFloatInputStream ais;
 125 
 126         private final AudioFormat targetFormat;
 127 
 128         private float[] conversion_buffer;
 129 
 130         AudioFloatInputStreamChannelMixer(AudioFloatInputStream ais,
 131                 int targetChannels) {
 132             this.sourceChannels = ais.getFormat().getChannels();
 133             this.targetChannels = targetChannels;
 134             this.ais = ais;
 135             AudioFormat format = ais.getFormat();
 136             targetFormat = new AudioFormat(format.getEncoding(), format
 137                     .getSampleRate(), format.getSampleSizeInBits(),
 138                     targetChannels, (format.getFrameSize() / sourceChannels)
 139                             * targetChannels, format.getFrameRate(), format
 140                             .isBigEndian());
 141         }
 142 
 143         public int available() throws IOException {
 144             return (ais.available() / sourceChannels) * targetChannels;
 145         }
 146 
 147         public void close() throws IOException {
 148             ais.close();
 149         }
 150 
 151         public AudioFormat getFormat() {
 152             return targetFormat;
 153         }
 154 
 155         public long getFrameLength() {
 156             return ais.getFrameLength();
 157         }
 158 
 159         public void mark(int readlimit) {
 160             ais.mark((readlimit / targetChannels) * sourceChannels);
 161         }
 162 
 163         public boolean markSupported() {
 164             return ais.markSupported();
 165         }
 166 
 167         public int read(float[] b, int off, int len) throws IOException {
 168             int len2 = (len / targetChannels) * sourceChannels;
 169             if (conversion_buffer == null || conversion_buffer.length < len2)
 170                 conversion_buffer = new float[len2];
 171             int ret = ais.read(conversion_buffer, 0, len2);
 172             if (ret < 0)
 173                 return ret;
 174             if (sourceChannels == 1) {
 175                 int cs = targetChannels;
 176                 for (int c = 0; c < targetChannels; c++) {
 177                     for (int i = 0, ix = off + c; i < len2; i++, ix += cs) {
 178                         b[ix] = conversion_buffer[i];
 179                     }
 180                 }
 181             } else if (targetChannels == 1) {
 182                 int cs = sourceChannels;
 183                 for (int i = 0, ix = off; i < len2; i += cs, ix++) {
 184                     b[ix] = conversion_buffer[i];
 185                 }
 186                 for (int c = 1; c < sourceChannels; c++) {
 187                     for (int i = c, ix = off; i < len2; i += cs, ix++) {
 188                         b[ix] += conversion_buffer[i];
 189                     }
 190                 }
 191                 float vol = 1f / ((float) sourceChannels);
 192                 for (int i = 0, ix = off; i < len2; i += cs, ix++) {
 193                     b[ix] *= vol;
 194                 }
 195             } else {
 196                 int minChannels = Math.min(sourceChannels, targetChannels);
 197                 int off_len = off + len;
 198                 int ct = targetChannels;
 199                 int cs = sourceChannels;
 200                 for (int c = 0; c < minChannels; c++) {
 201                     for (int i = off + c, ix = c; i < off_len; i += ct, ix += cs) {
 202                         b[i] = conversion_buffer[ix];
 203                     }
 204                 }
 205                 for (int c = minChannels; c < targetChannels; c++) {
 206                     for (int i = off + c; i < off_len; i += ct) {
 207                         b[i] = 0;
 208                     }
 209                 }
 210             }
 211             return (ret / sourceChannels) * targetChannels;
 212         }
 213 
 214         public void reset() throws IOException {
 215             ais.reset();
 216         }
 217 
 218         public long skip(long len) throws IOException {
 219             long ret = ais.skip((len / targetChannels) * sourceChannels);
 220             if (ret < 0)
 221                 return ret;
 222             return (ret / sourceChannels) * targetChannels;
 223         }
 224 
 225     }
 226 
 227     private static class AudioFloatInputStreamResampler extends
 228             AudioFloatInputStream {
 229 
 230         private final AudioFloatInputStream ais;
 231 
 232         private final AudioFormat targetFormat;
 233 
 234         private float[] skipbuffer;
 235 
 236         private SoftAbstractResampler resampler;
 237 
 238         private final float[] pitch = new float[1];
 239 
 240         private final float[] ibuffer2;
 241 
 242         private final float[][] ibuffer;
 243 
 244         private float ibuffer_index = 0;
 245 
 246         private int ibuffer_len = 0;
 247 
 248         private final int nrofchannels;
 249 
 250         private float[][] cbuffer;
 251 
 252         private final int buffer_len = 512;
 253 
 254         private final int pad;
 255 
 256         private final int pad2;
 257 
 258         private final float[] ix = new float[1];
 259 
 260         private final int[] ox = new int[1];
 261 
 262         private float[][] mark_ibuffer = null;
 263 
 264         private float mark_ibuffer_index = 0;
 265 
 266         private int mark_ibuffer_len = 0;
 267 
 268         AudioFloatInputStreamResampler(AudioFloatInputStream ais,
 269                 AudioFormat format) {
 270             this.ais = ais;
 271             AudioFormat sourceFormat = ais.getFormat();
 272             targetFormat = new AudioFormat(sourceFormat.getEncoding(), format
 273                     .getSampleRate(), sourceFormat.getSampleSizeInBits(),
 274                     sourceFormat.getChannels(), sourceFormat.getFrameSize(),
 275                     format.getSampleRate(), sourceFormat.isBigEndian());
 276             nrofchannels = targetFormat.getChannels();
 277             Object interpolation = format.getProperty("interpolation");
 278             if (interpolation != null && (interpolation instanceof String)) {
 279                 String resamplerType = (String) interpolation;
 280                 if (resamplerType.equalsIgnoreCase("point"))
 281                     this.resampler = new SoftPointResampler();
 282                 if (resamplerType.equalsIgnoreCase("linear"))
 283                     this.resampler = new SoftLinearResampler2();
 284                 if (resamplerType.equalsIgnoreCase("linear1"))
 285                     this.resampler = new SoftLinearResampler();
 286                 if (resamplerType.equalsIgnoreCase("linear2"))
 287                     this.resampler = new SoftLinearResampler2();
 288                 if (resamplerType.equalsIgnoreCase("cubic"))
 289                     this.resampler = new SoftCubicResampler();
 290                 if (resamplerType.equalsIgnoreCase("lanczos"))
 291                     this.resampler = new SoftLanczosResampler();
 292                 if (resamplerType.equalsIgnoreCase("sinc"))
 293                     this.resampler = new SoftSincResampler();
 294             }
 295             if (resampler == null)
 296                 resampler = new SoftLinearResampler2(); // new
 297                                                         // SoftLinearResampler2();
 298             pitch[0] = sourceFormat.getSampleRate() / format.getSampleRate();
 299             pad = resampler.getPadding();
 300             pad2 = pad * 2;
 301             ibuffer = new float[nrofchannels][buffer_len + pad2];
 302             ibuffer2 = new float[nrofchannels * buffer_len];
 303             ibuffer_index = buffer_len + pad;
 304             ibuffer_len = buffer_len;
 305         }
 306 
 307         public int available() throws IOException {
 308             return 0;
 309         }
 310 
 311         public void close() throws IOException {
 312             ais.close();
 313         }
 314 
 315         public AudioFormat getFormat() {
 316             return targetFormat;
 317         }
 318 
 319         public long getFrameLength() {
 320             return AudioSystem.NOT_SPECIFIED; // ais.getFrameLength();
 321         }
 322 
 323         public void mark(int readlimit) {
 324             ais.mark((int) (readlimit * pitch[0]));
 325             mark_ibuffer_index = ibuffer_index;
 326             mark_ibuffer_len = ibuffer_len;
 327             if (mark_ibuffer == null) {
 328                 mark_ibuffer = new float[ibuffer.length][ibuffer[0].length];
 329             }
 330             for (int c = 0; c < ibuffer.length; c++) {
 331                 float[] from = ibuffer[c];
 332                 float[] to = mark_ibuffer[c];
 333                 for (int i = 0; i < to.length; i++) {
 334                     to[i] = from[i];
 335                 }
 336             }
 337         }
 338 
 339         public boolean markSupported() {
 340             return ais.markSupported();
 341         }
 342 
 343         private void readNextBuffer() throws IOException {
 344 
 345             if (ibuffer_len == -1)
 346                 return;
 347 
 348             for (int c = 0; c < nrofchannels; c++) {
 349                 float[] buff = ibuffer[c];
 350                 int buffer_len_pad = ibuffer_len + pad2;
 351                 for (int i = ibuffer_len, ix = 0; i < buffer_len_pad; i++, ix++) {
 352                     buff[ix] = buff[i];
 353                 }
 354             }
 355 
 356             ibuffer_index -= (ibuffer_len);
 357 
 358             ibuffer_len = ais.read(ibuffer2);
 359             if (ibuffer_len >= 0) {
 360                 while (ibuffer_len < ibuffer2.length) {
 361                     int ret = ais.read(ibuffer2, ibuffer_len, ibuffer2.length
 362                             - ibuffer_len);
 363                     if (ret == -1)
 364                         break;
 365                     ibuffer_len += ret;
 366                 }
 367                 Arrays.fill(ibuffer2, ibuffer_len, ibuffer2.length, 0);
 368                 ibuffer_len /= nrofchannels;
 369             } else {
 370                 Arrays.fill(ibuffer2, 0, ibuffer2.length, 0);
 371             }
 372 
 373             int ibuffer2_len = ibuffer2.length;
 374             for (int c = 0; c < nrofchannels; c++) {
 375                 float[] buff = ibuffer[c];
 376                 for (int i = c, ix = pad2; i < ibuffer2_len; i += nrofchannels, ix++) {
 377                     buff[ix] = ibuffer2[i];
 378                 }
 379             }
 380 
 381         }
 382 
 383         public int read(float[] b, int off, int len) throws IOException {
 384 
 385             if (cbuffer == null || cbuffer[0].length < len / nrofchannels) {
 386                 cbuffer = new float[nrofchannels][len / nrofchannels];
 387             }
 388             if (ibuffer_len == -1)
 389                 return -1;
 390             if (len < 0)
 391                 return 0;
 392             int offlen = off + len;
 393             int remain = len / nrofchannels;
 394             int destPos = 0;
 395             int in_end = ibuffer_len;
 396             while (remain > 0) {
 397                 if (ibuffer_len >= 0) {
 398                     if (ibuffer_index >= (ibuffer_len + pad))
 399                         readNextBuffer();
 400                     in_end = ibuffer_len + pad;
 401                 }
 402 
 403                 if (ibuffer_len < 0) {
 404                     in_end = pad2;
 405                     if (ibuffer_index >= in_end)
 406                         break;
 407                 }
 408 
 409                 if (ibuffer_index < 0)
 410                     break;
 411                 int preDestPos = destPos;
 412                 for (int c = 0; c < nrofchannels; c++) {
 413                     ix[0] = ibuffer_index;
 414                     ox[0] = destPos;
 415                     float[] buff = ibuffer[c];
 416                     resampler.interpolate(buff, ix, in_end, pitch, 0,
 417                             cbuffer[c], ox, len / nrofchannels);
 418                 }
 419                 ibuffer_index = ix[0];
 420                 destPos = ox[0];
 421                 remain -= destPos - preDestPos;
 422             }
 423             for (int c = 0; c < nrofchannels; c++) {
 424                 int ix = 0;
 425                 float[] buff = cbuffer[c];
 426                 for (int i = c + off; i < offlen; i += nrofchannels) {
 427                     b[i] = buff[ix++];
 428                 }
 429             }
 430             return len - remain * nrofchannels;
 431         }
 432 
 433         public void reset() throws IOException {
 434             ais.reset();
 435             if (mark_ibuffer == null)
 436                 return;
 437             ibuffer_index = mark_ibuffer_index;
 438             ibuffer_len = mark_ibuffer_len;
 439             for (int c = 0; c < ibuffer.length; c++) {
 440                 float[] from = mark_ibuffer[c];
 441                 float[] to = ibuffer[c];
 442                 for (int i = 0; i < to.length; i++) {
 443                     to[i] = from[i];
 444                 }
 445             }
 446 
 447         }
 448 
 449         public long skip(long len) throws IOException {
 450             if (len < 0)
 451                 return 0;
 452             if (skipbuffer == null)
 453                 skipbuffer = new float[1024 * targetFormat.getFrameSize()];
 454             float[] l_skipbuffer = skipbuffer;
 455             long remain = len;
 456             while (remain > 0) {
 457                 int ret = read(l_skipbuffer, 0, (int) Math.min(remain,
 458                         skipbuffer.length));
 459                 if (ret < 0) {
 460                     if (remain == len)
 461                         return ret;
 462                     break;
 463                 }
 464                 remain -= ret;
 465             }
 466             return len - remain;
 467 
 468         }
 469 
 470     }
 471 
 472     private final Encoding[] formats = {Encoding.PCM_SIGNED,
 473                                         Encoding.PCM_UNSIGNED,
 474                                         Encoding.PCM_FLOAT};
 475 
 476     public AudioInputStream getAudioInputStream(Encoding targetEncoding,
 477             AudioInputStream sourceStream) {
 478         if (sourceStream.getFormat().getEncoding().equals(targetEncoding))
 479             return sourceStream;
 480         AudioFormat format = sourceStream.getFormat();
 481         int channels = format.getChannels();
 482         Encoding encoding = targetEncoding;
 483         float samplerate = format.getSampleRate();
 484         int bits = format.getSampleSizeInBits();
 485         boolean bigendian = format.isBigEndian();
 486         if (targetEncoding.equals(Encoding.PCM_FLOAT))
 487             bits = 32;
 488         AudioFormat targetFormat = new AudioFormat(encoding, samplerate, bits,
 489                 channels, channels * bits / 8, samplerate, bigendian);
 490         return getAudioInputStream(targetFormat, sourceStream);
 491     }
 492 
 493     public AudioInputStream getAudioInputStream(AudioFormat targetFormat,
 494             AudioInputStream sourceStream) {
 495         if (!isConversionSupported(targetFormat, sourceStream.getFormat()))
 496             throw new IllegalArgumentException("Unsupported conversion: "
 497                     + sourceStream.getFormat().toString() + " to "
 498                     + targetFormat.toString());
 499         return getAudioInputStream(targetFormat, AudioFloatInputStream
 500                 .getInputStream(sourceStream));
 501     }
 502 
 503     public AudioInputStream getAudioInputStream(AudioFormat targetFormat,
 504             AudioFloatInputStream sourceStream) {
 505 
 506         if (!isConversionSupported(targetFormat, sourceStream.getFormat()))
 507             throw new IllegalArgumentException("Unsupported conversion: "
 508                     + sourceStream.getFormat().toString() + " to "
 509                     + targetFormat.toString());
 510         if (targetFormat.getChannels() != sourceStream.getFormat()
 511                 .getChannels())
 512             sourceStream = new AudioFloatInputStreamChannelMixer(sourceStream,
 513                     targetFormat.getChannels());
 514         if (Math.abs(targetFormat.getSampleRate()
 515                 - sourceStream.getFormat().getSampleRate()) > 0.000001)
 516             sourceStream = new AudioFloatInputStreamResampler(sourceStream,
 517                     targetFormat);
 518         return new AudioInputStream(new AudioFloatFormatConverterInputStream(
 519                 targetFormat, sourceStream), targetFormat, sourceStream
 520                 .getFrameLength());
 521     }
 522 
 523     public Encoding[] getSourceEncodings() {
 524         return new Encoding[] { Encoding.PCM_SIGNED, Encoding.PCM_UNSIGNED,
 525                 Encoding.PCM_FLOAT };
 526     }
 527 
 528     public Encoding[] getTargetEncodings() {
 529         return new Encoding[] { Encoding.PCM_SIGNED, Encoding.PCM_UNSIGNED,
 530                 Encoding.PCM_FLOAT };
 531     }
 532 
 533     public Encoding[] getTargetEncodings(AudioFormat sourceFormat) {
 534         if (AudioFloatConverter.getConverter(sourceFormat) == null)
 535             return new Encoding[0];
 536         return new Encoding[] { Encoding.PCM_SIGNED, Encoding.PCM_UNSIGNED,
 537                 Encoding.PCM_FLOAT };
 538     }
 539 
 540     public AudioFormat[] getTargetFormats(Encoding targetEncoding,
 541             AudioFormat sourceFormat) {
 542         Objects.requireNonNull(targetEncoding);
 543         if (AudioFloatConverter.getConverter(sourceFormat) == null)
 544             return new AudioFormat[0];
 545         int channels = sourceFormat.getChannels();
 546 
 547         ArrayList<AudioFormat> formats = new ArrayList<AudioFormat>();
 548 
 549         if (targetEncoding.equals(Encoding.PCM_SIGNED))
 550             formats.add(new AudioFormat(Encoding.PCM_SIGNED,
 551                     AudioSystem.NOT_SPECIFIED, 8, channels, channels,
 552                     AudioSystem.NOT_SPECIFIED, false));
 553         if (targetEncoding.equals(Encoding.PCM_UNSIGNED))
 554             formats.add(new AudioFormat(Encoding.PCM_UNSIGNED,
 555                     AudioSystem.NOT_SPECIFIED, 8, channels, channels,
 556                     AudioSystem.NOT_SPECIFIED, false));
 557 
 558         for (int bits = 16; bits < 32; bits += 8) {
 559             if (targetEncoding.equals(Encoding.PCM_SIGNED)) {
 560                 formats.add(new AudioFormat(Encoding.PCM_SIGNED,
 561                         AudioSystem.NOT_SPECIFIED, bits, channels, channels
 562                                 * bits / 8, AudioSystem.NOT_SPECIFIED, false));
 563                 formats.add(new AudioFormat(Encoding.PCM_SIGNED,
 564                         AudioSystem.NOT_SPECIFIED, bits, channels, channels
 565                                 * bits / 8, AudioSystem.NOT_SPECIFIED, true));
 566             }
 567             if (targetEncoding.equals(Encoding.PCM_UNSIGNED)) {
 568                 formats.add(new AudioFormat(Encoding.PCM_UNSIGNED,
 569                         AudioSystem.NOT_SPECIFIED, bits, channels, channels
 570                                 * bits / 8, AudioSystem.NOT_SPECIFIED, true));
 571                 formats.add(new AudioFormat(Encoding.PCM_UNSIGNED,
 572                         AudioSystem.NOT_SPECIFIED, bits, channels, channels
 573                                 * bits / 8, AudioSystem.NOT_SPECIFIED, false));
 574             }
 575         }
 576 
 577         if (targetEncoding.equals(Encoding.PCM_FLOAT)) {
 578             formats.add(new AudioFormat(Encoding.PCM_FLOAT,
 579                     AudioSystem.NOT_SPECIFIED, 32, channels, channels * 4,
 580                     AudioSystem.NOT_SPECIFIED, false));
 581             formats.add(new AudioFormat(Encoding.PCM_FLOAT,
 582                     AudioSystem.NOT_SPECIFIED, 32, channels, channels * 4,
 583                     AudioSystem.NOT_SPECIFIED, true));
 584             formats.add(new AudioFormat(Encoding.PCM_FLOAT,
 585                     AudioSystem.NOT_SPECIFIED, 64, channels, channels * 8,
 586                     AudioSystem.NOT_SPECIFIED, false));
 587             formats.add(new AudioFormat(Encoding.PCM_FLOAT,
 588                     AudioSystem.NOT_SPECIFIED, 64, channels, channels * 8,
 589                     AudioSystem.NOT_SPECIFIED, true));
 590         }
 591 
 592         return formats.toArray(new AudioFormat[formats.size()]);
 593     }
 594 
 595     public boolean isConversionSupported(AudioFormat targetFormat,
 596             AudioFormat sourceFormat) {
 597         Objects.requireNonNull(targetFormat);
 598         if (AudioFloatConverter.getConverter(sourceFormat) == null)
 599             return false;
 600         if (AudioFloatConverter.getConverter(targetFormat) == null)
 601             return false;
 602         if (sourceFormat.getChannels() <= 0)
 603             return false;
 604         if (targetFormat.getChannels() <= 0)
 605             return false;
 606         return true;
 607     }
 608 
 609     public boolean isConversionSupported(Encoding targetEncoding,
 610             AudioFormat sourceFormat) {
 611         Objects.requireNonNull(targetEncoding);
 612         if (AudioFloatConverter.getConverter(sourceFormat) == null)
 613             return false;
 614         for (int i = 0; i < formats.length; i++) {
 615             if (targetEncoding.equals(formats[i]))
 616                 return true;
 617         }
 618         return false;
 619     }
 620 
 621 }