1. 程式人生 > >語音播報(播報本地音訊檔案)實現收款金額的播報

語音播報(播報本地音訊檔案)實現收款金額的播報

最近專案需要實現語音播報收款金額,本來要使用百度AL開發平臺的語音合成進行語音播報,雖然這個是可以完美實現,但是這個在免費的情況下有這播放條數的限制,所以最終決定使用播放本地語音的方式進行實現。不多說,直接貼程式碼,程式碼也都進行了註釋:

一、FileUtils檔案讀取工具類

public class FileUtils {

    public static AssetFileDescriptor getAssetFileDescription(String filename) throws IOException {
        AssetManager manager = CardDoctorApplication.getContext().getAssets();
        return manager.openFd(filename);
    }
}

CardDoctorApplication是自己的application類,就不貼出來了。

二、音訊播放類

import android.content.res.AssetFileDescriptor;
import android.media.MediaPlayer;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 音訊播放類
 * Created by Administrator on 2018/10/9.
 */

public class VoiceSpeaker {

    private static VoiceSpeaker sInstance;

    private ExecutorService service;//執行緒池

    private VoiceSpeaker() {
        service = Executors.newCachedThreadPool();
    }

    /**
     * 獲取音訊播放物件
     * @return
     */
    public static synchronized VoiceSpeaker getInstance() {
        if (sInstance == null) {
            sInstance = new VoiceSpeaker();
        }
        return sInstance;
    }


    /**
     * 播放前檢查
     * @param list
     */
    public void speak(final List<String> list){
        if (service != null){
            service.execute(new Runnable() {
                @Override
                public void run() {
                    start(list);
                }
            });
        }
    }

    /**
     * 開始播放
     * @param list
     */
    private void start(final List<String> list) {
        synchronized (this) {
            final CountDownLatch latch = new CountDownLatch(1);//併發
            MediaPlayer player = new MediaPlayer();
            /**
             * 遍歷音訊模板集合,按順序播放音訊檔案
             */
            if (list != null && list.size() > 0) {
                final int[] counter = {0};
                /**
                 * format方法使用佔位符進行格式化
                 * %s中的 s 代表 字串型別
                 *
                 * path是音訊檔案的路徑,通過格式化之後匹配到對應的音訊檔案
                 */
                String path = String.format("sound/tts_%s.mp3", list.get(counter[0]));
                AssetFileDescriptor fd = null;
                try {
                    fd = FileUtils.getAssetFileDescription(path);
                    /**
                     * 設定要使用的資料來源(FileDescriptor)。FileDescriptor必須是可搜尋的(注意,LocalSocket不可搜尋)。呼叫者有責任關閉檔案描述符。
                     * 引數說明:
                     * 第一個引數:FileDescriptor:您要播放的檔案的FileDescriptor
                     * 第二個引數:long:要播放的資料開始的檔案的偏移量,以位元組為單位
                     * 第三個引數:long:要播放的資料的位元組長度
                     */
                    player.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(),
                            fd.getLength());
                    /**
                     * 準備播放器以非同步方式播放。設定資料來源和顯示錶面後,您需要呼叫prepare()或prepareAsync()。
                     * 對於流,您應該呼叫prepareAsync(),它會立即返回,而不是阻塞,直到緩衝了足夠的資料。
                     */
                    player.prepareAsync();
                    /**
                     * 註冊媒體源準備播放時要呼叫的回撥。
                     */
                    player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                        @Override
                        public void onPrepared(MediaPlayer mp) {
                            mp.start();
                        }
                    });
                    /**
                     * 註冊在回放期間到達媒體源末尾時要呼叫的回撥。
                     */
                    player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                        @Override
                        public void onCompletion(MediaPlayer mp) {
                            /**
                             * 將MediaPlayer重置為未初始化狀態。呼叫此方法後,您必須通過設定資料來源並呼叫prepare()再次初始化它。
                             */
                            mp.reset();
                            counter[0]++;
                            if (counter[0] < list.size()) {
                                try {
                                    AssetFileDescriptor fileDescriptor = FileUtils.getAssetFileDescription(String.format("sound/tts_%s.mp3", list.get(counter[0])));
                                    mp.setDataSource(fileDescriptor.getFileDescriptor(), fileDescriptor.getStartOffset(), fileDescriptor.getLength());
                                    mp.prepare();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                    latch.countDown();//完成預期工作,發出完成訊號...
                                }
                            } else {
                                /**
                                 * 是否MediaPlayer資源
                                 */
                                mp.release();
                                latch.countDown();//完成預期工作,發出完成訊號...
                            }
                        }
                    });
                } catch (IOException e) {
                    e.printStackTrace();
                    latch.countDown();//完成預期工作,發出完成訊號...
                }finally {
                    if (fd != null){
                        try {
                            fd.close();//關閉檔案操作
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }

            try {
                latch.await();////等待主執行緒執行完畢,獲得開始執行訊號...
                this.notifyAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

三、音訊播放模板處理類

import android.text.TextUtils;

import com.yunkahui.datacubeper.common.utils.LogUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * 音訊播放模板處理類
 * Created by Administrator on 2018/10/9.
 */

public class VoiceTemplate {
    private static final String DOT = ".";

    private String numString;

    private String prefix;//收款成功音訊

    private String suffix;//元的音訊

    public VoiceTemplate() {

    }

    /**
     * 獲取播放的音訊檔案順序集合
     *
     * @param money
     * @return
     */
    public static List<String> getDefaultTemplate(String money) {
        return new VoiceTemplate()
                .prefix("success")
                .numString(money)
                .suffix("yuan")
                .gen();
    }

    public String getPrefix() {
        return prefix;
    }

    public VoiceTemplate prefix(String prefix) {
        this.prefix = prefix;
        return this;
    }

    public String getSuffix() {
        return suffix;
    }

    public VoiceTemplate suffix(String suffix) {
        this.suffix = suffix;
        return this;
    }


    public String getNumString() {
        return numString;
    }

    public VoiceTemplate numString(String numString) {
        this.numString = numString;
        return this;
    }


    public List<String> gen() {
        return genVoiceList();
    }

    private List<String> createReadableNumList(String numString) {
        List<String> result = new ArrayList<>();
        if (!TextUtils.isEmpty(numString)) {
            int len = numString.length();
            for (int i = 0; i < len; i++) {
                if ('.' == numString.charAt(i)) {
                    result.add("dot");
                } else {
                    result.add(String.valueOf(numString.charAt(i)));
                }
            }
        }
        return result;
    }


    /**
     * 播放音訊檔案順序
     *
     * @return
     */
    private List<String> genVoiceList() {
        List<String> result = new ArrayList<>();
        if (!TextUtils.isEmpty(prefix)) {
            result.add(prefix);
        }
        if (!TextUtils.isEmpty(numString)) {
            result.addAll(genReadableMoney(numString));
        }

        if (!TextUtils.isEmpty(suffix)) {
            result.add(suffix);
        }
        return result;
    }

    /**
     * 拆分金額成一個集合,以對應音訊檔案
     *
     * @param numString
     * @return
     */
    private List<String> genReadableMoney(String numString) {
        List<String> result = new ArrayList<>();
        if (!TextUtils.isEmpty(numString)) {
            /**
             * contains()返回TRUE時,該字串中存在該字元
             * 此處判斷字串是否包含小數點
             */
            if (numString.contains(DOT)) {
                String integerPart = numString.split("\\.")[0];
                String decimalPart = numString.split("\\.")[1];
                //整數部分
                List<String> intList = readIntPart(integerPart);
                //小數點之後的部分
                List<String> decimalList = readDecimalPart(decimalPart);
                result.addAll(intList);
                if (!decimalList.isEmpty()) {
                    result.add("dot");
                    result.addAll(decimalList);
                }
            } else {
                //int
                result.addAll(readIntPart(numString));
            }
        }
        return result;
    }

    /**
     * 拆分小數點後面字串
     *
     * @param decimalPart
     * @return
     */
    private List<String> readDecimalPart(String decimalPart) {
        List<String> result = new ArrayList<>();
        if (!"00".equals(decimalPart)) {
            char[] chars = decimalPart.toCharArray();
            for (char ch : chars) {
                result.add(String.valueOf(ch));
            }
        }
        return result;
    }

    /**
     * 當金額為整數時,直接拆分,拆分成
     *
     * @param integerPart
     * @return
     */
    private List<String> readIntPart(String integerPart) {
        List<String> result = new ArrayList<>();
        String intString = readLong(Long.parseLong(integerPart));
        int len = intString.length();
        for (int i = 0; i < len; i++) {
            char current = intString.charAt(i);
            if (current == '拾') {
                result.add("ten");
            } else if (current == '佰') {
                result.add("hundred");
            } else if (current == '仟') {
                result.add("thousand");
            } else if (current == '萬') {
                result.add("ten_thousand");
            } else if (current == '億') {
                result.add("ten_million");
            } else {
                result.add(String.valueOf(current));
            }
        }
        return result;
    }


    private static final char[] NUM = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
    private static final char[] CHINESE_UNIT = {'元', '拾', '佰', '仟', '萬', '拾', '佰', '仟', '億', '拾', '佰', '仟'};

    /**
     * 返回關於錢的中文式大寫數字,支僅持到億,超過億則需要使用長整型long
     */
    public static String readLong(long moneyNum) {
        long mMoneyNum = moneyNum;
        String res = "";
        int i = 0;
        if (moneyNum == 0) {
            return "0";
        }

        if (moneyNum == 10) {
            return "拾";
        }

        if (moneyNum > 10 && moneyNum < 20) {
            return "拾" + moneyNum % 10;
        }

        while (moneyNum > 0) {
            res = CHINESE_UNIT[i++] + res;
            res = NUM[(int) (moneyNum % 10)] + res;
            moneyNum /= 10;
        }
        /**
         * replaceAll() 方法使用給定的引數 replacement 替換字串所有匹配給定的正則表示式的子字串。
         * 引數
         * 第一個引數:regex -- 匹配此字串的正則表示式。
         * 第二個引數:newChar -- 用來替換每個匹配項的字串。
         * 成功則返回替換的字串,失敗則返回原始字串。
         */
        if (mMoneyNum >= 100000000) {
            //整億
            if (mMoneyNum % 100000000 == 0) {
                return res.replaceAll("0[拾佰仟萬]", "0")
                        .replaceAll("0+億", "億").replaceAll("0+萬", "萬")
                        .replaceAll("0+元", "元").replaceAll("0+", "0")
                        .replace("元", "");
            } else {
                //非整億,有萬位或者千位(這裡萬位包括萬、十萬、百萬、千萬位)
                String a = String.valueOf(mMoneyNum);
                LogUtils.e("原始資料--》" + a);
                LogUtils.e("擷取萬位--》" + a.substring(a.length() - 8, a.length() - 4));
                if (Long.parseLong(String.valueOf(mMoneyNum).substring(String.valueOf(mMoneyNum).length() - 8, String.valueOf(mMoneyNum).length() - 4)) == 0) {
                    //萬位為全部為0的情況,例如 1500002000.00 此情況不需要讀出 萬,直接讀一十五億零二千元
                    return res.replaceAll("0[拾佰仟]", "0")
                            .replaceAll("0+億", "億").replaceAll("0+萬", "0")
                            .replaceAll("0+元", "元").replaceAll("0+", "0")
                            .replace("元", "");

                } else {
                    //萬位為不為0的情況,例如  1520002000.00 此情況需要讀出 萬,讀一十五億二千萬二千元
                    return res.replaceAll("0[拾佰仟]", "0")
                            .replaceAll("0+億", "億").replaceAll("0+萬", "萬")
                            .replaceAll("0+元", "元").replaceAll("0+", "0")
                            .replace("元", "");
                }
            }
        } else {
            //小於一億
            return res.replaceAll("0[拾佰仟]", "0")
                    .replaceAll("0+億", "億").replaceAll("0+萬", "萬")
                    .replaceAll("0+元", "元").replaceAll("0+", "0")
                    .replace("元", "");
        }
    }

}

四、新建assets資料夾,把音訊檔案放在assets目錄下

此demo可支援到千億播放,對播放的語序也進行了一些處理,但是可能還有些許情況沒有考慮到,需要使用的朋友可以按自己的需求自行新增