Audio Unit詳解(一)實時變調(diào)音效處理

Audio Unit詳解

本篇博客有何不同

Audio Unit(以下稱AU)是iOS底層的音頻框架,對于進階開發(fā)者AU是必需掌握的框架之一,因為面向當下,掌握底層的音頻框架可以讓你與其他初級開發(fā)者區(qū)別開,如果面向未來损趋,隨著網(wǎng)絡帶寬的增加,音視頻技術的應用范圍一定會更廣椅寺,應用頻率也會更高舶沿。

我看了不少關于AU的技術博客,可能出于項目機密的原因配并,大多數(shù)只講原理括荡,而且只講某一個應用方向的原理,比如錄音溉旋、播放畸冲、錄音同時播放,對于api的講解也不夠全面,比如同樣實現(xiàn)錄音邑闲,大多數(shù)博客講的兩種不同方式算行,卻沒有說清楚原因。對于實現(xiàn)功能的代碼也不是很完整苫耸,大多都是從各自項目里面摘抄的部分代碼州邢,導致我們在實際使用的時候找不到完整的例子,今天這篇博客就是站在巨人的肩上褪子,從原理到demo統(tǒng)統(tǒng)給你講清楚量淌,希望對你有所幫助。

框架層級

1.1.png

從上圖可見嫌褪,AU處于距離硬件最近的底層呀枢,幾乎就是直接和硬件打交道了,所以如果使用這一層的api笼痛,你能得到最多的自由度和最低的延遲裙秋,但副作用就是最高的復雜度,這一層的很多api不是很直觀缨伊,也有的概念會出現(xiàn)重疊和歧義摘刑,再加上直接使用這層api的應用不多見,所以相關的資料比較少刻坊。
如果只是錄音或者播放音頻枷恕,完全沒有必要使用AU,直接使用AVKit或者Audio Queue簡單得多紧唱。
那么AU能實現(xiàn)哪些功能呢活尊?或者說什么樣的需求才犯得上我們直接啃AU的硬骨頭呢隶校?有這些

  • 低延時同步音頻輸入輸出漏益,例如 VoIP 應用
  • 響應回放合成聲音,例如音樂游戲或合成樂器
  • 使用特定的 audio unit 特征深胳,例如回聲消除绰疤,混音,色調(diào)均衡
  • 處理鏈結構讓你可以將音頻處理模塊組裝到靈活的網(wǎng)絡中舞终。這是 iOS 中唯一提供此功能的音頻 API轻庆。(這句話是從其他博客抄的,用人話說敛劝,就是你需要鏈式處理音頻單元時就會用到余爆,比如依次進行錄音-回聲消除-美音-混音-輸出到設備)

工作原理

這里用三個圖來舉例比較形象生動。

1夸盟、采集音頻-播放音頻

1.2.png

在AU中有三個基本的Element蛾方,分別是Element0、Element1和Global(下面會說),有的地方把Element叫做bus桩砰,就是總線拓春,很多關于Audio Unit的教程里面說的bus通常就是這玩意。

bus就是硬件管道在軟件上的抽象概念亚隅,在上圖中AU里面音頻數(shù)據(jù)的流動被抽象為從Element1流向Element0硼莽,即從硬件話筒到APP處理,再到硬件麥克風煮纵。scope表示在一個Element上輸入或輸出懂鸵。而APP能影響的范圍,就是從Element1的output scope到Element0的input scope醉途。


2矾瑰、多個音頻單元鏈式處理

1.3.jpeg

在AU中一個unit被稱為一個音頻處理單元,通常一種單元只能做一種固定的事情隘擎,比如連接硬件(remote i/o, VP i/o)殴穴、效果器(effect)、混音(mix)货葬、轉(zhuǎn)換器(Format)采幌,每種unit可能有一個或多個輸入,比如remote i/o只有一個輸入震桶, mix可有多個輸入休傍,但每種unit通常只有一個輸出。

多個unit可以并行或串行進行處理蹲姐,上圖中就是兩個效果器unit(EQ unit)的輸出連接到一個混音unit(Mixer unit)的輸入上磨取,最后輸出到硬件(I/O unit)。


3柴墩、音頻數(shù)據(jù)控制流

1.4.png

在實際處理音頻數(shù)據(jù)時忙厌,音頻數(shù)據(jù)雖然是按照順序在處理鏈中流動,但數(shù)據(jù)控制流卻是相反的江咳,有點像Cocoa Touch中事件傳遞鏈和響應鏈的關系逢净,這么說大概懂了吧。

舉個栗子歼指,考試的時候?qū)W霸坐在第一排爹土,后邊的都是學渣,最后一排的學渣想要小抄就去問前面一排的學渣踩身,前面一排的學渣說我也沒有胀茵,又去問再前一排的學渣,直到問到第一排的學霸挟阻,學霸才把答案寫好往后傳琼娘,每一排的學渣抄一遍答案后就把小抄往下傳呵哨,直到最后一名學渣得到答案。

真實的流程就是這樣轨奄,當啟動每個unit后孟害,每個unit都在等待獲取數(shù)據(jù),于是可以在回調(diào)函數(shù)中調(diào)用AudioUnitRender挪拟,來向上一級unit申請數(shù)據(jù)(上一級unit的回調(diào)函數(shù)就會響應)挨务,就算你不使用AudioUnitRender,系統(tǒng)也會根據(jù)Unit的連接順序pull上一個unit的輸出玉组。如果上一級也沒有谎柄,再向上一級申請。當?shù)谝患塽nit處理好數(shù)據(jù)后惯雳,就把數(shù)據(jù)從當前Unit的output輸出朝巫,這樣下一級unit就可以繼續(xù)處理了。


數(shù)字信號基礎知識

1石景、信號的編碼與解碼

編碼過程-信號的數(shù)字化

1.5.jpeg

<center>圖1.5</center>

信號的數(shù)字化就是將連續(xù)的模擬信號轉(zhuǎn)換成離散的數(shù)字信號劈猿, 一般需要完成采樣、量化和編碼三個步驟潮孽,如圖 1.5 所示揪荣。采樣是指用每隔一定時間間隔的信號樣本值序列來代替原來在時間上連續(xù)的信號。量化是用有限個幅度近似表示原來在時間上連續(xù)變化的幅度值往史,把模擬信號的連續(xù)幅度變?yōu)橛邢迶?shù)量仗颈、有一定時間間隔的離散值。編碼則是按照一定的規(guī)律椎例,把量化后的離散值用二進制數(shù)碼表示挨决。上述數(shù)字化的過程又稱為脈沖編碼調(diào)制(Pulse Code Modulation) ,通常由 A/D 轉(zhuǎn)換器來實現(xiàn)订歪。


解碼過程
音頻解碼及編碼的逆過程脖祈,通過使用與編碼方式對應的解碼器,對數(shù)字信號進行模擬化陌粹。

數(shù)字信號編解碼的特有難點在于壓縮算法撒犀,壓縮算法決定了帶寬的利用率福压、聲音的還原程度和延遲等掏秩,這里會根據(jù)具體的應用場景去靜態(tài)或動態(tài)地選擇不同的壓縮算法,即選擇不同的音頻格式荆姆。由于篇幅有限蒙幻,這里涵蓋的內(nèi)容又很多,等我研究清楚了再展開討論胆筒。


Audio Unit的基本使用方法

與AU相關的api通常有兩套邮破,一套是直接使用Audio Unit诈豌,另一套是使用AUGraph,但是AUGraph已經(jīng)被聲明為Deprecated抒和,目前主推的是AudioEngine矫渔,AudioEngine位于AVFoundation中,用起來像是被封裝過的AUGraph摧莽,本文僅討論Audio Unit的使用方法庙洼。

AU的使用步驟大概是這樣:

  • 創(chuàng)建需要的Unit
  • 給每個Unit設置對應的屬性,聲明每個Unit的output格式
  • 初始化Unit
  • 開啟Unit
  • 關閉Unit

AU包含的Unit有7種:

  1. Effect - iPod Equalizer 效果器镊辕,比如均衡油够、延遲、回響等
  2. Mixing - 3D Mixer 和OpenAl相關的混音
  3. Mixing - Multichannel Mixer 多路混音征懈,我們一般用這個
  4. I/O - Remote I/O 連接硬件的io
  5. I/O - Voice-Processing I/O 在硬件io的基礎上增加了回聲消除石咬、自動增益校正、語音質(zhì)量調(diào)整卖哎、靜音等功能
  6. I/O - Generic Output 脫離音頻硬件的io通道鬼悠,可以從文件中獲取音頻源
  7. Format conversion - Format Converter 格式轉(zhuǎn)換。注意了亏娜,變調(diào)timePitch是這個類型里面的子類

每種Unit可用的屬性都不同厦章,但是這些屬性的名字又都在一個Enum里面,用的時候要小心照藻,建議先了解清楚你需要的每種Unit的用法袜啃。


擼起袖子開始寫demo

接下來會先分析Audio Unit中的各種api,完整的Demo在最后面幸缕。

使用Audio Unit實現(xiàn)錄音耳返

使用AU進行錄音有兩種方式群发,一、直接使用Audio Unit发乔,二熟妓、使用AUGraphic連接音頻輸入輸出單元。這里我們分開講栏尚,但是他們有些共同的地方起愈。

一、直接使用Audio Unit

1.設置AudioSession

func setupAudioSession() {
        let session: AVAudioSession = AVAudioSession.sharedInstance()
        do {
            try session.setCategory(.playAndRecord, options: [.allowBluetooth, .allowBluetoothA2DP])
            try session.overrideOutputAudioPort(.none)
            try session.setPreferredSampleRate(Double(AudioConst.SampleRate))
            //每次處理的buffer大小
            try session.setPreferredIOBufferDuration(Double(AudioConst.BufferDuration) / 1000.0)
            try session.setActive(true, options: .notifyOthersOnDeactivation)
        } catch  {
            print(error.localizedDescription)
        }
    }

2.創(chuàng)建ioUnit

var ioDes: AudioComponentDescription = AudioComponentDescription.init(
            componentType: kAudioUnitType_Output,
            componentSubType: kAudioUnitSubType_RemoteIO,
            componentManufacturer: kAudioUnitManufacturer_Apple,
            componentFlags: 0,
            componentFlagsMask: 0)
        guard let inputComp: AudioComponent = AudioComponentFindNext(nil, &ioDes) else {
            print("outputComp init error")
            return false
        }
        if AudioComponentInstanceNew(inputComp, &ioUnit) != noErr {
            print("io AudioComponentInstanceNew error")
            return false
        }

3.設置ioUnit參數(shù)

//是否打開輸入译仗、輸出
var value: UInt32 = 1
        if AudioUnitSetProperty(self.ioUnit!, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, AudioConst.InputBus, &value, UInt32(MemoryLayout.size(ofValue: value))) != noErr {
            print("can't enable input io")
            return false
        }
        
        value = 1 //如果不需要從硬件輸出 就把value設置為0
        if AudioUnitSetProperty(self.ioUnit!, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, AudioConst.OutputBus, &value, UInt32(MemoryLayout.size(ofValue: value))) != noErr {
            print("can't enable output io")
            return false
        }
        
        //設置最大切片抬虽,就是連接兩個unit的管道有多粗,這個參數(shù)和第一步setPreferredIOBufferDuration的大小有關纵菌,太小的話會報錯阐污,最好設置大一些,
        var maxSlice: Int32 = 4096
        if AudioUnitSetProperty(self.ioUnit!, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, AudioConst.OutputBus, &maxSlice, UInt32(MemoryLayout.size(ofValue: maxSlice))) != noErr {
            print("set MaximumFramesPerSlice error")
            return false
        }
        
        //設置Unit輸出格式
        var ioFormat: AudioStreamBasicDescription = AudioStreamBasicDescription.init(
            mSampleRate: Float64(AudioConst.SampleRate),
            mFormatID: kAudioFormatLinearPCM,
            mFormatFlags: kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked,
            mBytesPerPacket:  UInt32(2 * AudioConst.Channels),
            mFramesPerPacket: 1,
            mBytesPerFrame: UInt32(2 * AudioConst.Channels),
            mChannelsPerFrame: UInt32(AudioConst.Channels),
            mBitsPerChannel: 16,
            mReserved: 0)
        if AudioUnitSetProperty(self.ioUnit!, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, AudioConst.InputBus, &ioFormat, UInt32(MemoryLayout.size(ofValue: ioFormat))) != noErr {
            print("set StreamFormat error")
            return false
        }

        if AudioUnitSetProperty(self.ioUnit!, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, AudioConst.OutputBus, &ioFormat, UInt32(MemoryLayout.size(ofValue: ioFormat))) != noErr {
            print("set StreamFormat error")
            return false
        }
        
        //設置回調(diào)咱圆,下一級unit取數(shù)據(jù)的時候回到這里來取笛辟,具體回調(diào)定義在demo里面
        if AudioUnitSetProperty(self.ioUnit!, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, AudioConst.OutputBus, &recordCallback, UInt32(MemoryLayout.size(ofValue: recordCallback))) != noErr {
            print("SetRenderCallback error")
            return false
        }

3.啟動功氨、關閉Unit

    //啟動
    public func startRecord() {
        
        var error = AudioUnitInitialize(self.ioUnit!)
        if error != noErr  {
            print("AudioUnitInitialize error: \(error)")
        }
        error = AudioOutputUnitStart(self.ioUnit!)
        if  error != noErr {
            print("AudioOutputUnitStart error")
        }

    }
    
    //關閉
    public func stopRecord() {
        AudioUnitUninitialize(self.ioUnit!)
        AudioOutputUnitStop(self.ioUnit!)
    }

tips:AU的每個api都會返回錯誤碼,如果遇到不是noErr手幢,即0的結果捷凄,可以到這里去查一下錯誤碼的定義,可以有效地幫助你排查問題原因

demo地址

二围来、使用AUGraphic實現(xiàn)錄音

1.設置AudioSession

func setupSession() {
        let session: AVAudioSession = AVAudioSession.sharedInstance()
        do {
            try session.setCategory(.playAndRecord, options: [.allowBluetooth, .allowBluetoothA2DP])
            try session.overrideOutputAudioPort(.none)
            try session.setPreferredSampleRate(Double(AudioConst.SampleRate))
            try session.setPreferredIOBufferDuration(Double(AudioConst.BufferDuration) / 1000.0)
            try session.setActive(true, options: .notifyOthersOnDeactivation)
        } catch  {
            print(error.localizedDescription)
        }
    }

2.獲取ioUnit

    var ioDes: AudioComponentDescription =      AudioComponentDescription.init(
            componentType: kAudioUnitType_Output,
            componentSubType: kAudioUnitSubType_RemoteIO,
            componentManufacturer: kAudioUnitManufacturer_Apple,
            componentFlags: 0,
            componentFlagsMask: 0)
        var status: OSStatus = noErr
        status = NewAUGraph(&process)
        if status != noErr {
            print("NewAUGraph error")
            return
        }
        
        status = AUGraphOpen(self.process!)
        if status != noErr {
            print("AUGraphOpen error")
            return
        }
        
            //獲取node
        var ioNode: AUNode = 0
        status = AUGraphAddNode(self.process!, &ioDes, &ioNode)
        if status != noErr {
            print("AUGraphAddNode error")
            return
        }
        
        //從node獲取unit引用
        AUGraphNodeInfo(self.process!, ioNode, &ioDes, &ioUnit)
      

2.設置unit參數(shù)纵势,同單獨使用Unit一樣

//是否打開輸入、輸出
var value: UInt32 = 1
        if AudioUnitSetProperty(self.ioUnit!, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, AudioConst.InputBus, &value, UInt32(MemoryLayout.size(ofValue: value))) != noErr {
            print("can't enable input io")
            return false
        }
        
        value = 1 //如果不需要從硬件輸出 就把value設置為0
        if AudioUnitSetProperty(self.ioUnit!, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, AudioConst.OutputBus, &value, UInt32(MemoryLayout.size(ofValue: value))) != noErr {
            print("can't enable output io")
            return false
        }
        
        //設置最大切片管钳,就是連接兩個unit的管道有多粗钦铁,這個參數(shù)和第一步setPreferredIOBufferDuration的大小有關,太小的話會報錯才漆,最好設置大一些牛曹,
        var maxSlice: Int32 = 4096
        if AudioUnitSetProperty(self.ioUnit!, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, AudioConst.OutputBus, &maxSlice, UInt32(MemoryLayout.size(ofValue: maxSlice))) != noErr {
            print("set MaximumFramesPerSlice error")
            return false
        }
        
        //設置Unit輸出格式
        var ioFormat: AudioStreamBasicDescription = AudioStreamBasicDescription.init(
            mSampleRate: Float64(AudioConst.SampleRate),
            mFormatID: kAudioFormatLinearPCM,
            mFormatFlags: kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked,
            mBytesPerPacket:  UInt32(2 * AudioConst.Channels),
            mFramesPerPacket: 1,
            mBytesPerFrame: UInt32(2 * AudioConst.Channels),
            mChannelsPerFrame: UInt32(AudioConst.Channels),
            mBitsPerChannel: 16,
            mReserved: 0)
        if AudioUnitSetProperty(self.ioUnit!, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, AudioConst.InputBus, &ioFormat, UInt32(MemoryLayout.size(ofValue: ioFormat))) != noErr {
            print("set StreamFormat error")
            return false
        }

        if AudioUnitSetProperty(self.ioUnit!, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, AudioConst.OutputBus, &ioFormat, UInt32(MemoryLayout.size(ofValue: ioFormat))) != noErr {
            print("set StreamFormat error")
            return false
        }
        
        //設置回調(diào),下一級unit取數(shù)據(jù)的時候回到這里來取醇滥,具體回調(diào)定義在demo里面
        if AudioUnitSetProperty(self.ioUnit!, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, AudioConst.OutputBus, &recordCallback, UInt32(MemoryLayout.size(ofValue: recordCallback))) != noErr {
            print("SetRenderCallback error")
            return false
        }

3.連接node

//連接node, 把ioNode的bus1 連接在ioNode的bus0黎比,即硬件的輸入連到硬件輸出上
        status = AUGraphConnectNodeInput(self.process!, ioNode, 1, ioNode, 0)
        if status != noErr {
            print("AUGraphConnectNodeInput error")
            return
        }
        
        //初始化AUGraphic流程,如果前面哪些步驟有問題鸳玩,這里也會報錯
        status = AUGraphInitialize(self.process!)
        if status != noErr {
            print("AUGraphInitialize error: \(status)")
        }

4.開啟和停止

public func start() {
    
        AUGraphStart(self.process!)
    }
    
    
    
    public func stop() {
        AUGraphStop(self.process!)
    }

使用Audio Unit實現(xiàn)實時變調(diào)

實現(xiàn)音頻變調(diào)有多種方式阅虫,這里只講通過Audio Unit中的timePitch來實現(xiàn)變調(diào)。

先講一個結論不跟,經(jīng)過我多種嘗試和國內(nèi)外論壇中摸爬滾打颓帝,始終沒能通過Audio Unit直接實現(xiàn)變調(diào),嘗試過程中要么是報錯要么沒有聲音窝革,最終是通過AUGraphic來實現(xiàn)實時變調(diào)功能购城。

以下是實現(xiàn)過程,這里只講重點哈虐译,因為大部分內(nèi)容和上述耳返過程相同瘪板。
1.獲取pitchUnit,并設置參數(shù)

//經(jīng)過多方嘗試漆诽,發(fā)現(xiàn)一旦使用了pitch這個unit侮攀,就不能設置ioUnit的輸入格式,所以需要把設置ioUnit輸入格式的地方注釋掉
//        if AudioUnitSetProperty(self.ioUnit!, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, AudioConst.InputBus, &ioFormat, UInt32(MemoryLayout.size(ofValue: ioFormat))) != noErr {
//            print("set StreamFormat error")
//            return
//        }

//如果需要獲取變調(diào)后的音頻數(shù)據(jù)厢拭,即設置了renderCallback兰英,
//使用pitchUnit的時候setPreferredIOBufferDuration的值要大一些,具體多大蚪腐,我設置了1s箭昵。發(fā)現(xiàn)超過200ms以后效果就有明顯改善
//如果沒有設置renderCallback税朴,setPreferredIOBufferDuration設置10ms就夠了回季,耳返效果完美家制。
//這里也是本項目未能完美解決的地方,如有更好的方案泡一,請與我分享颤殴。

var pitchDes: AudioComponentDescription = AudioComponentDescription.init(
            componentType: kAudioUnitType_FormatConverter,
            componentSubType: kAudioUnitSubType_NewTimePitch,
            componentManufacturer: kAudioUnitManufacturer_Apple,
            componentFlags: 0,
            componentFlagsMask: 0)
        
        var pitchNode: AUNode = 0
        status = AUGraphAddNode(self.process!, &pitchDes, &pitchNode)
        if status != noErr {
            print("AUGraphAddNode error")
            return
        }
        
        
        status = AUGraphNodeInfo(self.process!, pitchNode, &pitchDes, &pitchUnit)
        if status != noErr {
            print("AUGraphNodeInfo error")
            return
        }
        
        
        if AudioUnitSetProperty(self.pitchUnit!, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Global, AudioConst.OutputBus, &maxSlice, UInt32(MemoryLayout.size(ofValue: maxSlice))) != noErr {
            print("set MaximumFramesPerSlice error")
            return
        }

2.連接Unit

//連接node
        status = AUGraphConnectNodeInput(self.process!, ioNode, 1, pitchNode, 0)
        if status != noErr {
            print("AUGraphConnectNodeInput error")
            return
        }
        
        status = AUGraphConnectNodeInput(self.process!, pitchNode, 0, ioNode, 0)
        if status != noErr {
            print("AUGraphConnectNodeInput error")
            return
        }
        
        status = AUGraphInitialize(self.process!)
        if status != noErr {
            print("AUGraphInitialize error: \(status)")
        }

3.啟動和停止

    public func start() {
    
        AUGraphStart(self.process!)
    }
    
    
    
    public func stop() {
        AUGraphStop(self.process!)
    }

4.設置變調(diào)參數(shù)

public func setPitch(pValue: Float) {
        
        //取值范圍 -2000 ~ 2000
        var value: Float32 = Float32((pValue - 0.5) * 2 * 2000)
        if AudioUnitSetParameter(self.pitchUnit!, kNewTimePitchParam_Pitch, kAudioUnitScope_Global, AudioConst.OutputBus, AudioUnitParameterValue(value), 0) != noErr {
            print("set kNewTimePitchParam_Pitch error")
        }
    }

項目demo地址


引用文摘

https://blog.csdn.net/alwaysrun/article/details/108476785
https://www.cnblogs.com/wangyaoguo/p/8392660.html
https://blog.csdn.net/chenhande1990chenhan/article/details/78770452

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者鼻忠。
  • 序言:七十年代末涵但,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子帖蔓,更是在濱河造成了極大的恐慌矮瘟,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件塑娇,死亡現(xiàn)場離奇詭異澈侠,居然都是意外死亡,警方通過查閱死者的電腦和手機埋酬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門哨啃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人写妥,你說我怎么就攤上這事拳球。” “怎么了珍特?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵祝峻,是天一觀的道長。 經(jīng)常有香客問我扎筒,道長呼猪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任砸琅,我火速辦了婚禮宋距,結果婚禮上,老公的妹妹穿的比我還像新娘症脂。我一直安慰自己谚赎,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布诱篷。 她就那樣靜靜地躺著壶唤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪棕所。 梳的紋絲不亂的頭發(fā)上闸盔,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音琳省,去河邊找鬼迎吵。 笑死躲撰,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的击费。 我是一名探鬼主播拢蛋,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蔫巩!你這毒婦竟也來了谆棱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤圆仔,失蹤者是張志新(化名)和其女友劉穎垃瞧,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體坪郭,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡皆警,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了截粗。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片信姓。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖绸罗,靈堂內(nèi)的尸體忽然破棺而出意推,到底是詐尸還是另有隱情,我是刑警寧澤珊蟀,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布菊值,位于F島的核電站,受9級特大地震影響育灸,放射性物質(zhì)發(fā)生泄漏腻窒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一磅崭、第九天 我趴在偏房一處隱蔽的房頂上張望儿子。 院中可真熱鬧,春花似錦砸喻、人聲如沸柔逼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽愉适。三九已至,卻和暖如春癣漆,著一層夾襖步出監(jiān)牢的瞬間维咸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留癌蓖,地道東北人瞬哼。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像费坊,于是被迫代替她去往敵國和親倒槐。 傳聞我的和親對象是個殘疾皇子旬痹,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容