diff --git a/maven/core-unittests/src/test/java/com/codename1/media/AudioBufferSampleTest.java b/maven/core-unittests/src/test/java/com/codename1/media/AudioBufferSampleTest.java new file mode 100644 index 0000000000..b8d162f0f3 --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/media/AudioBufferSampleTest.java @@ -0,0 +1,79 @@ +package com.codename1.media; + +import com.codename1.capture.Capture; +import com.codename1.io.File; +import com.codename1.io.FileSystemStorage; +import com.codename1.io.Util; +import com.codename1.junit.FormTest; +import com.codename1.junit.UITestBase; + +import static org.junit.jupiter.api.Assertions.*; + +class AudioBufferSampleTest extends UITestBase { + + @FormTest + void captureAudioRedirectsFramesToBufferAndPersistsWav() throws Exception { + FileSystemStorage fs = FileSystemStorage.getInstance(); + implementation.clearFileSystem(); + implementation.clearAudioCaptureFrames(); + String recordingsDir = fs.getAppHomePath() + "recordings/"; + fs.mkdir(recordingsDir); + + String bufferPath = "tmpBuffer.pcm"; + int wavSampleRate = 16000; + AudioBuffer audioBuffer = MediaManager.getAudioBuffer(bufferPath, true, 8); + final float[] captured = new float[audioBuffer.getMaxSize()]; + final int[] callbackCount = new int[1]; + final int[] lastSampleRate = new int[1]; + final int[] lastSize = new int[1]; + + WAVWriter writer = new WAVWriter(new File("tmpBuffer.wav"), wavSampleRate, 1, 16); + try { + audioBuffer.addCallback(new AudioBuffer.AudioBufferCallback() { + public void frameReceived(AudioBuffer buffer) { + if (buffer.getSampleRate() > wavSampleRate) { + buffer.downSample(wavSampleRate); + } + buffer.copyTo(captured); + lastSampleRate[0] = buffer.getSampleRate(); + lastSize[0] = buffer.getSize(); + callbackCount[0]++; + try { + writer.write(captured, 0, buffer.getSize()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + + MediaRecorderBuilder options = new MediaRecorderBuilder() + .audioChannels(1) + .redirectToAudioBuffer(true) + .path(bufferPath); + + implementation.addAudioCaptureFrame(32000, 1, new float[]{1f, 0.5f, -0.5f, -1f}); + + Capture.captureAudio(options); + + writer.close(); + + String copyPath = recordingsDir + "sample.wav"; + Util.copy(fs.openInputStream(new File("tmpBuffer.wav").getAbsolutePath()), fs.openOutputStream(copyPath)); + + assertEquals(1, callbackCount[0]); + assertEquals(wavSampleRate, lastSampleRate[0]); + assertTrue(lastSize[0] > 0); + assertSame(options, implementation.getLastMediaRecorderBuilder()); + assertTrue(fs.exists(copyPath)); + assertTrue(fs.getLength(copyPath) > 44); + } finally { + MediaManager.releaseAudioBuffer(bufferPath); + try { + writer.close(); + } catch (Exception ignored) { + } + implementation.clearFileSystem(); + } + } +} + diff --git a/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java b/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java index 8418c66364..48dec5cf54 100644 --- a/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java +++ b/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java @@ -13,7 +13,9 @@ import com.codename1.payment.Purchase; import com.codename1.l10n.L10NManager; import com.codename1.location.LocationManager; +import com.codename1.media.AudioBuffer; import com.codename1.media.Media; +import com.codename1.media.MediaManager; import com.codename1.media.MediaRecorderBuilder; import com.codename1.messaging.Message; import com.codename1.notifications.LocalNotification; @@ -36,6 +38,7 @@ import com.codename1.ui.geom.Rectangle; import com.codename1.ui.geom.Shape; import com.codename1.util.AsyncResource; +import java.io.Closeable; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -174,6 +177,7 @@ public class TestCodenameOneImplementation extends CodenameOneImplementation { private String nextCaptureAudioPath = "file://test-audio.wav"; private MediaRecorderBuilder lastMediaRecorderBuilder; private VideoCaptureConstraints lastVideoConstraints; + private final List audioCaptureFrames = new ArrayList(); public TestCodenameOneImplementation() { @@ -1958,7 +1962,14 @@ public boolean exists(String file) { public void rename(String file, String newName) { TestFile f = fileSystem.remove(file); if (f != null) { - fileSystem.put(newName, f); + String target = newName; + if (newName != null && !newName.contains("://") && !newName.startsWith("/")) { + int lastSlash = file.lastIndexOf('/'); + if (lastSlash >= 0) { + target = file.substring(0, lastSlash + 1) + newName; + } + } + fileSystem.put(target, f); } } @@ -2130,6 +2141,17 @@ public void closingOutput(OutputStream stream) { @Override public void cleanup(Object obj) { cleanupCalls.add(obj); + if (obj instanceof Closeable) { + try { + ((Closeable) obj).close(); + } catch (IOException ignored) { + } + } else if (obj instanceof AutoCloseable) { + try { + ((AutoCloseable) obj).close(); + } catch (Exception ignored) { + } + } } @Override @@ -3408,6 +3430,15 @@ public void captureAudio(MediaRecorderBuilder recordingOptions, ActionListener r recordingOptions = new MediaRecorderBuilder(); } lastMediaRecorderBuilder = recordingOptions; + if (recordingOptions.isRedirectToAudioBuffer()) { + AudioBuffer buffer = MediaManager.getAudioBuffer(recordingOptions.getPath()); + if (buffer != null) { + for (AudioCaptureFrame frame : audioCaptureFrames) { + buffer.copyFrom(frame.getSampleRate(), frame.getNumChannels(), frame.getSamples()); + } + } + audioCaptureFrames.clear(); + } response.actionPerformed(new ActionEvent(nextCaptureAudioPath)); } @@ -3438,10 +3469,42 @@ public MediaRecorderBuilder getLastMediaRecorderBuilder() { return lastMediaRecorderBuilder; } + public void addAudioCaptureFrame(int sampleRate, int numChannels, float[] samples) { + audioCaptureFrames.add(new AudioCaptureFrame(sampleRate, numChannels, samples)); + } + + public void clearAudioCaptureFrames() { + audioCaptureFrames.clear(); + } + public VideoCaptureConstraints getLastVideoConstraints() { return lastVideoConstraints; } + private static final class AudioCaptureFrame { + private final int sampleRate; + private final int numChannels; + private final float[] samples; + + private AudioCaptureFrame(int sampleRate, int numChannels, float[] samples) { + this.sampleRate = sampleRate; + this.numChannels = numChannels; + this.samples = Arrays.copyOf(samples, samples.length); + } + + private int getSampleRate() { + return sampleRate; + } + + private int getNumChannels() { + return numChannels; + } + + private float[] getSamples() { + return Arrays.copyOf(samples, samples.length); + } + } + public static final class TestFile { final boolean directory; final byte[] content;