swift OSX:使用GCD

时间:2016-02-25 08:57:29

标签: swift macos grand-central-dispatch

我正在尝试使用NSSpeechSynthesizer.startSpeakingString()生成.aiff文件,并使用GCd使用串行队列,因为NSSpeechSynthesizer接受一个字符串并在指定的NSURL地址创建一个aiff文件。我使用标准for循环方法获取[String:[String]]中的字符串列表,但这会创建一些具有0字节的文件。

以下是生成语音的功能:

    func createSpeech(type: String, name: String) {
        if !NSFileManager.defaultManager().fileExistsAtPath("\(dataPath)\(type)/\(name)/\(name).aiff"){
            do{
             try NSFileManager().createDirectoryAtPath("\(dataPath)\(type)/\(name)/", withIntermediateDirectories: true, attributes:  nil)
let URL = NSURL(fileURLWithPath: "\(dataPath)\(type)/\(name)/\(name).aiff")
                            print("Attempting to save speech \(name).aiff")
self.synth.startSpeakingString(name, toURL: URL)
            }catch{
                print("error occured")
            }
        }
    }

这是遍历字典以创建文件的函数:

        for key in self.nodeLibrary.keys{
                                dispatch_sync(GlobalBackgroundQueue){
                let type = self.nodeLibrary[key]?.0
            let name = key.componentsSeparatedByString("_")[0]
                                    if !speechCheck.contains(name){
mixer.createSpeech(type!, name: name)
                                    }
            }
        }

为了便于阅读,globalBackgroundQueue是GCD队列调用_T的别名。

例程运行正常,根据另一个外部函数的需要创建文件夹和子文件夹然后合成语音但在我的情况下我总是得到一个或一些不能正确加载,给出0个字节或太少的字节数使文件无法使用。

我阅读了以下帖子,并且已经使用了这些GCD方法一段时间但我不确定我在哪里错了:

http://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1

任何帮助都像往常一样非常受欢迎

编辑:更新完成时关闭并发现可能存在错误

我已经创建了一个如下所示的闭包函数,并在另一个帮助器方法中使用它,它在加载后检查任何错误,例如sourceFile.length为0。但是,所有文件都显示0长度,这是不可能的,因为我使用finder的属性命令+ i检查了每个文件的音频属性。

func synthesise(type: String, name: String, completion: (success: Bool)->()) {
        if !NSFileManager.defaultManager().fileExistsAtPath("\(dataPath)\(type)/\(name)/\(name).aiff"){
            do{
             try NSFileManager().createDirectoryAtPath("\(dataPath)\(type)/\(name)/", withIntermediateDirectories: true, attributes:  nil)
                let URL = NSURL(fileURLWithPath: "\(dataPath)\(type)/\(name)/\(name).aiff")
                let success = self.synth.startSpeakingString(name, toURL: URL)
                completion(success: success)
            }catch{
                print("error occured")
            }
        }
    }

    func loadSpeech(type: String, name: String){
            synthesise(type, name: name, completion: {(success: Bool)->Void in
                if success{
    print("File \(name) created successfully with return \(self.synthSuccess), checking file integrity")
                    let URL = NSURL(fileURLWithPath: "\(self.dataPath)\(type)/\(name)/\(name).aiff")
                    do{
                    let source = try AVAudioFile(forReading: URL)
                        print("File has length: \(source.)")
                    }catch{
                        print("error loading file")
                    }
                }else{
                    print("creation unsuccessful, trying again")
    self.loadSpeech(type, name: name)
                }
            })
        }

文件是用它们的文件夹生成的,方法是startSpeakingString-> Bool和我在我的类中的委托函数,它更新了synthSuccess属性show true。所以我加载一个AVAudioFile来检查它的长度。所有文件长度都是0.除了一个之外,它们不是。

当我说bug时,这是来自应用程序的另一部分,我加载AVAudioEngine并开始加载缓冲区,其中frameCount参数设置为sourceAudioFile.length,这会产生诊断错误,但现在这已经脱离了上下文。

2 个答案:

答案 0 :(得分:1)

在上面的代码中,检查调用synth.startSpeakingString(name, toURL: URL)的结果,如果合成器无法开始讲话,则可以返回false。如果失败,找出原因,或者只是重试它。

另外,添加[NSSpeechSynthesiserDelegate][1],然后在那里查找speechSynthesizer:didFinishSpeaking:个回调。当合成器认为它已完成说话时,检查文件大小。如果为零,请重试该操作。

答案 1 :(得分:1)

startSpeakingString(_:toURL:)将在后台启动异步任务。实际上,您的代码会启动许多同时运行的异步任务。这可能是您遇到问题的原因。

解决方案需要确保一次只有一个任务处于活动状态。

startSpeakingString(_:toURL:)的问题是它启动了一个异步任务 - 但是本身函数无法在此任务完成时获得通知。

但是,您需要设置委托才能收到通知。

因此,您的解决方案需要定义NSSpeechSynthesizerDelegate

您可能希望创建自己的助手类,该类公开具有完成处理程序的异步函数:

func exportSpeakingString(string: String, url: NSURL, 
    completion: (NSURL?, ErrorType?) -> ())

在内部,该类创建了NSSpeechSynthesizer NSSpeechSynthesizerDelegate的实例,并相应地实现了委托方法。

要完成挑战,您需要搜索一种方法来按顺序运行多个异步函数。 SO上已有解决方案。

修改

我设置了自己的项目,以确认或忽略NSSpeechSynthesizer系统框架中的可能问题。到目前为止,可能自己的测试确认NSSpeechSynthesizer按预期工作。

然而,值得一提的微妙之处:

  1. 确保您创建一个有效的文件网址,并将其作为参数传递给方法URL中的参数startSpeakingString(:toURL:)

  2. 确保为NSSpeechSynthesizer已知的输出文件和播放此文件的系统框架选择扩展名,例如.aiff。不幸的是,这里的文档非常缺乏 - 所以我不得不试错。 QuickTime支持的音频文件格式列表可能会有所帮助。不过,我不知道NSSpeechSynthesizer如何选择输出格式。

  3. 以下两个类构成了一个易于使用的简单库:

    import Foundation
    import AppKit
    
    
    enum SpeechSynthesizerError: ErrorType {
        case ErrorActive
        case ErrorURL(message: String)
        case ErrorUnknown
    }
    
    internal class InternalSpeechSynthesizer: NSObject, NSSpeechSynthesizerDelegate {
    
        typealias CompletionFunc = (NSURL?, ErrorType?) -> ()
    
        private let synthesizer = NSSpeechSynthesizer(voice: nil)!
        private var _completion: CompletionFunc?
        private var _url: NSURL?
    
        override init() {
            super.init()
            synthesizer.delegate = self
        }
    
        // CAUTION: This call is not thread-safe! Ensure that multiple method invocations
        // will be called from the same thread!
        // Only _one_ task can be active at a time.
        internal func synthesize(input: String, output: NSURL, completion: CompletionFunc) {
            guard _completion == nil else {
                dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
                    completion(nil, SpeechSynthesizerError.ErrorActive)
                }
                return
            }
            guard output.path != nil else {
                dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
                    completion(nil, SpeechSynthesizerError.ErrorURL(message: "The URL must be a valid file URL."))
                }
                return
            }
            _completion = completion
            _url = output
            if !synthesizer.startSpeakingString(input, toURL: output) {
                fatalError("Could not start speeaking")
            }
        }
    
    
        internal func speechSynthesizer(sender: NSSpeechSynthesizer,
                                        willSpeakWord characterRange: NSRange,
                                        ofString string: String)
        {
            NSLog("willSpeakWord")
        }
    
        internal func speechSynthesizer(sender: NSSpeechSynthesizer,
                                        willSpeakPhoneme phonemeOpcode: Int16)
        {
            NSLog("willSpeakPhoneme")
        }
    
        internal func speechSynthesizer(sender: NSSpeechSynthesizer,
                                        didEncounterErrorAtIndex characterIndex: Int,
                                        ofString string: String,
                                        message: String)
        {
            NSLog("didEncounterErrorAtIndex")
        }
    
        internal func speechSynthesizer(sender: NSSpeechSynthesizer,
                                        didFinishSpeaking finishedSpeaking: Bool)
        {
            assert(self._url != nil)
            assert(self._url!.path != nil)
            assert(self._completion != nil)
            var error: ErrorType?            
            if !finishedSpeaking {
                do {
                    error = try self.synthesizer.objectForProperty(NSSpeechErrorsProperty) as? NSError
                } catch let err {
                    error = err
                }
            } 
            let url: NSURL? = NSFileManager.defaultManager().fileExistsAtPath(self._url!.path!) ? self._url : nil        
            let completion = self._completion!
            dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
                if url == nil && error == nil {
                    error = SpeechSynthesizerError.ErrorUnknown
                }
                completion(url, error)
            }
            _completion = nil
            _url = nil
        }
    
    }
    
    
    public struct SpeechSynthesizer {
        public init() {}    
        private let _synthesizer = InternalSpeechSynthesizer()
    
        public func synthesize(input: String, output: NSURL, completion: (NSURL?, ErrorType?) -> ()) {
            _synthesizer.synthesize(input, output: output) { (url, error) in
                completion(url, error)
            }
        }
    }
    

    您可以使用它,如下所示:

    func testExample() {
        let expect = self.expectationWithDescription("future should be fulfilled")
    
        let synth = SpeechSynthesizer()
        let url = NSURL(fileURLWithPath: "/Users/me/Documents/speech.aiff")
    
        synth.synthesize("Hello World!", output: url) { (url, error) in
            if let url = url {
                print("URL: \(url)")
            }
            if let error = error {
                print("Error: \(error)")
            }
            expect.fulfill()
        }  
    
    
        self.waitForExpectationsWithTimeout(1000, handler: nil)
        // Test: output file should exist.
    }