java如何呼叫本地揚聲器

青衫染紅塵發表於2021-05-14

各位看官可以關注博主個人部落格,瞭解更多資訊。
作者:Surpasser
連結地址:https://surpass.org.cn

前言

博主的畢設系統在做一個餐廳的點餐管理系統,在記性移動端頁面開發的時候突發奇想做一個呼叫服務員,揚聲器發聲的一個功能類似於:“工作人員請注意,桌號8001顧客正在尋求幫助!”。

實現方式

接下來就對這個小功能進行分析和實現。先寫一個Demo。

  1. 首先,我們需要一個dll作為輔助。這裡解釋一下dll的含義(DLL(Dynamic Link Library)檔案為動態連結庫檔案,又稱“應用百程式擴充”,是軟體檔案型別。在Windows中,許多應用程式並不是一個度完整的可執行檔案,它們被分割成一些相知對獨立的動態連結庫,即DLL檔案,放置於道系統中。當我們執行某一個程式時,相應的版DLL檔案就會被呼叫。一個應用程式可使用權多個DLL檔案,一個DLL檔案也可能被不同的應用程式使用,這樣的DLL檔案被稱為共享DLL檔案)。

    需要把jacob-1.17-M2-x64.dll複製到C:\Windows\System32\目錄下。我們也能看到目錄下有很多的.dll檔案。

    這裡的檔案大家自己百度下,很好找的。

  2. 使用maven專案匯入座標。

    <!-- https://mvnrepository.com/artifact/net.sf.jacob-project/jacob -->
    <dependency>
    	<groupId>net.sf.jacob-project</groupId>
    	<artifactId>jacob</artifactId>
    	<version>1.14.3</version>
    </dependency>
    
  3. 測試類程式碼。

    /**
     * 文字轉語音測試 jdk bin檔案中需要匯入jacob-1.17-M2-x64.dll
     * 注意導包哈
     * @date: 2020年2月25日 上午10:05:21
     */
    public class Jacobtest {
    
    
        public static void main(String[] args) {
            textToSpeech("工作人員請注意,桌號8001顧客正在尋求幫助!!");
        }
    
        /**
         * 語音轉文字並播放
         *
         * @param text
         */
        public static void textToSpeech(String text) {
            ActiveXComponent ax = null;
            try {
                ax = new ActiveXComponent("Sapi.SpVoice");
    
                // 執行時輸出語音內容
                Dispatch spVoice = ax.getObject();
                // 音量 0-100
                ax.setProperty("Volume", new Variant(100));
                // 語音朗讀速度 -10 到 +10
                ax.setProperty("Rate", new Variant(0));
                // 執行朗讀
                Dispatch.call(spVoice, "Speak", new Variant(text));
    
               /* // 下面是構建檔案流把生成語音檔案
    
                ax = new ActiveXComponent("Sapi.SpFileStream");
                Dispatch spFileStream = ax.getObject();
    
                ax = new ActiveXComponent("Sapi.SpAudioFormat");
                Dispatch spAudioFormat = ax.getObject();
    
                // 設定音訊流格式
                Dispatch.put(spAudioFormat, "Type", new Variant(22));
                // 設定檔案輸出流格式
                Dispatch.putRef(spFileStream, "Format", spAudioFormat);
                // 呼叫輸出 檔案流開啟方法,建立一個.wav檔案
                Dispatch.call(spFileStream, "Open", new Variant("./text.wav"), new Variant(3), new Variant(true));
                // 設定聲音物件的音訊輸出流為輸出檔案物件
                Dispatch.putRef(spVoice, "AudioOutputStream", spFileStream);
                // 設定音量 0到100
                Dispatch.put(spVoice, "Volume", new Variant(100));
                // 設定朗讀速度
                Dispatch.put(spVoice, "Rate", new Variant(-2));
                // 開始朗讀
                Dispatch.call(spVoice, "Speak", new Variant(text));
    
                // 關閉輸出檔案
                Dispatch.call(spFileStream, "Close");
                Dispatch.putRef(spVoice, "AudioOutputStream", null);
    
                spAudioFormat.safeRelease();
                spFileStream.safeRelease();*/
                spVoice.safeRelease();
                ax.safeRelease();
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
  4. 從測試類可以看出,這個方法既可以發聲還能輸出字尾為.wav的檔案,這是一個標準的多媒體檔案。上述程式碼註釋很清晰,就不解釋了,自己看哈。

  5. 測試成功,現在整合到自己的專案中。

另述

這裡說到了呼叫揚聲器發聲,不放還可以想一下如何呼叫麥克風收音。

public class EngineeCore {
    String filePath = "E:\\voice\\voice_cache.wav";
    AudioFormat audioFormat;
    TargetDataLine targetDataLine;
    boolean flag = true;
    
	private void stopRecognize() {
        flag = false;
        targetDataLine.stop();
        targetDataLine.close();
    }
    private AudioFormat getAudioFormat() {
        float sampleRate = 16000;
        // 8000,11025,16000,22050,44100
        int sampleSizeInBits = 16;
        // 8,16
        int channels = 1;
        // 1,2
        boolean signed = true;
        // true,false
        boolean bigEndian = false;
        // true,false
        return new AudioFormat(sampleRate, sampleSizeInBits, channels, signed, bigEndian);
    }// end getAudioFormat


    private void startRecognize() {
        try {
            // 獲得指定的音訊格式
            audioFormat = getAudioFormat();
            DataLine.Info dataLineInfo = new DataLine.Info(TargetDataLine.class, audioFormat);
            targetDataLine = (TargetDataLine) AudioSystem.getLine(dataLineInfo);
            // Create a thread to capture the microphone
            // data into an audio file and start the
            // thread running. It will run until the
            // Stop button is clicked. This method
            // will return after starting the thread.
            flag = true;
            new CaptureThread().start();
        } catch (Exception e) {
            e.printStackTrace();
        } // end catch
    }// end captureAudio method

    class CaptureThread extends Thread {
        public void run() {
            AudioFileFormat.Type fileType = null;
            File audioFile = new File(filePath);

            fileType = AudioFileFormat.Type.WAVE;
            //聲音錄入的權值
            int weight = 2;
            //判斷是否停止的計數
            int downSum = 0;

            ByteArrayInputStream bais = null;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            AudioInputStream ais = null;
            try {
                targetDataLine.open(audioFormat);
                targetDataLine.start();
                byte[] fragment = new byte[1024];

                ais = new AudioInputStream(targetDataLine);
                while (flag) {

                    targetDataLine.read(fragment, 0, fragment.length);
                    //當陣列末位大於weight時開始儲存位元組(有聲音傳入),一旦開始不再需要判斷末位
                    if (Math.abs(fragment[fragment.length-1]) > weight || baos.size() > 0) {
                        baos.write(fragment);
                        System.out.println("守衛:"+fragment[0]+",末尾:"+fragment[fragment.length-1]+",lenght"+fragment.length);
                        //判斷語音是否停止
                        if(Math.abs(fragment[fragment.length-1])<=weight){
                            downSum++;
                        }else{
                            System.out.println("重置奇數");
                            downSum=0;
                        }
               //計數超過20說明此段時間沒有聲音傳入(值也可更改)
                        if(downSum>20){
                            System.out.println("停止錄入");
                            break;
                        }

                    }
                }

                //取得錄音輸入流
                audioFormat = getAudioFormat();
                byte audioData[] = baos.toByteArray();
                bais = new ByteArrayInputStream(audioData);
                ais = new AudioInputStream(bais, audioFormat, audioData.length / audioFormat.getFrameSize());
                //定義最終儲存的檔名
                System.out.println("開始生成語音檔案");
                AudioSystem.write(ais, AudioFileFormat.Type.WAVE, audioFile);
                downSum = 0;
                stopRecognize();

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //關閉流

                try {
                    ais.close();
                    bais.close();
                    baos.reset();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }// end run
    }// end inner class CaptureThread

這個測試沒測試,偷個懶找的“哈哈”。

還有一點是Java操作語音檔案.wav先不要研究了 :laugh and cry:,這裡涉及到了語音識別,但是有百度那麼些api,有興趣的試試吧!

好了,在這裡就結束了

更新

博主把自己的畢設專案打包放到自己的伺服器上,這個揚聲器出現了新的問題。

本來所有的基礎都是在本地執行的,通過呼叫本地dll檔案實現揚聲器發聲,現在部署到centOS上將會失去這個dll的支援,目前所存在的問題是如何不使用dll檔案實現這個功能,中間藉助了.wav字尾的音視訊檔案。

  • 如何在Linux上生成.wav的檔案。
  • 如何獲取這個檔案並輸出。(解釋一下,用餐顧客點選手機網頁的選單,然後再餐廳的主機來播放這個聲音)
  • 如何在輸出主機不進行任何操作就能播放這個聲音或者能夠恢復之前的工作狀態。

現在的臨時解決辦法是本地跑一個呼叫服務的介面,當需要這個功能的時候遠端伺服器呼叫本地跑的介面,進而實現餐廳主機發聲。

這個和上面描述的並無差別,不一樣的是存在了兩臺主機的呼叫(當然兩臺主機都應該連結網路,能夠互相通訊)

先寫到這了,當有解決辦法的時候再更新吧!

相關文章