iOS音頻系列(二)--CoreAudio

這一篇主要是CoreAudio官方文檔的重點(diǎn)內(nèi)容的筆記甥啄。

通過回調(diào)函數(shù)與CoreAudio交互

iOS的CoreAudio是通過callback函數(shù)與App交互的灰粮。其中需要設(shè)置回調(diào)函數(shù)有以下幾種情況:

  • CoreAudio會(huì)向回調(diào)函數(shù)給App傳入PCM音頻數(shù)據(jù)隶校,然后App需要在回調(diào)函數(shù)中將音頻數(shù)據(jù)寫入文件文件系統(tǒng)琼蚯。(錄音時(shí)候)
  • CoreAudio會(huì)需要向App請(qǐng)求一些音頻數(shù)據(jù),App通過從文件系統(tǒng)中讀取音頻數(shù)據(jù)惠况,然后通過callback函數(shù)傳遞給CoreAudio遭庶。(播放時(shí)候)
  • 通過注冊(cè)屬性觀察者,監(jiān)聽CoreAudio的屬性稠屠,注冊(cè)回調(diào)函數(shù)

下面是一個(gè)使用Audio Queue Services的屬性監(jiān)聽器的callback函數(shù)的調(diào)用模板峦睡。

typedef void (*AudioQueuePropertyListenerProc) (
                void *                  inUserData,
                AudioQueueRef           inAQ,
                AudioQueuePropertyID    inID
            );

在實(shí)現(xiàn)和使用這個(gè)callback函數(shù)時(shí)候,你需要完成兩件事:

  • 實(shí)現(xiàn)這個(gè)函數(shù)权埠。例如榨了,你可以實(shí)現(xiàn)property listener callback,根據(jù)audio是否在running或者stop狀態(tài)攘蔽,去改變更新UI龙屉。
  • 注冊(cè)callback函數(shù)時(shí)候帶上userData數(shù)據(jù),在callback函數(shù)觸發(fā)時(shí)候使用满俗。

下面是一個(gè)property listener callback函數(shù)的實(shí)現(xiàn):

static void propertyListenerCallback (
    void                    *inUserData,
    AudioQueueRef           queueObject,
    AudioQueuePropertyID    propertyID
) {
    AudioPlayer *player = (AudioPlayer *) inUserData;
        // gets a reference to the playback object
    [player.notificationDelegate updateUserInterfaceOnAudioQueueStateChange: player];
        // your notificationDelegate class implements the UI update method
}

下面是注冊(cè)一個(gè)callback函數(shù)的例子:

AudioQueueAddPropertyListener (
    self.queueObject,                // the object that will invoke your callback
    kAudioQueueProperty_IsRunning,   // the ID of the property you want to listen for
    propertyListenerCallback,        // a reference to your callback function
    self
);

Audio Data Formats

這部分內(nèi)容是iOS中支持的音頻格式转捕,理解以后對(duì)后面的音頻相關(guān)的編程能夠理解更加深刻。

iOS中通用的音頻數(shù)據(jù)類型

在CoreAudio中唆垃,使用AudioStreamBasicDescriptionAudioStreamPacketDescription這兩個(gè)類型描述了通用的音頻數(shù)據(jù)類型,包括壓縮音頻數(shù)據(jù)五芝,非壓縮的音頻數(shù)據(jù)。他們的數(shù)據(jù)結(jié)構(gòu)如下:

struct AudioStreamBasicDescription {
    Float64 mSampleRate;
    UInt32  mFormatID;
    UInt32  mFormatFlags;
    UInt32  mBytesPerPacket;
    UInt32  mFramesPerPacket;
    UInt32  mBytesPerFrame;
    UInt32  mChannelsPerFrame;
    UInt32  mBitsPerChannel;
    UInt32  mReserved;              //0
};
typedef struct AudioStreamBasicDescription  AudioStreamBasicDescription;

struct  AudioStreamPacketDescription {
    SInt64  mStartOffset;
    UInt32  mVariableFramesInPacket;
    UInt32  mDataByteSize;
};
typedef struct AudioStreamPacketDescription AudioStreamPacketDescription;

compressed audio formats use a varying number of bits per sample. For these formats, the value of the mBitsPerChannel member is 0.

Audio Data Packets

前面定義過辕万,將一個(gè)或者多個(gè)frames稱為一個(gè)packet枢步。或者說packet是最有意義的一組frames渐尿,它在audio file中代表一個(gè)有意義的時(shí)間單元醉途。使用Core Audio中一般是對(duì)packets進(jìn)行處理的。

每個(gè)audio data格式在packets被裝配完成以后砖茸,它的fromat就被確定了隘擎。ASBD數(shù)據(jù)結(jié)構(gòu)通過mBytesPerPacketmFramesPerPacket描述音頻格式的packet信息,其中頁包含其他的信息渔彰。

在整個(gè)Core Audio中可能會(huì)用到三種不同的packets:

  • CBR (constant bit rate) formats:例如 linear PCM and IMA/ADPCM嵌屎,所有的packet使用相同的大小推正。
  • VBR (variable bit rate) formats:例如 AAC恍涂,Apple Lossless,MP3植榕,所有的packets擁有相同的frames再沧,但是每個(gè)sample中的bits數(shù)目不同。
  • VFR (variable frame rate) formats:packets擁有數(shù)目不同的的frames尊残。

在Core Audio中使用VBR或者VFR格式炒瘸,使用ASPD結(jié)構(gòu)體只能用來描述單個(gè)packet淤堵。如果是record或者play VBR或者VFR音頻文件,需要涉及到多個(gè)ASPD結(jié)構(gòu)顷扩。

在AudioFileService等接口中拐邪,都是通過pakcets工作的。例如AudioFileReadPackets會(huì)得到一系列的packets隘截,同時(shí)會(huì)得到一個(gè)數(shù)組的AudioStreamPacketDescription扎阶。

下面是通過packets計(jì)算audio data buffer的大小:

- (void) calculateSizesFor: (Float64) seconds {
 
    UInt32 maxPacketSize;
    UInt32 propertySize = sizeof (maxPacketSize);
 
    AudioFileGetProperty (
        audioFileID,
        kAudioFilePropertyPacketSizeUpperBound,
        &propertySize,
        &maxPacketSize
    );
 
    static const int maxBufferSize = 0x10000;   // limit maximum size to 64K
    static const int minBufferSize = 0x4000;    // limit minimum size to 16K
 
    if (audioFormat.mFramesPerPacket) {
        Float64 numPacketsForTime =
            audioFormat.mSampleRate / audioFormat.mFramesPerPacket * seconds;
        [self setBufferByteSize: numPacketsForTime * maxPacketSize];
    } else {
        // if frames per packet is zero, then the codec doesn't know the
        // relationship between packets and time. Return a default buffer size
        [self setBufferByteSize:
            maxBufferSize > maxPacketSize ? maxBufferSize : maxPacketSize];
    }
 
    // clamp buffer size to our specified range
    if (bufferByteSize > maxBufferSize && bufferByteSize > maxPacketSize) {
        [self setBufferByteSize: maxBufferSize];
    } else {
        if (bufferByteSize < minBufferSize) {
            [self setBufferByteSize: minBufferSize];
        }
    }
 
    [self setNumPacketsToRead: self.bufferByteSize / maxPacketSize];
}

Data Format Conversion

將音頻數(shù)據(jù)從一種audio data轉(zhuǎn)換成另外一種audio data婶芭。常見的有三中音頻格式轉(zhuǎn)換:

  • Decoding an audio format (such as AAC (Advanced Audio Coding)) to linear PCM format.
  • Converting linear PCM data into a different audio format.
  • Converting between different variants of linear PCM (for example, converting 16-bit signed integer linear PCM to 8.24 fixed-point linear PCM).

Sound Files

如果要使用聲音文件东臀,你需要使用Audio File Services的接口。一般而且犀农,在iOS中需要與音頻文件的創(chuàng)建惰赋,操作都離不開Audio File Services。

使用AudioFileGetGlobalInfoSizeAudioFileGetGlobalInfo分別分配info的內(nèi)存和獲取info的內(nèi)容呵哨。你可以獲取以下的內(nèi)容:

  • Readable file types
  • Writable file types
  • For each writable type, the audio data formats you can put into the file

Creating a New Sound File

為了創(chuàng)建一個(gè)能夠存儲(chǔ)音頻數(shù)據(jù)的audio file赁濒,你需要進(jìn)行以下三步:

  • 使用CFURL或者NSURL表示的系統(tǒng)文件的路徑
  • 你需要?jiǎng)?chuàng)建的文件的類型的標(biāo)識(shí)identifier,這些identifier定義在Audio File Types枚舉中孟害。例如流部,為了創(chuàng)建一個(gè)CAF文件,你需要使用kAudioFileCAFType的identifier纹坐。
  • 創(chuàng)建過程中你需要提供音頻數(shù)據(jù)的ASBD結(jié)構(gòu)體枝冀。為了獲取ASBD,你可以先提供ASBD結(jié)構(gòu)體的部分成員的值耘子,然后通過函數(shù)讓Audio File Services將剩余的信息填滿果漾。

下面是創(chuàng)建一個(gè)AudioFile的方法:

AudioFileCreateWithURL (
    audioFileURL,
    kAudioFileCAFType,
    &audioFormat,
    kAudioFileFlags_EraseFile,
    &audioFileID   // the function provides the new file object here
);

Opening a Sound File

為了打開sound file,需要使用AudioFileOpenURL函數(shù)谷誓,該函數(shù)會(huì)返回一個(gè)唯一ID绒障,供后面使用。

為了獲取sound file的一些屬性捍歪,通常使用AudioFileGetPropertyInfoAudioFileGetProperty户辱,日常使用的屬性以下:

  • kAudioFilePropertyFileFormat
  • kAudioFilePropertyDataFormat
  • kAudioFilePropertyMagicCookieData
  • kAudioFilePropertyChannelLayout

Reading From and Writing To a Sound File

iOS中,我們經(jīng)常需要使用Audio File Services去讀寫audio data到sound file中糙臼。讀和寫是一對(duì)相反的內(nèi)容庐镐,操作的對(duì)象都可以是bytes或者packets,但是一般而言都是直接使用的packets变逃。

  • 讀寫VBR數(shù)據(jù)必逆,只能使用packet
  • 直接使用packet,更加容易計(jì)算時(shí)間

iPhone Audio File Formats

iOS支持的sound file格式如下:

Format name Format filename extensions
AIFF .aif, .aiff
CAF .caf
MPEG-1, layer 3 .mp3
MPEG-2 or MPEG-4 ADTS .aac
MPEG-4 .m4a, .mp4
WAV .wav

iOS中的native format是CAF file format。


Audio Sessions: Cooperating with Core Audio

在iOS中名眉,app在運(yùn)行過程中有可能接到電話粟矿,如果此時(shí)正在播放sound,系統(tǒng)會(huì)做一定的處理损拢。

AudioSession就是在這種情況的中間人陌粹,每個(gè)app都會(huì)有一個(gè)audio session。在播放或者錄音時(shí)候需要session在做正確的事情福压,需要我們自己弄清楚以下的情況:

  • app收到系統(tǒng)的中斷時(shí)候應(yīng)該如何響應(yīng)申屹,比如收到phone call?
  • 你是否需要app的sound和其他后臺(tái)運(yùn)行的app的sounds混合播放隧膏,或者需要獨(dú)占播放哗讥?
  • 你需要app如何響應(yīng)遠(yuǎn)音頻路徑的響應(yīng),比如拔插耳機(jī)時(shí)候

AudioSession提供了三種類型的接口:

  • Categories: A category is a key that identifies a set of audio behaviors for your application. By setting a category, you indicate your audio intentions to iOS, such as whether your audio should continue when the screen locks.
  • Interruptions and route changes :Your audio session posts notifications when your audio is interrupted, when an interruption ends, and when the hardware audio route changes. These notifications let you respond to changes in the larger audio environment—such as an interruption due to in an incoming phone call—gracefully.
  • Hardware characteristics:You can query the audio session to discover characteristics of the device your application is running on, such as hardware sample rate, number of hardware channels, and whether audio input is available.

Audio Session Default Behavior

Audio Session擁有一些默認(rèn)的行為策略:

  • 當(dāng)用戶將靜音開關(guān)靜音時(shí)胞枕,audio就會(huì)靜音杆煞。
  • 當(dāng)用戶鎖屏(手動(dòng),自動(dòng))時(shí)候腐泻,audio就會(huì)靜音决乎。
  • 當(dāng)你app的audio啟動(dòng)時(shí),其他app正在使用的audio就會(huì)靜音派桩。

audio session的這個(gè)特定的默認(rèn)的行為策略被稱為kAudioSessionCategory_SoloAmbientSound构诚。同時(shí),它還包括其他的多種策略選擇铆惑。

Interruptions: Deactivation and Activation

默認(rèn)的audio session的一個(gè)典型的特征是范嘱,audio會(huì)在中斷以后自動(dòng)恢復(fù)活動(dòng)。Audio session有兩個(gè)重要的狀態(tài):activeinactive员魏。只有當(dāng)Audio session處于active狀態(tài)時(shí)候丑蛤,app才能使用audio。

在app啟動(dòng)以后撕阎,你的默認(rèn)的audio session就會(huì)是active狀態(tài)受裹。然而,如果一個(gè)電話被打進(jìn)來虏束,你的session就會(huì)立刻被置為inactive棉饶,然后app中的audio就會(huì)停止。這個(gè)電話就被稱為一個(gè)中斷镇匀,如果用戶選擇忽略電話照藻,app就會(huì)繼續(xù)運(yùn)行。但是此時(shí)你的audio session依然會(huì)是inactive狀態(tài)坑律,audio也就不會(huì)工作岩梳。

如果你使用 Audio Queue Services操作audio,我們就需要給中斷注冊(cè)listener回調(diào)函數(shù)晃择,手動(dòng)去重啟audio session冀值。具體內(nèi)容可以見Audio Session Programming Guide

Determining if Audio Input is Available

一個(gè)錄音的app只有在設(shè)備的音頻硬件可用的時(shí)候才能錄音宫屠。為了檢查這個(gè)屬性列疗,需要使用audio session的kAudioSessionProperty_AudioInputAvailable屬性。

UInt32 audioInputIsAvailable;
UInt32 propertySize = sizeof (audioInputIsAvailable);
 
AudioSessionGetProperty (
    kAudioSessionProperty_AudioInputAvailable,
    &propertySize,
    &audioInputIsAvailable // A nonzero value on output means that
                           // audio input is available
);

Using Your Audio Session

你的app同時(shí)只能有一個(gè)audio session策略浪蹂,你的所有的audio都需要遵循這個(gè)active策略的特點(diǎn)抵栈。如何響應(yīng)中斷,在Audio Session Programming Guide`中有更加詳細(xì)的內(nèi)容坤次。

如果要測(cè)試Audio session古劲,需要使用真機(jī)


Playback using the AVAudioPlayer Class

AVAudioPlayer提供了簡單的OC接口用于audio播放。如果非網(wǎng)絡(luò)stream缰猴,或者需要精確控制产艾,apple推薦使用這個(gè)類,它可以用于播放iOS支持的任何audio format滑绒,同時(shí)這個(gè)類并不需要去設(shè)置audio session闷堡,因?yàn)樗鼤?huì)在中斷發(fā)生以后自動(dòng)恢復(fù)播放,除非你需要指定特地的行為疑故。

它可以完成以下工作:

  • Play sounds of any duration
  • Play sounds from files or memory buffers
  • Loop sounds
  • Play multiple sounds simultaneously
  • Control relative playback level for each sound you are playing
  • Seek to a particular point in a sound file, which supports such application features as fast forward and rewind
  • Obtain data that you can use for audio level metering

下面就是使用AVAudioPlayer的具體流程:

  1. Configuring an AVAudioPlayer object
NSString *soundFilePath =
                [[NSBundle mainBundle] pathForResource: @"sound"
                                                ofType: @"wav"];
 
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath: soundFilePath];
 
AVAudioPlayer *newPlayer =
                [[AVAudioPlayer alloc] initWithContentsOfURL: fileURL
                                                       error: nil];
[fileURL release];
 
self.player = newPlayer;
[newPlayer release];
 
[self.player prepareToPlay];
[self.player setDelegate: self];

你的delegate對(duì)象用于處理interruptions或者音頻播放停止以后的操作杠览。

  1. Implementing an AVAudioPlayer delegate method
- (void) audioPlayerDidFinishPlaying: (AVAudioPlayer *) player
                        successfully: (BOOL) flag {
    if (flag == YES) {
        [self.button setTitle: @"Play" forState: UIControlStateNormal];
    }
}
  1. Controlling an AVAudioPlayer object
- (IBAction) playOrPause: (id) sender {
 
    // if already playing, then pause
    if (self.player.playing) {
        [self.button setTitle: @"Play" forState: UIControlStateHighlighted];
        [self.button setTitle: @"Play" forState: UIControlStateNormal];
        [self.player pause];
 
    // if stopped or paused, start playing
    } else {
        [self.button setTitle: @"Pause" forState: UIControlStateHighlighted];
        [self.button setTitle: @"Pause" forState: UIControlStateNormal];
        [self.player play];
    }
}

Recording and Playback using Audio Queue Services

Audio Queue Services是一個(gè)更加直觀的record和play audio的方式。同時(shí)它還有更多個(gè)高級(jí)功能纵势,可以使用這個(gè)服務(wù)完成更多的工作踱阿,比如對(duì)LPCM數(shù)據(jù)進(jìn)行壓縮等等。它和AVAudioPlayer是iOS中唯二可以播放壓縮后音頻格式的接口钦铁。使用Audio Queue Service播放和錄音都是通過回調(diào)方法完成的扫茅。

Creating an Audio Queue Object

為了創(chuàng)建Audio Queue對(duì)象,它分成兩類:

  • AudioQueueNewInput用于錄音
  • AudioQueueNewOutput用于播放

使用audio queue object播放audio file育瓜,需要一些幾個(gè)步驟:

  1. 創(chuàng)建數(shù)據(jù)結(jié)構(gòu)用于管理audio queue需要的信息葫隙,例如audio format,audio fileID等躏仇。
  2. 定義callback函數(shù)恋脚,用于管理audio queue buffers。callback會(huì)使用Audio File Service去讀取audio file用來播放
  3. 使用AudioQueueNewOutput用來播放audio file焰手。

Creating an audio queue object具體的代碼如下:

static const int kNumberBuffers = 3;
// Create a data structure to manage information needed by the audio queue
struct myAQStruct {
    AudioFileID                     mAudioFile;
    CAStreamBasicDescription        mDataFormat;
    AudioQueueRef                   mQueue;
    AudioQueueBufferRef             mBuffers[kNumberBuffers];
    SInt64                          mCurrentPacket;
    UInt32                          mNumPacketsToRead;
    AudioStreamPacketDescription    *mPacketDescs;
    bool                            mDone;
};
// Define a playback audio queue callback function
static void AQTestBufferCallback(
    void                   *inUserData,
    AudioQueueRef          inAQ,
    AudioQueueBufferRef    inCompleteAQBuffer
) {
    myAQStruct *myInfo = (myAQStruct *)inUserData;
    if (myInfo->mDone) return;
    UInt32 numBytes;
    UInt32 nPackets = myInfo->mNumPacketsToRead;
 
    AudioFileReadPackets (
        myInfo->mAudioFile,
        false,
        &numBytes,
        myInfo->mPacketDescs,
        myInfo->mCurrentPacket,
        &nPackets,
        inCompleteAQBuffer->mAudioData
    );
    if (nPackets > 0) {
        inCompleteAQBuffer->mAudioDataByteSize = numBytes;
        AudioQueueEnqueueBuffer (
            inAQ,
            inCompleteAQBuffer,
            (myInfo->mPacketDescs ? nPackets : 0),
            myInfo->mPacketDescs
        );
        myInfo->mCurrentPacket += nPackets;
    } else {
        AudioQueueStop (
            myInfo->mQueue,
            false
        );
        myInfo->mDone = true;
    }
}
// Instantiate an audio queue object
AudioQueueNewOutput (
    &myInfo.mDataFormat,
    AQTestBufferCallback,
    &myInfo,
    CFRunLoopGetCurrent(),
    kCFRunLoopCommonModes,
    0,
    &myInfo.mQueue
);

Controlling Audio Queue Playback Level

Audio Queue對(duì)象提供了兩個(gè)方式控制音頻level糟描。第一種是直接使用AudioQueueSetParameter以及kAudioQueueParam_Volume參數(shù),就可以設(shè)置书妻,設(shè)置完成以后會(huì)立即生效船响。

Float32 volume = 1;
AudioQueueSetParameter (
    myAQstruct.audioQueueObject,
    kAudioQueueParam_Volume,
    volume
);

也可以通過AudioQueueEnqueueBufferWithParameters給audio queue buffer設(shè)置。這種方式只有在audio queue buffer 開始播放時(shí)候才起作用。

Indicating Audio Queue Playback Level

你也可以直接查詢audio queue的kAudioQueueProperty_CurrentLevelMeterDB屬性见间,得到的值是一組AudioQueueLevelMeterState結(jié)構(gòu)體(一個(gè)channel一個(gè)數(shù)組)聊闯,具體的結(jié)構(gòu)體是AudioQueueLevelMeterState,顯示如下:

typedef struct AudioQueueLevelMeterState {
    Float32     mAveragePower;
    Float32     mPeakPower;
};  AudioQueueLevelMeterState;

System Sounds: Alerts and Sound Effects

如果你需要播放的音頻時(shí)間少于30s米诉,那么可以使用System Sound Services菱蔬。調(diào)用AudioServicesPlaySystemSound函數(shù)可以立即播放一個(gè)sound file。你也可以調(diào)用AudioServicesPlayAlertSound播放alert聲音史侣。這兩個(gè)方法都會(huì)在手機(jī)靜音的情況下振動(dòng)拴泌。

當(dāng)然,你也在調(diào)用AudioServicesPlaySystemSound方法使用kSystemSoundID_Vibrate屬性惊橱,顯示的觸發(fā)振動(dòng)蚪腐。

為了使用AudioServicesPlaySystemSound方法播放sound,首先需要將sound file注冊(cè)到系統(tǒng)中税朴,得到一個(gè)sound ID削茁,然后才能播放。

下面一段代碼顯示了使用System Sound Services去play sound:

#include <AudioToolbox/AudioToolbox.h>
#include <CoreFoundation/CoreFoundation.h>
 
// Define a callback to be called when the sound is finished
// playing. Useful when you need to free memory after playing.
static void MyCompletionCallback (
    SystemSoundID  mySSID,
    void * myURLRef
) {
        AudioServicesDisposeSystemSoundID (mySSID);
        CFRelease (myURLRef);
        CFRunLoopStop (CFRunLoopGetCurrent());
}
 
int main (int argc, const char * argv[]) {
    // Set up the pieces needed to play a sound.
    SystemSoundID    mySSID;
    CFURLRef        myURLRef;
    myURLRef = CFURLCreateWithFileSystemPath (
        kCFAllocatorDefault,
        CFSTR ("../../ComedyHorns.aif"),
        kCFURLPOSIXPathStyle,
        FALSE
    );
 
    // create a system sound ID to represent the sound file
    OSStatus error = AudioServicesCreateSystemSoundID (myURLRef, &mySSID);
 
    // Register the sound completion callback.
    // Again, useful when you need to free memory after playing.
    AudioServicesAddSystemSoundCompletion (
        mySSID,
        NULL,
        NULL,
        MyCompletionCallback,
        (void *) myURLRef
    );
 
    // Play the sound file.
    AudioServicesPlaySystemSound (mySSID);
 
    // Invoke a run loop on the current thread to keep the application
    // running long enough for the sound to play; the sound completion
    // callback later stops this run loop.
    CFRunLoopRun ();
    return 0;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末掉房,一起剝皮案震驚了整個(gè)濱河市茧跋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌卓囚,老刑警劉巖瘾杭,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異哪亿,居然都是意外死亡粥烁,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門蝇棉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來讨阻,“玉大人,你說我怎么就攤上這事篡殷《鬯保” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵板辽,是天一觀的道長奇瘦。 經(jīng)常有香客問我,道長劲弦,這世上最難降的妖魔是什么耳标? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮邑跪,結(jié)果婚禮上次坡,老公的妹妹穿的比我還像新娘呼猪。我一直安慰自己,他們只是感情好砸琅,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布宋距。 她就那樣靜靜地躺著,像睡著了一般明棍。 火紅的嫁衣襯著肌膚如雪乡革。 梳的紋絲不亂的頭發(fā)上寇僧,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼卓起。 笑死衫生,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的细办。 我是一名探鬼主播橙凳,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼笑撞!你這毒婦竟也來了岛啸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤茴肥,失蹤者是張志新(化名)和其女友劉穎坚踩,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瓤狐,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瞬铸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了础锐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嗓节。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖皆警,靈堂內(nèi)的尸體忽然破棺而出拦宣,到底是詐尸還是另有隱情,我是刑警寧澤信姓,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布恢着,位于F島的核電站,受9級(jí)特大地震影響财破,放射性物質(zhì)發(fā)生泄漏掰派。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一左痢、第九天 我趴在偏房一處隱蔽的房頂上張望靡羡。 院中可真熱鬧系洛,春花似錦、人聲如沸略步。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽趟薄。三九已至绽诚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間杭煎,已是汗流浹背恩够。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留羡铲,地道東北人蜂桶。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像也切,于是被迫代替她去往敵國和親扑媚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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