語音播報(播報本地音訊檔案)實現收款金額的播報
阿新 • • 發佈:2019-01-01
最近專案需要實現語音播報收款金額,本來要使用百度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可支援到千億播放,對播放的語序也進行了一些處理,但是可能還有些許情況沒有考慮到,需要使用的朋友可以按自己的需求自行新增