Guest User

Untitled

a guest
Jan 22nd, 2018
88
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.08 KB | None | 0 0
  1. package streaming;
  2.  
  3. import java.util.concurrent.Semaphore;
  4.  
  5. import javax.sound.sampled.AudioFormat;
  6. import javax.sound.sampled.AudioSystem;
  7. import javax.sound.sampled.DataLine;
  8. import javax.sound.sampled.Line;
  9. import javax.sound.sampled.LineUnavailableException;
  10. import javax.sound.sampled.Mixer;
  11. import javax.sound.sampled.SourceDataLine;
  12.  
  13. /**
  14. * Low-latency javax.sound.sampled implementation with callback API. Implemented as
  15. * singleton to match my JPA API.
  16. *
  17. * @author René Jeschke (rene_jeschke@yahoo.de)
  18. */
  19. public final class NeetJavaSound
  20. {
  21. /** Constructor. */
  22. private NeetJavaSound()
  23. {
  24. // prevent instantiation.
  25. }
  26.  
  27. /** Our audio thread worker. */
  28. private static AudioThread audioThread;
  29. /** The callback. */
  30. static NjsCallback callback = null;
  31.  
  32. /**
  33. * Opens the audio stream.
  34. *
  35. * @param sampleRate Sample rate.
  36. * @param channels Channels.
  37. * @param wantedLatency Goal latency in ms.
  38. * @throws NjsException if an error occurred.
  39. */
  40. public static void open(final double sampleRate, final int channels, final int wantedLatency)
  41. {
  42. if(audioThread != null)
  43. {
  44. throw new NjsException("NeetJavaAudio already open.");
  45. }
  46. final AudioFormat af = new AudioFormat((float)sampleRate, 16, channels, true, false);
  47. try
  48. {
  49. final SourceDataLine source = AudioSystem.getSourceDataLine(af, findMixer(af));
  50. source.open(af);
  51.  
  52. audioThread = new AudioThread(source, channels, Math.max((int)(sampleRate * wantedLatency * 0.001 + 0.5), 512));
  53.  
  54. final Thread t = new Thread(audioThread);
  55. t.setPriority(Thread.MAX_PRIORITY);
  56. t.setDaemon(true);
  57. t.start();
  58. }
  59. catch (LineUnavailableException e)
  60. {
  61. throw new NjsException(e);
  62. }
  63. }
  64.  
  65. /**
  66. * Tries to find a suitable mixer, skipping 'Java Sound Audio Engine'. This is a
  67. * workaround for audio on linux (which seems to default to the crappy 'Java Sound'.
  68. *
  69. * @param af The wanted AudioFormat.
  70. * @return
  71. * The Mixer.Info or <code>null</code> if no suitable mixer could be found or
  72. * the property 'javax.sound.sampled.SourceDataLine' is set.
  73. */
  74. private static Mixer.Info findMixer(final AudioFormat af)
  75. {
  76. if(System.getProperty("javax.sound.sampled.SourceDataLine") != null)
  77. return null;
  78.  
  79. final Line.Info line = new DataLine.Info(SourceDataLine.class, af);
  80. final Mixer.Info[] mixers = AudioSystem.getMixerInfo();
  81.  
  82. for(final Mixer.Info mi : mixers)
  83. {
  84. final Mixer m = AudioSystem.getMixer(mi);
  85. if(m.isLineSupported(line)
  86. && !mi.getName().toLowerCase().startsWith("java sound"))
  87. {
  88. return mi;
  89. }
  90. }
  91. return null;
  92. }
  93.  
  94. /**
  95. * Sets the audio callback.
  96. *
  97. * @param callback The callback.
  98. */
  99. public static void setCallback(NjsCallback callback)
  100. {
  101. NeetJavaSound.callback = callback;
  102. }
  103.  
  104. /**
  105. * Starts the audio stream. (Can't be restarted).
  106. */
  107. public static void start()
  108. {
  109. if(audioThread != null)
  110. audioThread.start();
  111. }
  112.  
  113. /**
  114. * Stops the audio stream.
  115. */
  116. public static void stop()
  117. {
  118. if(audioThread != null)
  119. audioThread.stop();
  120. }
  121.  
  122. /**
  123. * Closes the audio system.
  124. */
  125. public static void close()
  126. {
  127. if(audioThread != null)
  128. {
  129. stop();
  130. audioThread.line.close();
  131. audioThread = null;
  132. }
  133. }
  134.  
  135. /**
  136. * The rendering callback interface.
  137. *
  138. * @author René Jeschke (rene_jeschke@yahoo.de)
  139. */
  140. public static interface NjsCallback
  141. {
  142. /**
  143. * Called when sound hast to be rendered.
  144. *
  145. * @param output Interleaved output.
  146. * @param nframes Number of frames to render.
  147. */
  148. public void render(float[] output, int nframes);
  149. }
  150.  
  151. /**
  152. * Runtime exception based NeetJavaSound exception.
  153. *
  154. * @author René Jeschke (rene_jeschke@yahoo.de)
  155. */
  156. public static class NjsException extends RuntimeException
  157. {
  158. /** serialVersionUID */
  159. private static final long serialVersionUID = 6003769797804167546L;
  160.  
  161. /** @see RuntimeException#RuntimeException(String) */
  162. public NjsException(String msg)
  163. {
  164. super(msg);
  165. }
  166.  
  167. /** @see RuntimeException#RuntimeException(String, Throwable) */
  168. public NjsException(String msg, Throwable t)
  169. {
  170. super(msg, t);
  171. }
  172.  
  173. /** @see RuntimeException#RuntimeException(Throwable) */
  174. public NjsException(Throwable t)
  175. {
  176. super(t);
  177. }
  178. }
  179.  
  180. /**
  181. * The audio thread class.
  182. *
  183. * @author René Jeschke (rene_jeschke@yahoo.de)
  184. */
  185. private static class AudioThread implements Runnable
  186. {
  187. /** Syncing semaphore. */
  188. private Semaphore syncer = new Semaphore(1);
  189. /** Are we running? .*/
  190. private boolean running = false;
  191. /** The data line. */
  192. final SourceDataLine line;
  193. /** Wanted latency in frames. */
  194. private final int wantedLatency;
  195. /** Number of channels. */
  196. private final int channels;
  197. /** De we already ran? .*/
  198. private boolean alreadyRan = false;
  199.  
  200. /**
  201. * Constructor.
  202. *
  203. * @param dataLine The data line.
  204. * @param channels Number of channels.
  205. * @param wantedLatency Wanted latency in frames.
  206. */
  207. public AudioThread(final SourceDataLine dataLine, final int channels, final int wantedLatency)
  208. {
  209. try
  210. {
  211. this.syncer.acquire();
  212. }
  213. catch (InterruptedException eaten)
  214. {
  215. // *munch*
  216. }
  217. this.line = dataLine;
  218. this.wantedLatency = wantedLatency;
  219. this.channels = channels;
  220. }
  221.  
  222. /**
  223. * Starts audio processing.
  224. */
  225. public void start()
  226. {
  227. if(this.running || this.alreadyRan)
  228. return;
  229. this.running = true;
  230. this.alreadyRan = true;
  231. this.syncer.release();
  232. }
  233.  
  234. /**
  235. * Stops audio processing.
  236. */
  237. public void stop()
  238. {
  239. if(!this.running)
  240. return;
  241. this.running = false;
  242. try
  243. {
  244. this.syncer.acquire();
  245. }
  246. catch (InterruptedException eaten)
  247. {
  248. // *munch*
  249. }
  250. }
  251.  
  252. /**
  253. * The mighty clamp.
  254. *
  255. * @param x x.
  256. * @param min min.
  257. * @param max max.
  258. * @return x < min ? min : x > max ? max : x;
  259. */
  260. private static int clamp(int x, int min, int max)
  261. {
  262. return x < min ? min : x > max ? max : x;
  263. }
  264.  
  265. /** @see Runnable#run() */
  266. @Override
  267. public void run()
  268. {
  269. final int maxBuffer = this.line.getBufferSize();
  270. final int max = this.wantedLatency;
  271. final int frame = this.channels * 2;
  272. final int diff = max * frame;
  273. final int allowed = maxBuffer - diff;
  274. float[] output = new float[max * frame];
  275. byte[] buffer = new byte[output.length * 2];
  276. try
  277. {
  278. this.syncer.acquire();
  279. }
  280. catch (InterruptedException eaten)
  281. {
  282. // *munch*
  283. }
  284. this.line.start();
  285. while(this.running)
  286. {
  287. try
  288. {
  289. Thread.sleep(1);
  290. }
  291. catch (InterruptedException eaten)
  292. {
  293. // munch
  294. }
  295. final int delta = this.line.available() - allowed;
  296. if(delta > 0)
  297. {
  298. final int todo = (delta + diff) / frame;
  299. if(todo * this.channels >= output.length)
  300. {
  301. output = new float[todo * this.channels];
  302. buffer = new byte[output.length * 2];
  303. }
  304. if(NeetJavaSound.callback != null)
  305. {
  306. NeetJavaSound.callback.render(output, todo);
  307. }
  308. for(int i = 0; i < todo * this.channels; i++)
  309. {
  310. final int a = clamp((int)(output[i] * 32768.0), -32768, 32767);
  311.  
  312. buffer[i * 2 + 0] = (byte)a;
  313. buffer[i * 2 + 1] = (byte)(a >> 8);
  314. }
  315. this.line.write(buffer, 0, todo * frame);
  316. }
  317. }
  318. this.line.stop();
  319. this.syncer.release();
  320. }
  321. }
  322. }
Add Comment
Please, Sign In to add comment