1   package org.daisy.pipeline.audio.impl;
2   
3   import java.io.File;
4   import java.io.FileInputStream;
5   import java.io.FileOutputStream;
6   import java.time.Duration;
7   import java.util.Map;
8   import java.util.Optional;
9   
10  import javax.sound.sampled.AudioFileFormat;
11  import javax.sound.sampled.AudioFormat;
12  import javax.sound.sampled.AudioFormat.Encoding;
13  import javax.sound.sampled.AudioInputStream;
14  import javax.sound.sampled.AudioSystem;
15  
16  import org.daisy.pipeline.audio.AudioClip;
17  import org.daisy.pipeline.audio.AudioEncoder;
18  import org.daisy.pipeline.audio.AudioEncoderService;
19  import org.daisy.pipeline.audio.AudioUtils;
20  
21  import org.osgi.service.component.annotations.Component;
22  
23  @Component(
24  	name = "system-audio-encoder",
25  	immediate = true,
26  	service = { AudioEncoderService.class }
27  )
28  public class SystemAudioEncoder implements AudioEncoderService {
29  
30  	@Override
31  	public boolean supportsFileType(AudioFileFormat.Type fileType) {
32  		return AudioSystem.isFileTypeSupported(fileType);
33  	}
34  
35  	@Override
36  	public Optional<AudioEncoder> newEncoder(Map<String,String> params) {
37  		if (instance == null)
38  			instance = new AudioEncoder() {
39  					@Override
40  					public AudioClip encode(AudioInputStream pcm, AudioFileFormat.Type outputFileType, File outputFile) throws Throwable {
41  						AudioFormat format = pcm.getFormat();
42  						if (outputFileType == AudioFileFormat.Type.WAVE) {
43  							if (format.getEncoding() == Encoding.PCM_FLOAT) {
44  								if (format.isBigEndian()) {
45  
46  									// AudioSystem stores this as big-endian which is not according
47  									// to the WAV standard. To work around this issue first convert
48  									// the samples to little-endian.
49  									return encode(
50  										AudioUtils.convertAudioStream(
51  											new AudioFormat(format.getEncoding(),
52  											                format.getSampleRate(),
53  											                format.getSampleSizeInBits(),
54  											                format.getChannels(),
55  											                format.getFrameSize(),
56  											                format.getFrameRate(),
57  											                false), // little endian
58  											pcm),
59  										outputFileType,
60  										outputFile);
61  								} else {
62  
63  									// AudioSystem creates the wrong file header. We fix the wrong bytes here.
64  									// see http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
65  									File tmpFile = File.createTempFile("wrong-header-", ".wav.tmp");
66  									tmpFile.deleteOnExit();
67  									AudioSystem.write(pcm, outputFileType, tmpFile);
68  									try (FileInputStream wrongFile = new FileInputStream(tmpFile);
69  									     FileOutputStream correctedFile = new FileOutputStream(outputFile)) {
70  										byte[] header = new byte[44];
71  										wrongFile.read(header);
72  										// format = WAVE_FORMAT_IEEE_FLOAT
73  										header[20] = (byte)3;
74  										correctedFile.write(header);
75  										byte[] buf = new byte[1024];
76  										while (true) {
77  											int bytesRead = wrongFile.read(buf);
78  											if (bytesRead == -1)
79  												break;
80  											correctedFile.write(buf, 0, bytesRead);
81  										}
82  									}
83  									tmpFile.delete();
84  								}
85  							} else if (format.getEncoding() == Encoding.PCM_UNSIGNED
86  							           && format.getSampleSizeInBits() > 8
87  							           && !format.isBigEndian()) {
88  
89  								// AudioSystem writes this as signed little-endian (according to the WAV standard)
90  								// but does not convert the data. This is probably because com.sun.media.sound.PCMtoPCMCodec
91  								// does not correctly convert unsigned little-endian to signed little-endian.
92  								// To work around this issue first convert the samples to big-endian.
93  								return encode(
94  									AudioUtils.convertAudioStream(
95  										new AudioFormat(format.getSampleRate(),
96  										                format.getSampleSizeInBits(),
97  										                format.getChannels(),
98  										                false, // unsigned
99  										                true), // big endian
100 										pcm),
101 									outputFileType,
102 									outputFile);
103 							} else
104 								AudioSystem.write(pcm, outputFileType, outputFile);
105 						} else
106 							AudioSystem.write(pcm, outputFileType, outputFile);
107 						return new AudioClip(outputFile, Duration.ZERO, AudioUtils.getDuration(pcm));
108 					}
109 				};
110 		return Optional.of(instance);
111 	}
112 
113 	private static AudioEncoder instance = null;
114 
115 }