Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8195124

Delay When Changing SourceDataLine Volume on Windows

XMLWordPrintable

      FULL PRODUCT VERSION :
      java version "9.0.1"
      Java(TM) SE Runtime Environment (build 9.0.1+11)
      Java HotSpot(TM) 64-Bit Server VM (build 9.0.1+11, mixed mode)

      Also confirmed on 8_151

      ADDITIONAL OS VERSION INFORMATION :
      Microsoft Windows [Version 6.1.7601]

      A DESCRIPTION OF THE PROBLEM :
      When I change the master gain of a SourceDataLine in Windows, there is a small delay (~500ms) between when control.setValue ( newVolume ) is called and when the volume out of the speakers actually decreases.

      This issue does not arise on GNU/Linux, only Window. (I have not tested on OSX).

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Download the sample wav file (or use one of your chooosing), save it to a location, notate that location on line 21 of the following code, compile, and run. (Please make sure your speakers/headphones are at a reasonable level before running -- the music will start as soon as it is launched.)

      Here is one sample wav. It happens for all audio playback through a SourceDataLine:

      https://ia801403.us.archive.org/27/items/onclassical-quality-wav-audio-files-of-classical-music/onclassical_demo_luisi_bach_partita_a-minor_bwv-827_1_small-version.wav

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The volume-heard changes immediately
      ACTUAL -
      There is approximately a 500ms delay between when the volume-change command is sent and the volume actually changes.

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------

      import java.io.File;
      import java.io.IOException;
      import java.util.logging.Logger;

      import javax.sound.sampled.AudioFormat;
      import javax.sound.sampled.AudioInputStream;
      import javax.sound.sampled.AudioSystem;
      import javax.sound.sampled.DataLine;
      import javax.sound.sampled.FloatControl;
      import javax.sound.sampled.LineUnavailableException;
      import javax.sound.sampled.SourceDataLine;

      import javax.swing.JFrame;
      import javax.swing.JSlider;

      public class VolumeWindowsDelay {

          private static final Logger LOGGER = Logger.getLogger( VolumeWindowsDelay.class.getName() );

          private static final int BUFFER_SIZE = 32000;

          private static String targetFile = "D:\\sample.wav";
          private static File soundFile;

          private static AudioInputStream audioInput;
          private static AudioFormat audioFormat;
          private static SourceDataLine audioOutput;

          public static void main ( String[] args ) throws Exception {

              Thread playerThread = new Thread ( () -> playSound ( targetFile ) );
              playerThread.setDaemon ( true );
              playerThread.start();

              JSlider volumeSlider = new JSlider();
              volumeSlider.setMinimum( 0 );
              volumeSlider.setMaximum( 100 );
              volumeSlider.setValue( 100 );
              volumeSlider.setMajorTickSpacing ( 25 );
              volumeSlider.setMinorTickSpacing ( 25 );
              volumeSlider.setSnapToTicks ( true );

              volumeSlider.addChangeListener ( e -> {
                  double min = volumeSlider.getMinimum();
                  double max = volumeSlider.getMaximum();
                  double percent = (volumeSlider.getValue() - min) / (max - min);
                  setVolumePercent( percent );
              });

              JFrame frame = new JFrame ( "Volume Example" );
              frame.setSize ( 400, 100 );
              frame.getContentPane().add ( volumeSlider );
              frame.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
              frame.setVisible ( true );
          }

          static void playSound ( String filename ) {

              String strFilename = filename;

              try {
                  soundFile = new File( strFilename );
              } catch ( Exception e ) {
                  e.printStackTrace();
                  System.exit( 1 );
              }

              try {
                  audioInput = AudioSystem.getAudioInputStream( soundFile );
              } catch ( Exception e ) {
                  e.printStackTrace();
                  System.exit( 1 );
              }

              audioFormat = audioInput.getFormat();

              DataLine.Info info = new DataLine.Info( SourceDataLine.class, audioFormat );
              try {
                  audioOutput = (SourceDataLine) AudioSystem.getLine( info );
                  audioOutput.open( audioFormat );
              } catch ( LineUnavailableException e ) {
                  e.printStackTrace();
                  System.exit( 1 );
              } catch ( Exception e ) {
                  e.printStackTrace();
                  System.exit( 1 );
              }

              audioOutput.start();

              int nBytesRead = 0;
              byte[] abData = new byte [ BUFFER_SIZE ];
              while ( nBytesRead != -1 ) {
                  try {
                      nBytesRead = audioInput.read( abData, 0, abData.length );
                  } catch ( IOException e ) {
                      e.printStackTrace();
                  }
                  if ( nBytesRead >= 0 ) {
                      audioOutput.write( abData, 0, nBytesRead );
                  }
              }

              audioOutput.drain();
              audioOutput.close();
          }

          public static void setVolumePercent ( double percent ) throws IllegalArgumentException {
              System.out.println ( "Percent requested: " + percent );

              if ( audioOutput == null ) {
                  LOGGER.info( "Cannot set volume, audioOutput is null" );
                  return;
              }
              if ( audioOutput.isControlSupported( FloatControl.Type.MASTER_GAIN ) ) { //Type.VOLUME not supported by windows
                  FloatControl masterGain = (FloatControl)audioOutput.getControl( FloatControl.Type.MASTER_GAIN );
                  setVolume ( masterGain, percent );

              } else {
                  LOGGER.info( "Cannot set volume, volume control is not supported by system for this audio format." );
              }
          }

          private static void setVolume ( FloatControl control, double percent ) {
              double min = control.getMinimum();
              double max = control.getMaximum();
              double value = (max - min) * volumeCurveDB ( percent ) + min;
              control.setValue( (float)value );

          }

          private static double volumeCurveDB ( double input ) {
              if ( input <= 0 ) return 0;
              if ( input >= 1 ) return 1;

              double value = logOfBase( 50, 49 * input + 1 );

              if ( value < 0 ) value = 0;
              if ( value > 1 ) value = 1;

              return value;
          }

          public static double logOfBase ( int base, double num ) {
              return Math.log ( num ) / Math.log ( base );
          }
      }
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      None found

            Unassigned Unassigned
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated: