]> git.basschouten.com Git - openhab-addons.git/commitdiff
[pulseaudio] Fix playing time with pulseaudio sink (#11170) (#11171)
authordalgwen <dalgwen@users.noreply.github.com>
Wed, 8 Sep 2021 19:08:05 +0000 (21:08 +0200)
committerGitHub <noreply@github.com>
Wed, 8 Sep 2021 19:08:05 +0000 (21:08 +0200)
Fixes #11170 by introducing an intelligent thread.sleep (getting the duration of the sound, if possible, then wait the appropriate time for letting the sound play). By the way, the method to get the sound duration is not as easy as I thought.

Also fix a minor issue with the last volume not propertly saved.

And fix some minor warnings by using final local variable.

Signed-off-by: Gwendal ROULLEAU <gwendal.roulleau@gmail.com>
bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/PulseAudioAudioSink.java
bundles/org.openhab.binding.pulseaudio/src/main/java/org/openhab/binding/pulseaudio/internal/handler/PulseaudioHandler.java

index 6f3756eef9990046fa28fb764056aa3501d2a200..ba3ba0f1c67ef59e254700ad4f693d91fce57398 100644 (file)
@@ -15,15 +15,20 @@ package org.openhab.binding.pulseaudio.internal;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.Socket;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.HashSet;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 import javazoom.spi.mpeg.sampled.convert.MpegFormatConversionProvider;
 import javazoom.spi.mpeg.sampled.file.MpegAudioFileReader;
 
+import javax.sound.sampled.AudioFileFormat;
 import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioSystem;
 import javax.sound.sampled.UnsupportedAudioFileException;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -38,6 +43,7 @@ import org.openhab.core.audio.UnsupportedAudioStreamException;
 import org.openhab.core.library.types.PercentType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.tritonus.share.sampled.file.TAudioFileFormat;
 
 /**
  * The audio sink for openhab, implemented by a connection to a pulseaudio sink
@@ -87,9 +93,29 @@ public class PulseAudioAudioSink implements AudioSink {
      * @param input
      * @return
      */
-    private @Nullable InputStream getPCMStreamFromMp3Stream(InputStream input) {
+    private @Nullable AudioStreamAndDuration getPCMStreamFromMp3Stream(InputStream input) {
         try {
+
             MpegAudioFileReader mpegAudioFileReader = new MpegAudioFileReader();
+
+            int duration = -1;
+            if (input instanceof FixedLengthAudioStream) {
+                final Long audioFileLength = ((FixedLengthAudioStream) input).length();
+                AudioFileFormat audioFileFormat = mpegAudioFileReader.getAudioFileFormat(input);
+                if (audioFileFormat instanceof TAudioFileFormat) {
+                    Map<String, Object> taudioFileFormatProperties = ((TAudioFileFormat) audioFileFormat).properties();
+                    if (taudioFileFormatProperties.containsKey("mp3.framesize.bytes")
+                            && taudioFileFormatProperties.containsKey("mp3.framerate.fps")) {
+                        Integer frameSize = (Integer) taudioFileFormatProperties.get("mp3.framesize.bytes");
+                        Float frameRate = (Float) taudioFileFormatProperties.get("mp3.framerate.fps");
+                        if (frameSize != null && frameRate != null) {
+                            duration = Math.round((audioFileLength / (frameSize * frameRate)) * 1000);
+                        }
+                    }
+                }
+                input.reset();
+            }
+
             AudioInputStream sourceAIS = mpegAudioFileReader.getAudioInputStream(input);
             javax.sound.sampled.AudioFormat sourceFormat = sourceAIS.getFormat();
 
@@ -98,7 +124,8 @@ public class PulseAudioAudioSink implements AudioSink {
                     javax.sound.sampled.AudioFormat.Encoding.PCM_SIGNED, sourceFormat.getSampleRate(), 16,
                     sourceFormat.getChannels(), sourceFormat.getChannels() * 2, sourceFormat.getSampleRate(), false);
 
-            return mpegconverter.getAudioInputStream(convertFormat, sourceAIS);
+            AudioInputStream audioInputStreamConverted = mpegconverter.getAudioInputStream(convertFormat, sourceAIS);
+            return new AudioStreamAndDuration(audioInputStreamConverted, duration);
 
         } catch (IOException | UnsupportedAudioFileException e) {
             logger.warn("Cannot convert this mp3 stream to pcm stream: {}", e.getMessage());
@@ -126,10 +153,11 @@ public class PulseAudioAudioSink implements AudioSink {
      * Disconnect the socket to pulseaudio simple protocol
      */
     public void disconnect() {
-        if (clientSocket != null && isIdle) {
+        final Socket clientSocketLocal = clientSocket;
+        if (clientSocketLocal != null && isIdle) {
             logger.debug("Disconnecting");
             try {
-                clientSocket.close();
+                clientSocketLocal.close();
             } catch (IOException e) {
             }
         } else {
@@ -137,6 +165,23 @@ public class PulseAudioAudioSink implements AudioSink {
         }
     }
 
+    private AudioStreamAndDuration getWavAudioAndDuration(AudioStream audioStream) {
+        int duration = -1;
+        if (audioStream instanceof FixedLengthAudioStream) {
+            final Long audioFileLength = ((FixedLengthAudioStream) audioStream).length();
+            try {
+                AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(audioStream);
+                int frameSize = audioInputStream.getFormat().getFrameSize();
+                float frameRate = audioInputStream.getFormat().getFrameRate();
+                float durationInSeconds = (audioFileLength / (frameSize * frameRate));
+                duration = Math.round(durationInSeconds * 1000);
+            } catch (UnsupportedAudioFileException | IOException e) {
+                logger.warn("Error when getting duration information from AudioFile");
+            }
+        }
+        return new AudioStreamAndDuration(audioStream, duration);
+    }
+
     @Override
     public void process(@Nullable AudioStream audioStream)
             throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
@@ -145,13 +190,13 @@ public class PulseAudioAudioSink implements AudioSink {
             return;
         }
 
-        InputStream audioInputStream = null;
+        AudioStreamAndDuration audioInputStreamAndDuration = null;
         try {
 
             if (AudioFormat.MP3.isCompatible(audioStream.getFormat())) {
-                audioInputStream = getPCMStreamFromMp3Stream(audioStream);
+                audioInputStreamAndDuration = getPCMStreamFromMp3Stream(audioStream);
             } else if (AudioFormat.WAV.isCompatible(audioStream.getFormat())) {
-                audioInputStream = audioStream;
+                audioInputStreamAndDuration = getWavAudioAndDuration(audioStream);
             } else {
                 throw new UnsupportedAudioFormatException("pulseaudio audio sink can only play pcm or mp3 stream",
                         audioStream.getFormat());
@@ -160,10 +205,23 @@ public class PulseAudioAudioSink implements AudioSink {
             for (int countAttempt = 1; countAttempt <= 2; countAttempt++) { // two attempts allowed
                 try {
                     connectIfNeeded();
-                    if (audioInputStream != null && clientSocket != null) {
+                    final Socket clientSocketLocal = clientSocket;
+                    if (audioInputStreamAndDuration != null && clientSocketLocal != null) {
                         // send raw audio to the socket and to pulse audio
                         isIdle = false;
-                        audioInputStream.transferTo(clientSocket.getOutputStream());
+                        Instant start = Instant.now();
+                        audioInputStreamAndDuration.inputStream.transferTo(clientSocketLocal.getOutputStream());
+                        if (audioInputStreamAndDuration.duration != -1) { // ensure, if the sound has a duration
+                            // that we let at least this time for the system to play
+                            Instant end = Instant.now();
+                            long millisSecondTimedToSendAudioData = Duration.between(start, end).toMillis();
+                            if (millisSecondTimedToSendAudioData < audioInputStreamAndDuration.duration) {
+                                long timeToSleep = audioInputStreamAndDuration.duration
+                                        - millisSecondTimedToSendAudioData;
+                                logger.debug("Sleep time to let the system play sound : {}", timeToSleep);
+                                Thread.sleep(timeToSleep);
+                            }
+                        }
                         break;
                     }
                 } catch (IOException e) {
@@ -184,8 +242,8 @@ public class PulseAudioAudioSink implements AudioSink {
             }
         } finally {
             try {
-                if (audioInputStream != null) {
-                    audioInputStream.close();
+                if (audioInputStreamAndDuration != null) {
+                    audioInputStreamAndDuration.inputStream.close();
                 }
                 audioStream.close();
                 scheduleDisconnect();
@@ -219,4 +277,15 @@ public class PulseAudioAudioSink implements AudioSink {
     public void setVolume(PercentType volume) {
         pulseaudioHandler.setVolume(volume.intValue());
     }
+
+    private static class AudioStreamAndDuration {
+        private InputStream inputStream;
+        private int duration;
+
+        public AudioStreamAndDuration(InputStream inputStream, int duration) {
+            super();
+            this.inputStream = inputStream;
+            this.duration = duration + 200; // introduce some delay
+        }
+    }
 }
index 41011f5d254970044e89fe66ccf29d1043f54d63..2aae655d039dad4671008987ff56d85efb71ade2 100644 (file)
@@ -231,24 +231,28 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL
                     // refresh to get the current volume level
                     bridge.getClient().update();
                     device = bridge.getDevice(name);
-                    savedVolume = device.getVolume();
+                    int oldVolume = device.getVolume();
+                    int newVolume = oldVolume;
                     if (command.equals(IncreaseDecreaseType.INCREASE)) {
-                        savedVolume = Math.min(100, savedVolume + 5);
+                        newVolume = Math.min(100, oldVolume + 5);
                     }
                     if (command.equals(IncreaseDecreaseType.DECREASE)) {
-                        savedVolume = Math.max(0, savedVolume - 5);
+                        newVolume = Math.max(0, oldVolume - 5);
                     }
-                    bridge.getClient().setVolumePercent(device, savedVolume);
-                    updateState = new PercentType(savedVolume);
+                    bridge.getClient().setVolumePercent(device, newVolume);
+                    updateState = new PercentType(newVolume);
+                    savedVolume = newVolume;
                 } else if (command instanceof PercentType) {
                     DecimalType volume = (DecimalType) command;
                     bridge.getClient().setVolumePercent(device, volume.intValue());
                     updateState = (PercentType) command;
+                    savedVolume = volume.intValue();
                 } else if (command instanceof DecimalType) {
                     // set volume
                     DecimalType volume = (DecimalType) command;
                     bridge.getClient().setVolume(device, volume.intValue());
                     updateState = (DecimalType) command;
+                    savedVolume = volume.intValue();
                 }
             } else if (channelUID.getId().equals(MUTE_CHANNEL)) {
                 if (command instanceof OnOffType) {
@@ -318,6 +322,7 @@ public class PulseaudioHandler extends BaseThingHandler implements DeviceStatusL
         AbstractAudioDeviceConfig device = bridge.getDevice(name);
         bridge.getClient().setVolumePercent(device, volume);
         updateState(VOLUME_CHANNEL, new PercentType(volume));
+        savedVolume = volume;
     }
 
     @Override