java節拍器 定時任務 ScheduledExecutorService 記錄
java節拍器 定時任務 ScheduledExecutorService 總結
起因
明明干啥都是個小菜雞,還那么多事事。是這樣的,想學吉他,手機上的節拍器軟件嫌聲音小,電腦上沒仔細找好用的節拍器,但是電腦有音箱,也用電腦看譜子練練啥的,就想有個電腦好用的節拍器。
簡介
從網上復制了一個播放WAV的代碼, 播放WAV的聲音。
使用ScheduledExecutorService 來定時任務。
掉進了很多坑
先用多線程,會卡。
又用線程池,還是會卡。
查了資料可以用ScheduledExecutorService ,為了學習,先用了Timer和TimerTask,慢的速度可以,快速就不行。
最后用了ScheduledExecutorService。
還有個極大的坑,那就是我剛開始一直連接的藍牙音箱,速度快多線程,線程池都不卡,慢的時候有時候會有拍子不響,真是奇了怪。可是巧了,誤打誤撞,最后用ScheduledExecutorService 的時候,用的筆記本自帶揚聲器,很順利,然后連上了藍牙音箱,速度慢音箱放的聲音會卡。卡不卡和播放設備很有關系。就試了之前用的線程那些方法,果然還是卡,用ScheduledExecutorService 就對了。
多線程
將寫一個播放的類,繼承Thread或者實現Runnable接口,然后
while(true) { //diPath, rate 文件路徑和延時時間
Thread thread1 = new Thread(new MyPlayer(new FileInputStream(diPath)));
Thread thread2 = new Thread(new MyPlayer(new FileInputStream(daPath)));
Thread thread3 = new Thread(new MyPlayer(new FileInputStream(daPath)));
Thread thread4 = new Thread(new MyPlayer(new FileInputStream(daPath)));
thread1.start();
Thread.sleep(rate);
thread2.start();
Thread.sleep(rate);
thread3.start();
Thread.sleep(rate);
thread4.start();
Thread.sleep(rate);
}
}
沒有想到別的辦法,在循環里每次創建。這很垃圾,我也知道。
線程池
用線程池,理論效果應該減少了系統的開銷,可是實際上沒有效果。
思路還是剛剛的思路就是用了線程池,還是垃圾代碼。
ThreadPoolExecutor tpool = new ThreadPoolExecutor(16, 20, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
while (true) { //diPath, rate 文件路徑和延時時間
tpool.execute(new MyPlayerSu(diPath));
Thread.sleep(rate);
tpool.execute(new MyPlayerSu(daPath));
Thread.sleep(rate);
tpool.execute(new MyPlayerSu(daPath));
Thread.sleep(rate);
tpool.execute(new MyPlayerSu(daPath));
Thread.sleep(rate);
}
Timer和TimerTask
使用方法,創建一個類繼承TimerTask,和繼承線程類差不多,然后重寫里面的run()方法,然后創建Timer對象,調用相應的方法,傳入相關對象和數值,也可以使用匿名對象。
Timer timer = new Timer();// 實例化Timer類
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
//代碼
},xx,xx)
Timer有兩個主要的運行方法,schedule和scheduleAtFixedRate,有細微差距。schedule和scheduleAtFixedRate方法一般情況下是沒什么區別的,只在某個情況出現時會有區別--當前任務沒有來得及完成下次任務又交到手上。
我們來舉個例子:
暑假到了老師給schedule和scheduleAtFixedRate兩個同學布置作業。
老師要求學生暑假每天寫2頁,30天后完成作業。
這兩個學生每天按時完成作業,直到第10天,出了意外,兩個學生出去旅游花了5天時間,這5天時間里兩個人都沒有做作業。任務被拖延了。
這時候兩個學生采取的策略就不同了:
schedule重新安排了任務時間,旅游回來的第一天做第11天的任務,第二天做第12天的任務,最后完成任務花了35天。
scheduleAtFixedRate是個守時的學生,她總想按時完成老師的任務,于是在旅游回來的第一天把之前5天欠下的任務以及第16天當天的任務全部完成了,之后還是按照老師的原安排完成作業,最后完成任務花了30天。
用Timer這個方法,太快了的時候,滴答的聲音會播放不完,從而會繼續等播放結束后才能播放下一個聲音,所以假如聲音時間長,或者太快就會不準了。
ScheduledExecutorService 代替Timer
ExecutorService可以調度命令在給定的延遲之后運行,或定期執行。 詳解去百度,百度上用一些。
創建ScheduledExecutorService
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
使用的話,有三個方法schedule,scheduleAtFixedRate,scheduleWithFixedDelay。
schedule(Callable<V> callable, long delay, TimeUnit unit)創建并執行在給定延遲后啟用的ScheduledFuture。schedule(Runnable command, long delay, TimeUnit unit)創建并執行在給定延遲后啟用的單次操作。scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)創建并執行在給定的初始延遲之后,隨后以給定的時間段首先啟用的周期性動作; 那就是執行將在initialDelay之后開始,然后是initialDelay+period,然后是initialDelay + 2 * period,等等。scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)創建并執行在給定的初始延遲之后首先啟用的定期動作,隨后在一個執行的終止和下一個執行的開始之間給定的延遲。
看方法描述,用scheduleAtFixedRate,傳入的runnable會自己循環運行,就傳入一次就行。
我先用循環創建幾個Thread對象
/**
* @param di 滴的文件路徑
* @param da 噠的文件路徑
* @param patNum 一小節響幾次
* @return 創建的播放聲音的線程數組
*/
public Thread[] getThread(String di, String da, int patNum) {
Thread[] threads = new Thread[patNum];
threads[0] = new Thread(new MyPlayerWAV(di));
for (int i = 1; i < threads.length; i++) {
threads[i] = new Thread(new MyPlayerWAV(da));
}
return threads;
}
然后,創建對象,傳入數值,執行方法就OK
PalyerPat palyer = new PalyerPat();
Thread[] threads = palyer.getThread(diPath, daPath, patNum);
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
for (int i = 0; i < threads.length; i++) {
service.scheduleAtFixedRate(threads[i], rate * i, rate * patNum, TimeUnit.MILLISECONDS);
}//rate * i 頭一次響的開始時間
//rate * patNum 延時多久。響一次需要延時一小節。
播放WAV方法
java原生就支持,不用第三方。(copy大佬的)
import java.io.*;
import java.util.concurrent.*;
import javax.sound.sampled.*;
public void player(String filename) throws UnsupportedAudioFileException, IOException, LineUnavailableException {
AudioInputStream stream;
stream = AudioSystem.getAudioInputStream(new File(filename));
AudioFormat target = stream.getFormat();
DataLine.Info dinfo = new DataLine.Info(SourceDataLine.class, target);
SourceDataLine line = null;
int len = -1;
line = (SourceDataLine) AudioSystem.getLine(dinfo);
line.open(target);
line.start();
byte[] buffer = new byte[1024];
while ((len = stream.read(buffer)) > 0) {
line.write(buffer, 0, len);
}
line.drain();
line.stop();
line.close();
stream.close();
}
播放mp3
需要包mp3spi。
maven依賴
<dependency>
<groupId>com.googlecode.soundlibs</groupId>
<artifactId>mp3spi</artifactId>
<version>1.9.5.4</version>
</dependency>
Player player;
player = new Player(new FileInputStream(filename));//網上的代碼外面還有一層BufferedInputStream,當時找播放卡的問題看了源碼,原理里面有創建一個BufferedInputStream,我就省掉了。
player.play();
如果用這個播放,會卡,還是卡,啊,剛剛試了一下,原因以后再找。這只是播放的,在節拍器中會卡的。原來還是有坑。坑太多了。
end
非音樂專業,不專業,勿吐槽。非大佬,小菜雞一只,望大佬指點。
更新一下,播放MP3卡不卡,和聲音文件也有關系,把之前用WAV文件轉成了MP3,是不會卡的,之前的MP3是我從一段音頻中截出來的,可能太粗糙了,造成了這樣的原因。現在所用的WAV音頻,是從手機安裝包中偷的,很nice。
附上全部代碼
import java.io.File;
import java.io.IOException;
import java.util.concurrent.*;
import javax.sound.sampled.*;
public class PalyerPat {
public static void main(String[] args) {
String diPath = "src/main/resources/tone1.wav";
String daPath = "src/main/resources/tone1_1.wav";
int patNum = 4;
int speed = 160;//輸入的速度
int rate = (int) (60F / speed * 1000);//轉成每響一次延時的毫秒。
PalyerPat palyer = new PalyerPat();
Thread[] threads = palyer.getThread(diPath, daPath, patNum);
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
for (int i = 0; i < threads.length; i++) {
service.scheduleAtFixedRate(threads[i], rate * i, rate * patNum, TimeUnit.MILLISECONDS);
}
}
/**
* @param di 滴的文件路徑
* @param da 噠的文件路徑
* @param patNum 一小節響幾次
* @return 創建的播放聲音的線程數組
*/
public Thread[] getThread(String di, String da, int patNum) {
Thread[] threads = new Thread[patNum];
threads[0] = new Thread(new MyPlayerWAV(di));
for (int i = 1; i < threads.length; i++) {
threads[i] = new Thread(new MyPlayerWAV(da));
}
return threads;
}
}
class MyPlayerWAV implements Runnable {
String filename = "src/main/resources/tone1.wav";
public MyPlayerWAV() {
}
public MyPlayerWAV(String filename) {
super();
this.filename = filename;
}
public void run() {
try {
playerWAV(filename);
// System.out.println(filename);
} catch (UnsupportedAudioFileException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (LineUnavailableException e) {
e.printStackTrace();
}
}
//播放WAV文件方法
public void playerWAV(String filename) throws UnsupportedAudioFileException, IOException, LineUnavailableException {
AudioInputStream stream;
stream = AudioSystem.getAudioInputStream(new File(filename));
AudioFormat target = stream.getFormat();
DataLine.Info dinfo = new DataLine.Info(SourceDataLine.class, target);
SourceDataLine line = null;
int len = -1;
line = (SourceDataLine) AudioSystem.getLine(dinfo);
line.open(target);
line.start();
byte[] buffer = new byte[1024];
while ((len = stream.read(buffer)) > 0) {
line.write(buffer, 0, len);
}
line.drain();
line.stop();
line.close();
stream.close();
}
}
浙公網安備 33010602011771號