Author: bpoussin Date: 2012-09-05 01:42:36 +0200 (Wed, 05 Sep 2012) New Revision: 520 Url: http://forge.codelutin.com/repositories/revision/sammoa/520 Log: lecteur et enregistreur fonctionnel Added: trunk/sammoa-application/src/main/java/fr/ulr/sammoa/application/device/audio/SammoaAudioReader.java Modified: trunk/sammoa-application/src/main/java/fr/ulr/sammoa/application/device/audio/SammoaAudioRecorder.java Copied: trunk/sammoa-application/src/main/java/fr/ulr/sammoa/application/device/audio/SammoaAudioReader.java (from rev 479, trunk/sammoa-application/src/main/java/fr/ulr/sammoa/application/device/audio/AudioReaderMock.java) =================================================================== --- trunk/sammoa-application/src/main/java/fr/ulr/sammoa/application/device/audio/SammoaAudioReader.java (rev 0) +++ trunk/sammoa-application/src/main/java/fr/ulr/sammoa/application/device/audio/SammoaAudioReader.java 2012-09-04 23:42:36 UTC (rev 520) @@ -0,0 +1,316 @@ +package fr.ulr.sammoa.application.device.audio; +/* + * #%L + * SAMMOA :: Application + * $Id$ + * $HeadURL$ + * %% + * Copyright (C) 2012 UMS 3462, Code Lutin + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import com.google.common.base.Preconditions; +import fr.ulr.sammoa.application.device.DeviceState; +import fr.ulr.sammoa.application.device.DeviceStateEvent; +import fr.ulr.sammoa.application.device.DeviceStateListener; +import fr.ulr.sammoa.application.device.DeviceTechnicalException; +import java.io.File; +import java.util.HashSet; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import javax.sound.sampled.AudioFileFormat; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Clip; +import javax.sound.sampled.DataLine; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Classe permettant la lecture d'un fichier audio, et de ce deplace a une + * position souhaite dans ce fichier. + * + * Created: 04 septembre 2012 + * + * @author Benjamin POUSSIN <poussin@codelutin.com> + * @version $Revision$ + * + * Last update: $Date$ + * by : $Author$ + */ +public class SammoaAudioReader implements AudioReader { + + private static final Logger logger = LoggerFactory.getLogger(SammoaAudioReader.class); + + protected Clip clip; // Contents of a sampled audio file + protected AudioPositionTraker audioPositionTraker; + + // Length and position of the sound are measured in milliseconds for + // sampled sounds + protected long audioLength; // Length of the sound. + protected long audioPosition = 0; // Current position within the sound + + protected DeviceState state = DeviceState.UNAVAILABLE; + + protected Set<DeviceStateListener> listeners = new HashSet<DeviceStateListener>(); + protected Set<AudioPositionListener> audioPositionListener = new HashSet<AudioPositionListener>(); + + /** + * Permet de se mettre listener sur l'avancement de la lecture du fichier + */ + static public interface AudioPositionListener { + /** + * + * @param source La source de l'evenement + * @param audioPosition la nouvelle position en milliseconds + */ + public void positionChange(Object source, long audioPosition); + } + + /** + * Classe servant a surveiller l'avancement de la lecture + * et a prevenir les listeners + */ + static protected class AudioPositionTraker { + static final protected long STEP = 100; // toutes les 100 ms + + protected Timer timer; + protected TimerTask task; + protected SammoaAudioReader reader; + + public AudioPositionTraker(SammoaAudioReader reader) { + timer = new Timer("SammoaAudioReader"); + this.reader = reader; + } + + /** + * Demarre la surveillance + */ + public void start() { + task = new TimerTask() { + @Override + public void run() { + if (reader.clip.isActive()) { + // on appele pas setPosition car, sinon on modifie la position dans le clips :( + // alors que c'est lui qu'on surveille et qui fait le changement + long old = reader.audioPosition; + long position = reader.getClip().getMicrosecondPosition()/1000L; + if (old != position) { + reader.audioPosition = position; + reader.firePositionChange(position); + } + } else { + reader.stop(); + reader.setPosition(0); + } + } + }; + timer.schedule(task , 0, STEP); + } + + /** + * Arrete la surveillance + */ + public void stop() { + if (task != null) { + task.cancel(); + task = null; + } + } + + } + + public SammoaAudioReader() { + audioPositionTraker = new AudioPositionTraker(this); + } + + public Clip getClip() { + return clip; + } + + @Override + public AudioFileFormat.Type getOutputType() { + return AudioFileFormat.Type.WAVE; + } + + @Override + public void load(File file) { + Preconditions.checkNotNull(file); + Preconditions.checkArgument(file.exists()); + unload(); + try { + // Getting a Clip object for a file of sampled audio data is kind + // of cumbersome. The following lines do what we need. + AudioInputStream ain = AudioSystem.getAudioInputStream(file); + + // Get information about the format of the stream + AudioFormat format = ain.getFormat( ); + DataLine.Info info = new DataLine.Info(Clip.class, format); + + if (!AudioSystem.isLineSupported(info)) { + // This is the PCM format we want to transcode to. + // The parameters here are audio format details that you + // shouldn't need to understand for casual use. + AudioFormat pcm = + new AudioFormat(format.getSampleRate( ), 16, + format.getChannels( ), true, false); + + // Get a wrapper stream around the input stream that does the + // transcoding for us. + ain = AudioSystem.getAudioInputStream(pcm, ain); + + // Update the format and info variables for the transcoded data + format = ain.getFormat( ); + info = new DataLine.Info(Clip.class, format); + } + + try { + clip = (Clip) AudioSystem.getLine(info); + clip.open(ain); + } + finally { // We're done with the input stream. + ain.close( ); + } + // Get the clip length in microseconds and convert to milliseconds + audioLength = (int)(clip.getMicrosecondLength( )/1000); + + setState(DeviceState.READY); + logger.debug(String.format("Sound file '%s' loaded", file)); + } catch (Exception eee) { + logger.error(String.format("Can't load audio file '%s'",file), eee); + setState(DeviceState.ERROR); + } + + } + + @Override + public void unload() { + if (clip != null) { + stop(); + clip.close(); + clip = null; + } + setState(DeviceState.UNAVAILABLE); + } + + @Override + public long getLength() { + return audioLength; + } + + @Override + public void setPosition(long position) { + if (clip != null) { + long old = audioPosition; + this.audioPosition = position; + if (old != position ) { + clip.setMicrosecondPosition(position * 1000L); + firePositionChange(position); + } + } + } + + protected void firePositionChange(long position) { + for (AudioPositionListener l : audioPositionListener) { + l.positionChange(this, position); + } + } + + @Override + public long getPosition() { + return audioPosition; + } + + @Override + public void open() throws DeviceTechnicalException { + // no state set here, only open device and throw error + } + + @Override + public void start() { + if (clip != null) { + clip.start(); + audioPositionTraker.start(); // all time start tracker after clip + logger.debug("Sound playing"); + if (state == DeviceState.READY) { + setState(DeviceState.RUNNING); + } + } + } + + @Override + public void stop() { + logger.debug("Stop playing"); + audioPositionTraker.stop(); + if (clip != null) { + clip.stop( ); + } + + if (state == DeviceState.RUNNING) { + setState(DeviceState.READY); + } + } + + @Override + public void close() throws DeviceTechnicalException { + unload(); + } + + @Override + public DeviceState getState() { + return state; + } + + protected void setState(DeviceState state) { + DeviceState oldValue = getState(); + this.state = state; + + DeviceStateEvent event = new DeviceStateEvent(this, oldValue, state); + for (DeviceStateListener listener : listeners) { + listener.stateChanged(event); + } + } + + @Override + public void addDeviceStateListener(DeviceStateListener listener) { + listeners.add(listener); + } + + @Override + public void removeDeviceStateListener(DeviceStateListener listener) { + listeners.remove(listener); + } + + @Override + public Set<DeviceStateListener> getDeviceStateListeners() { + return listeners; + } + + public void addAudioPositionListener(AudioPositionListener listener) { + audioPositionListener.add(listener); + } + + public void removeAudioPositionListener(AudioPositionListener listener) { + audioPositionListener.remove(listener); + } + + public Set<AudioPositionListener> getAudioPositionListeners() { + return audioPositionListener; + } +} Modified: trunk/sammoa-application/src/main/java/fr/ulr/sammoa/application/device/audio/SammoaAudioRecorder.java =================================================================== --- trunk/sammoa-application/src/main/java/fr/ulr/sammoa/application/device/audio/SammoaAudioRecorder.java 2012-09-04 23:41:55 UTC (rev 519) +++ trunk/sammoa-application/src/main/java/fr/ulr/sammoa/application/device/audio/SammoaAudioRecorder.java 2012-09-04 23:42:36 UTC (rev 520) @@ -30,9 +30,11 @@ import fr.ulr.sammoa.application.device.DeviceStateEvent; import fr.ulr.sammoa.application.device.DeviceStateListener; import fr.ulr.sammoa.application.device.DeviceTechnicalException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import java.io.File; +import java.io.IOException; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; import javax.sound.sampled.AudioFileFormat; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; @@ -40,16 +42,27 @@ import javax.sound.sampled.DataLine; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.TargetDataLine; -import java.io.File; -import java.io.IOException; -import java.util.Set; -import java.util.Timer; -import java.util.TimerTask; +import org.apache.commons.io.FilenameUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * Created: 16/05/12 + * Classe permettant l'enregistrement d'un fichier audio. Par defaut la + * methode record n'ecrase jamais un fichier existant mais incremente le nom + * du fichier. + * + * Pour l'instant aucune compression n'est faite. Si on souhaite une compression + * il faut trouver une librairie de compression et modifier la ligne 284 + * AudioFormat.Encoding targetEncoding = AudioFormat.Encoding.PCM_SIGNED; + * pour utilise le format de compression choisi. + * + * Created: 27 aout 2012 * - * @author fdesbois <desbois@codelutin.com> + * @author Benjamin POUSSIN <poussin@codelutin.com> + * @version $Revision$ + * + * Last update: $Date$ + * by : $Author$ */ public class SammoaAudioRecorder implements AudioRecorder { @@ -67,16 +80,35 @@ protected AudioRecorderThread currentRecorder; + protected int delay; + public SammoaAudioRecorder(AudioConfig config) { this.config = config; + init(config.getSampleRate(), config.getSampleSizeInBits(), + config.getRecordDelayInSeconds()); + } + /** + * Permet de forcer le sampleRate et sampleSizeInBits + * @param sampleRate 8000, 11025, 16000, 22050, 44100 + * @param sampleSizeInBits 8 ou 16 + * @param delay le delai avant de reellement coupe un enregistrement (tous + * les PC ne le supporte pas) + */ + public SammoaAudioRecorder(float sampleRate, int sampleSizeInBits, int delay) { + init(sampleRate, sampleSizeInBits, delay); + } + + protected void init(float sampleRate, int sampleSizeInBits, int delay) { + this.delay = delay; + int channels = 1; // 1,2 boolean signed = true; // true,false boolean bigEndian = false; // true,false audioFormat = new AudioFormat( - config.getSampleRate(), - config.getSampleSizeInBits(), + sampleRate, + sampleSizeInBits, channels, signed, bigEndian); @@ -132,20 +164,11 @@ // nothing to do } + @Override public void record(File outputFile) { - Preconditions.checkNotNull(outputFile); - try { - // stop last started if necessary - stop(); - // start new recorder thread - currentRecorder = new AudioRecorderThread( - audioFormat, outputType, outputFile, config.getRecordDelayInSeconds(), this); - currentRecorder.start(); - - setState(DeviceState.RUNNING, null); - + recordStream(outputFile, false); } catch (LineUnavailableException ex) { fireError("Error recording " + outputFile.getName(), ex); } catch (IllegalArgumentException ex) { @@ -157,6 +180,49 @@ } } + /** + * Cette methode laisse passer les exceptions sans les intercepter + * @param outputFile + * @param overwrite + * @throws LineUnavailableException + */ + public void record(File outputFile, boolean overwrite) throws LineUnavailableException { + recordStream(outputFile, overwrite); + } + + /*** + * + * @param outputFile + * @param overwrite si faux alors on recherche un nom de fichier inexistant + * pour l'utiliser à la place du fichier d'entre. Par exemple si toto.wav + * est déjà existant, alors toto-1.wav sera utilise ou toto-2.wav, ... + */ + public void recordStream(File outputFile, boolean overwrite) throws LineUnavailableException { + Preconditions.checkNotNull(outputFile); + + if (!overwrite && outputFile.exists()) { + // recherche du premier fichier inexistant + String path = outputFile.getName(); + String filename = FilenameUtils.getBaseName(path); + String ext = FilenameUtils.getExtension(path); + + int i = 1; + while (outputFile.exists() && i < Integer.MAX_VALUE) { + outputFile = new File(path + "-"+(i++) + ext); + } + } + + // stop last started if necessary + stop(); + // start new recorder thread + currentRecorder = new AudioRecorderThread( + audioFormat, outputType, outputFile, delay, this); + currentRecorder.start(); + + setState(DeviceState.RUNNING, null); + + } + @Override public void stopRecord() { stop(); @@ -211,7 +277,8 @@ targetDataLine = (TargetDataLine) AudioSystem.getLine(dataLineInfo); - AudioFormat.Encoding targetEncoding = AudioFormat.Encoding.ULAW; + AudioFormat.Encoding targetEncoding = AudioFormat.Encoding.PCM_SIGNED; //ULAW; + targetDataLine.open(audioFormat); targetDataLine.start(); stream = new AudioInputStream(targetDataLine);
participants (1)
-
bpoussin@users.forge.codelutin.com