利用科大讯飞API实现语音识别,SWT实现客户端封装2

解决SWT主线程假死现象,研究SWT UI线程以及非UI线程关系。

消息循环

所谓“消息循环”,实际是程序循环。即Windows 中有一个系统消息队列,对于每一个正在执行的Windows应用程序,系统为其建立一个“消息队列”,即应用程序队列,用来存放该程序可能创建的各种窗口的消息。应用程序中含有一段称作“消息循环”的代码,用来从消息队列中检索这些消息并把它们分发到相应的窗口函数中。如果应用程序处理某个消息事件的时候,用了很长的时间,这时候后续的消息无法及时得到处理,就会造成应用程序没有响应,也就是常说的“假死”状态。 所以,应用程序如果处理某个事件需要较长的时间,需要把这个操作放到一个另外的线程中进行处理。

UI线程与非UI线程

SWT中主线程为UI线程

   /**
	 * Launch the application.
	 * @param args
	 */
	public static void main(String args[]) {
		try {
			VoiceToText window = new VoiceToText();
			window.setBlockOnOpen(true);
			window.open();
			Display.getCurrent().dispose();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

主线程中各种控件的操作函数也为UI线程

      Button btnNewButton = new Button(container, SWT.NONE);
		btnNewButton.setBounds(89, 59, 80, 27);
		btnNewButton.setText("New Button");
		btnNewButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				// TODO Auto-generated method stub
				for(int i=0; i<20; i++){
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
				}
			}
		});

UI线程中不能执行阻塞操作,否则会出现假死状态

UI线程必须实时得到响应,否则会出现假死状态,例如执行如下程序:

       btnNewButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				// TODO Auto-generated method stub
				for(int i=0; i<20; i++){
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
				}
			}
		});

UI线程会出现假死状态

                           

UI线程中耗时操作,应该启动非UI线程来执行耗时操作

将耗时操作放入到非UI线程中,则不会出现假死状态

      btnNewButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				// TODO Auto-generated method stub				
				new Thread(new Runnable() {					
					@Override
					public void run() {
						// TODO Auto-generated method stub
						for(int i=0; i<40; i++){
							try {
								Thread.sleep(1000);
							} catch (InterruptedException e1) {
								// TODO Auto-generated catch block
								e1.printStackTrace();
							}
						}
					}
				}).start();
	
			}
		});

                                    

非UI线程中不能直接操作UI线程元素,否则会出现Invalid thread access

     btnNewButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				// TODO Auto-generated method stub				
				new Thread(new Runnable() {					
					@Override
					public void run() {
						// TODO Auto-generated method stub
						for(int i=0; i<40; i++){
							try {
								Thread.sleep(1000);
								btnNewButton.setText(String.valueOf(i));
							} catch (InterruptedException e1) {
								// TODO Auto-generated catch block
								e1.printStackTrace();
							}
						}
					}
				}).start();
	
			}
		});

                

非UI线程中操作UI线程元素应该使用Display.asyncExec或者syncExec方法

       btnNewButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				// TODO Auto-generated method stub				
				new Thread(new Runnable() {					
					@Override
					public void run() {
						// TODO Auto-generated method stub
						for(int i=0; i<40; i++){
							try {
								Thread.sleep(1000);
								parent.getDisplay().asyncExec(new Runnable() {									
									@Override
									public void run() {
										// TODO Auto-generated method stub
										btnNewButton.setText(String.valueOf(k++));
									}
								});

							} catch (InterruptedException e1) {
								// TODO Auto-generated catch block
								e1.printStackTrace();
							}
						}
					}
				}).start();
	
			}
		});

                                 

非UI线程中Display.asyncExec或者syncExec方法不能阻塞,否则会出现假死状态。因为非UI线程中Display.asyncExec或者syncExec方法是通过UI线程来执行实现的,应该若该方法阻塞,则UI线程会出现阻塞。

        btnNewButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				// TODO Auto-generated method stub				
				new Thread(new Runnable() {					
					@Override
					public void run() {					
						parent.getDisplay().asyncExec(new Runnable() {									
							@Override
							public void run() {
								for(int i=0; i<40; i++){
									try {
										Thread.sleep(1000);
										parent.getDisplay().asyncExec(new Runnable() {									
											@Override
											public void run() {
												// TODO Auto-generated method stub
												btnNewButton.setText(String.valueOf(k++));
											}
										});

									} catch (InterruptedException e1) {
										// TODO Auto-generated catch block
										e1.printStackTrace();
									}
								}
							}
						});					
					}
				}).start();
	
			}
		});

                                  

由以上可知,耗时操作应该放在非UI线程中,在非UI线程中操作UI线程元素,需要使用Display.asyncExec或者syncExec方法,并且该方法中不能执行耗时操作。

因此最终SWT程序设计如下:

开始转换的按钮逻辑如下:

       //开始转换按钮
		Button startThansfer = new Button(container, SWT.NONE);
		startThansfer.setBounds(340, 185, 283, 27);
		startThansfer.setText("开始转换");
		startThansfer.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				logDetailText.append(DateUtils.getDataPrefix() + "开始转换........" + "\n");
				voicePath = voicePathText.getText();
				textPath = textPathText.getText();
				outputPath = textPath + "\\" + System.currentTimeMillis() + ".txt";
				new Thread(new TransferThreadRunnable(parent, logDetailText, voicePath, textPath, startThansfer, outputPath)).start();				
			}
		});

非UI线程类的设计如下:

package com.voice.text;

import java.io.FileOutputStream;
import java.util.HashMap;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Text;

import com.alibaba.fastjson.JSON;
import com.iflytek.msp.cpdb.lfasr.client.LfasrClientImp;
import com.iflytek.msp.cpdb.lfasr.exception.LfasrException;
import com.iflytek.msp.cpdb.lfasr.model.LfasrType;
import com.iflytek.msp.cpdb.lfasr.model.Message;
import com.iflytek.msp.cpdb.lfasr.model.ProgressStatus;
import com.iflytek.voicecloud.lfasr.demo.Test;

public class TransferThreadRunnable implements Runnable {
	
	private Text logDetailText;
	private LfasrType type = LfasrType.LFASR_STANDARD_RECORDED_AUDIO;
	private int sleepSecond = 20;
	private String voicePath;
	private Display display;
	private Button startThansfer;
	private String outputPath;
	
	public TransferThreadRunnable(Composite parent, Text logDetailText, String voicePath, String textPath, Button startThansfer, String outputPath) {
		this.logDetailText = logDetailText;		
		this.voicePath = voicePath;
		this.display = parent.getDisplay();
		this.startThansfer = startThansfer;
		this.outputPath = outputPath;
	}
	
	
	public void asycExecPrint(String log){
		display.asyncExec(new Runnable() {				
			@Override
			public void run() {		   
				logDetailText.append(log);						
			}
		});
	}
	
	public void asycExecSetStartThansferButtonStatus(boolean status){
		display.asyncExec(new Runnable() {				
			@Override
			public void run() {		   
				startThansfer.setEnabled(status);					
			}
		});
	}
	
	@Override
	public void run() {	
		transfer();	
	}
	
	private int transfer(){
		asycExecSetStartThansferButtonStatus(false);
		// 初始化LFASRClient实例
        LfasrClientImp lc = null;
        try {
            lc = LfasrClientImp.initLfasrClient();
        } catch (LfasrException e) {
            // 初始化异常,解析异常描述信息
            Message initMsg = JSON.parseObject(e.getMessage(), Message.class);
            asycExecPrint(DateUtils.getDataPrefix() + "ecode = " + initMsg.getErr_no() + "\n");
            asycExecPrint(DateUtils.getDataPrefix() + "failed = " + initMsg.getFailed() + "\n");            
            return -1;
        }

        // 获取上传任务ID
        String task_id = "";
        HashMap<String, String> params = new HashMap<String, String>();
        params.put("has_participle", "true");
        //合并后标准版开启电话版功能
        //params.put("has_seperate", "true");
        try {
            // 上传音频文件
            Message uploadMsg = lc.lfasrUpload(voicePath, type, params);

            while(true){
            	// 判断返回值
            	try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
            	asycExecPrint(DateUtils.getDataPrefix() + "文件正在上传......" + "\n");
                int ok = uploadMsg.getOk();
                if (ok == 0) {
                    // 创建任务成功
                    task_id = uploadMsg.getData();
                    asycExecPrint(DateUtils.getDataPrefix() + "创建任务成功  task_id = " + task_id + "\n");
                    break;
                } else {
                    // 创建任务失败-服务端异常
                    asycExecPrint(DateUtils.getDataPrefix() + "ecode = " + uploadMsg.getErr_no() + "\n");
                    asycExecPrint(DateUtils.getDataPrefix() + "failed = " + uploadMsg.getFailed() + "\n");
                    return -1;
                }
            }            
        } catch (LfasrException e) {
            // 上传异常,解析异常描述信息
            Message uploadMsg = JSON.parseObject(e.getMessage(), Message.class);
            asycExecPrint(DateUtils.getDataPrefix() + "ecode=" + uploadMsg.getErr_no() + "\n"); 
            asycExecPrint(DateUtils.getDataPrefix() + "failed=" + uploadMsg.getFailed() + "\n"); 
            return -1;
        }

        // 循环等待音频处理结果
        while (true) {
            try {
            	// 等待20s在获取任务进度
            	for(int i=0; i<sleepSecond/2; i++){
            		//每隔2秒打印一次当前状态
                    Thread.sleep(2000);
                    asycExecPrint(DateUtils.getDataPrefix() + "当前任务正在处理 " + "waiting ..." + "\n");
            	}               
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                // 获取处理进度
                Message progressMsg = lc.lfasrGetProgress(task_id);

                // 如果返回状态不等于0,则任务失败
                if (progressMsg.getOk() != 0) {
                    asycExecPrint(DateUtils.getDataPrefix() + "任务处理失败. task_id:" + task_id + "\n");
                    asycExecPrint(DateUtils.getDataPrefix() + "ecode=" + progressMsg.getErr_no() + "\n");
                    asycExecPrint(DateUtils.getDataPrefix() + "failed=" + progressMsg.getFailed() + "\n");
                    return -1;
                } else {
                    ProgressStatus progressStatus = JSON.parseObject(progressMsg.getData(), ProgressStatus.class);
                    if (progressStatus.getStatus() == 9) {
                        // 处理完成
                        asycExecPrint(DateUtils.getDataPrefix() + "任务处理完成. task_id = " + task_id + "\n");
                        break;
                    } else {
                        // 未处理完成
                        asycExecPrint(DateUtils.getDataPrefix() + "任务没有处理完成,继续处理. task_id = " + task_id + ", status:" + progressStatus.getDesc() + "\n");
                        continue;
                    }
                }
            } catch (LfasrException e) {
                // 获取进度异常处理,根据返回信息排查问题后,再次进行获取
                Message progressMsg = JSON.parseObject(e.getMessage(), Message.class);
                asycExecPrint(DateUtils.getDataPrefix() + "ecode = " + progressMsg.getErr_no() + "\n");
                asycExecPrint(DateUtils.getDataPrefix() + "failed = " + progressMsg.getFailed() + "\n");
            }
        }

        // 获取任务结果
        try {
            Message resultMsg = lc.lfasrGetResult(task_id);
            // 如果返回状态等于0,则获取任务结果成功
            if (resultMsg.getOk() == 0) {
                // 打印转写结果
            	String result = Test.getFinalResult(resultMsg.getData());
            	FileOutputStream f;
				try {
					f = new FileOutputStream(outputPath);
					f.write(result.getBytes());
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}            	
                asycExecPrint(DateUtils.getDataPrefix() + "结果存放路径: " + outputPath + "\n");
            } else {
                // 获取任务结果失败
                asycExecPrint(DateUtils.getDataPrefix() + "ecode = " + resultMsg.getErr_no() + "\n");
                asycExecPrint(DateUtils.getDataPrefix() + "failed = " + resultMsg.getFailed() + "\n");
                return -1;
            }
        } catch (LfasrException e) {
            // 获取结果异常处理,解析异常描述信息
            Message resultMsg = JSON.parseObject(e.getMessage(), Message.class);
            asycExecPrint(DateUtils.getDataPrefix() + "ecode=" + resultMsg.getErr_no() + "\n");
            asycExecPrint(DateUtils.getDataPrefix() + "failed=" + resultMsg.getFailed() + "\n");
            return -1;
        }
        asycExecSetStartThansferButtonStatus(true);
		return 1;
	}
} 

代码位置:https://github.com/ChenWenKaiVN/VoiceToText/chen0327

问题记录:

三月 27, 2019 7:58:51 下午 org.jaudiotagger.tag.id3.AbstractID3v2Frame readIdentifier
警告: 格式工厂20171225佟麟阁中学访谈2.mp3:No space to find another frame:
三月 27, 2019 7:58:51 下午 org.jaudiotagger.tag.id3.ID3v23Tag readFrames
警告: 格式工厂20171225佟麟阁中学访谈2.mp3:Invalid Frame:格式工厂20171225佟麟阁中学访谈2.mp3:No space to find another frame

注意JDK版本1.8

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值