幾種播放音頻文件的方式(八) —— 音頻隊(duì)列服務(wù)(Audio Queue Services)之播放音頻(五)

版本記錄

版本號(hào) 時(shí)間
V1.0 2017.12.28

前言

ios系統(tǒng)中有很多方式可以播放音頻文件赚爵,這里我們就詳細(xì)的說(shuō)明下播放音樂(lè)文件的原理和實(shí)例。感興趣的可以看我寫(xiě)的上面幾篇容为。
1. 幾種播放音頻文件的方式(一) —— 播放本地音樂(lè)
2. 幾種播放音頻文件的方式(二) —— 音效播放
3. 幾種播放音頻文件的方式(三) —— 網(wǎng)絡(luò)音樂(lè)播放
4. 幾種播放音頻文件的方式(四) —— 音頻隊(duì)列服務(wù)(Audio Queue Services)(一)
5. 幾種播放音頻文件的方式(五) —— 音頻隊(duì)列服務(wù)(Audio Queue Services)簡(jiǎn)介(二)
6. 幾種播放音頻文件的方式(六) —— 音頻隊(duì)列服務(wù)(Audio Queue Services)之關(guān)于音頻隊(duì)列(三)
7. 幾種播放音頻文件的方式(七) —— 音頻隊(duì)列服務(wù)(Audio Queue Services)之錄制音頻(四)

Playing Audio - 播放音頻

使用音頻隊(duì)列服務(wù)播放音頻時(shí),音源可以是任何東西 - 磁盤(pán)上文件,基于軟件的音頻合成器态坦,內(nèi)存中的對(duì)象等等。本章介紹最常見(jiàn)的情況:播放磁盤(pán)上的文件棒拂。

注意:本章介紹了一個(gè)基于ANSI-C的播放實(shí)現(xiàn)伞梯,并且使用了Mac OS X Core Audio SDK中的C ++類(lèi)。對(duì)于基于Objective-C的示例帚屉,請(qǐng)參閱iOS Dev CenterSpeakHere示例代碼谜诫。

要為您的應(yīng)用程序添加播放功能,通常需要執(zhí)行以下步驟:

    1. 定義一個(gè)自定義結(jié)構(gòu)來(lái)管理狀態(tài)攻旦,格式和路徑信息喻旷。
    1. 編寫(xiě)音頻隊(duì)列回調(diào)函數(shù)來(lái)執(zhí)行實(shí)際播放。
    1. 編寫(xiě)代碼來(lái)確定音頻隊(duì)列緩沖區(qū)的大小牢屋。
    1. 打開(kāi)音頻文件進(jìn)行播放并確定其音頻數(shù)據(jù)格式掰邢。
    1. 創(chuàng)建一個(gè)播放音頻隊(duì)列并將其配置為播放牺陶。
    1. 分配和入隊(duì)音頻隊(duì)列緩沖區(qū)。告訴音頻隊(duì)列開(kāi)始播放辣之。完成后,回放回調(diào)告知音頻隊(duì)列停止皱炉。
    1. 銷(xiāo)毀音頻隊(duì)列怀估,釋放資源。

本章的其余部分將詳細(xì)介紹這些步驟合搅。


Define a Custom Structure to Manage State - 定義一個(gè)自定義結(jié)構(gòu)管理狀態(tài)

首先多搀,定義一個(gè)用來(lái)管理音頻格式和音頻隊(duì)列狀態(tài)信息的自定義結(jié)構(gòu)。 Listing 3-1說(shuō)明了這樣一個(gè)結(jié)構(gòu):

// Listing 3-1  A custom structure for a playback audio queue

static const int kNumberBuffers = 3;                              // 1
struct AQPlayerState {
    AudioStreamBasicDescription   mDataFormat;                    // 2
    AudioQueueRef                 mQueue;                         // 3
    AudioQueueBufferRef           mBuffers[kNumberBuffers];       // 4
    AudioFileID                   mAudioFile;                     // 5
    UInt32                        bufferByteSize;                 // 6
    SInt64                        mCurrentPacket;                 // 7
    UInt32                        mNumPacketsToRead;              // 8
    AudioStreamPacketDescription  *mPacketDescs;                  // 9
    bool                          mIsRunning;                     // 10
};

此結(jié)構(gòu)中的大多數(shù)字段與用于記錄的自定義結(jié)構(gòu)中的字段相同(或接近)灾部,如Define a Custom Structure to Manage State中的“錄制音頻”章節(jié)中所述康铭。例如,mDataFormat字段在此用于保存正在播放的文件的格式赌髓。錄制時(shí)从藤,類(lèi)似字段保存正在寫(xiě)入磁盤(pán)的文件的格式。

以下是這個(gè)結(jié)構(gòu)中的字段的描述:

    1. 設(shè)置要使用的音頻隊(duì)列緩沖區(qū)的數(shù)量锁蠕。如Audio Queue Buffers中所述夷野,“三”通常是一個(gè)很好的數(shù)字。
    1. 表示正在播放的文件的音頻數(shù)據(jù)格式的AudioStreamBasicDescription結(jié)構(gòu)(來(lái)自CoreAudioTypes.h)荣倾。該格式被mQueue字段中指定的音頻隊(duì)列使用悯搔。通過(guò)查詢(xún)音頻文件的kAudioFilePropertyDataFormat屬性來(lái)填充mDataFormat字段,如Obtaining a File’s Audio Data Format中所述舌仍。有關(guān)AudioStreamBasicDescription結(jié)構(gòu)的詳細(xì)信息妒貌,請(qǐng)參閱Core Audio Data Types Reference
    1. 由應(yīng)用程序創(chuàng)建的播放音頻隊(duì)列铸豁。
    1. 一個(gè)數(shù)組灌曙,指向由音頻隊(duì)列管理的音頻隊(duì)列緩沖區(qū)的指針。
    1. 音頻文件對(duì)象推姻,表示您的程序播放的音頻文件平匈。
    1. 每個(gè)音頻隊(duì)列緩沖區(qū)的大小(以字節(jié)為單位)藏古。在創(chuàng)建音頻隊(duì)列之后并在啟動(dòng)之前增炭,在DeriveBufferSize函數(shù)的這些示例中計(jì)算此值。請(qǐng)參閱 Write a Function to Derive Playback Audio Queue Buffer Size拧晕。
    1. 從音頻文件播放下一個(gè)數(shù)據(jù)包的數(shù)據(jù)包索引隙姿。
    1. 每次調(diào)用音頻隊(duì)列的回放回調(diào)時(shí)讀取的數(shù)據(jù)包數(shù)量。與bufferByteSize字段類(lèi)似厂捞,在創(chuàng)建音頻隊(duì)列之后并在啟動(dòng)之前输玷,在DeriveBufferSize函數(shù)的這些示例中計(jì)算此值队丝。
    1. 對(duì)于VBR音頻數(shù)據(jù),正在播放的文件的數(shù)據(jù)包描述數(shù)組欲鹏。對(duì)于CBR數(shù)據(jù)机久,該字段的值為NULL。
    1. 指示音頻隊(duì)列是否正在運(yùn)行的布爾值赔嚎。

Write a Playback Audio Queue Callback - 編寫(xiě)播放音頻隊(duì)列回調(diào)

接下來(lái)膘盖,寫(xiě)一個(gè)回放音頻隊(duì)列回調(diào)函數(shù)。 這個(gè)回調(diào)有三個(gè)主要的事情:

  • 從音頻文件讀取指定數(shù)量的數(shù)據(jù)并將其放入音頻隊(duì)列緩沖區(qū)
  • 將音頻隊(duì)列緩沖區(qū)排入緩沖隊(duì)列
  • 當(dāng)沒(méi)有更多的數(shù)據(jù)要從音頻文件讀取尤误,告訴音頻隊(duì)列停止

本部分顯示了一個(gè)示例回調(diào)聲明侠畔,分別描述了這些任務(wù),并最終呈現(xiàn)整個(gè)回放回調(diào)损晤。 有關(guān)回放回調(diào)角色的說(shuō)明软棺,請(qǐng)參閱圖Figure 1-4

1. The Playback Audio Queue Callback Declaration - 播放音頻隊(duì)列回調(diào)函數(shù)聲明

Listing 3-2顯示了一個(gè)播放音頻隊(duì)列回調(diào)函數(shù)的示例聲明尤勋,聲明為AudioQueue.h頭文件中的AudioQueueOutputCallback

// Listing 3-2  The playback audio queue callback declaration

static void HandleOutputBuffer (
    void                 *aqData,                 // 1
    AudioQueueRef        inAQ,                    // 2
    AudioQueueBufferRef  inBuffer                 // 3
)

以下是這段代碼的工作原理:

    1. 通常喘落,aqData是包含音頻隊(duì)列狀態(tài)信息的自定義結(jié)構(gòu),如Define a Custom Structure to Manage State所述斥黑。
    1. 擁有此回調(diào)的音頻隊(duì)列揖盘。
    1. 一個(gè)音頻隊(duì)列緩沖區(qū),回調(diào)將通過(guò)讀取音頻文件來(lái)填充數(shù)據(jù)锌奴。

2. Reading From a File into an Audio Queue Buffer - 從文件中讀取數(shù)據(jù)到音頻隊(duì)列緩沖區(qū)

回放音頻隊(duì)列回調(diào)的第一個(gè)動(dòng)作是從音頻文件讀取數(shù)據(jù)并將其放入音頻隊(duì)列緩沖區(qū)兽狭。Listing 3-3顯示了如何做到這一點(diǎn)。

// Listing 3-3  Reading from an audio file into an audio queue buffer

AudioFileReadPackets (                        // 1
    pAqData->mAudioFile,                      // 2
    false,                                    // 3
    &numBytesReadFromFile,                    // 4
    pAqData->mPacketDescs,                    // 5
    pAqData->mCurrentPacket,                  // 6
    &numPackets,                              // 7
    inBuffer->mAudioData                      // 8
);

以下是這段代碼的工作原理:

    1. AudioFile.h頭文件中聲明的AudioFileReadPackets函數(shù)從音頻文件讀取數(shù)據(jù)并將其放入緩沖區(qū)鹿蜀。
    1. 要從中讀取的音頻文件箕慧。
    1. 使用值為false來(lái)指示函數(shù)在讀取時(shí)不應(yīng)該緩存數(shù)據(jù)。
    1. 輸出時(shí)茴恰,從音頻文件中讀取的音頻數(shù)據(jù)的字節(jié)數(shù)颠焦。
    1. 在輸出上,從音頻文件中讀取數(shù)據(jù)的數(shù)據(jù)包描述數(shù)組往枣。 對(duì)于CBR數(shù)據(jù)伐庭,此參數(shù)的輸入值為NULL。
    1. 從音頻文件中讀取的第一個(gè)數(shù)據(jù)包數(shù)據(jù)包索引分冈。
    1. 輸入時(shí)圾另,從音頻文件中讀取的數(shù)據(jù)包數(shù)量。 輸出時(shí)雕沉,實(shí)際讀取的數(shù)據(jù)包數(shù)量集乔。
    1. 輸出時(shí),填充的音頻隊(duì)列緩沖區(qū)包含從音頻文件中讀取的數(shù)據(jù)坡椒。

3. Enqueuing an Audio Queue Buffer - 聲頻隊(duì)列緩沖入隊(duì)

現(xiàn)在扰路,數(shù)據(jù)已經(jīng)從音頻文件中讀取并放入音頻隊(duì)列緩沖區(qū)尤溜,回調(diào)將緩沖區(qū)排入隊(duì)列中,如Listing 3-4所示汗唱。 一旦進(jìn)入緩沖區(qū)隊(duì)列宫莱,緩沖區(qū)中的音頻數(shù)據(jù)就可供音頻隊(duì)列發(fā)送到輸出設(shè)備。

// Listing 3-4  Enqueuing an audio queue buffer after reading from disk

AudioQueueEnqueueBuffer (                      // 1
    pAqData->mQueue,                           // 2
    inBuffer,                                  // 3
    (pAqData->mPacketDescs ? numPackets : 0),  // 4
    pAqData->mPacketDescs                      // 5
);

以下是這段代碼的工作原理:

    1. AudioQueueEnqueueBuffer函數(shù)將音頻隊(duì)列緩沖區(qū)添加到緩沖區(qū)隊(duì)列中哩罪。
    1. 擁有緩沖隊(duì)列buffer queue的音頻隊(duì)列audio queue梢睛。
    1. 要排隊(duì)的音頻隊(duì)列緩沖區(qū)
    1. 音頻隊(duì)列緩沖區(qū)數(shù)據(jù)中表示的數(shù)據(jù)包數(shù)量。 對(duì)于不使用數(shù)據(jù)包描述的CBR數(shù)據(jù)识椰,使用0。
    1. 對(duì)于使用數(shù)據(jù)包描述的壓縮音頻數(shù)據(jù)格式深碱,緩沖區(qū)中數(shù)據(jù)包的數(shù)據(jù)包描述腹鹉。

4. Stopping an Audio Queue - 停止音頻隊(duì)列

你回調(diào)的最后一件事是檢查是否沒(méi)有更多的數(shù)據(jù)從你正在播放的音頻文件中讀取。 一旦發(fā)現(xiàn)文件結(jié)束敷硅,你的回調(diào)告訴播放音頻隊(duì)列停止功咒。 Listing 3-5說(shuō)明了這一點(diǎn)。

// Listing 3-5  Stopping an audio queue

if (numPackets == 0) {                          // 1
    AudioQueueStop (                            // 2
        pAqData->mQueue,                        // 3
        false                                   // 4
    );
    pAqData->mIsRunning = false;                // 5
}

以下是這段代碼的工作原理:

    1. 檢查AudioFileReadPackets函數(shù)讀取的數(shù)據(jù)包的數(shù)量是否為0绞蹦。
    1. AudioQueueStop函數(shù)停止音頻隊(duì)列力奋。
    1. 要停止的音頻隊(duì)列。
    1. 當(dāng)所有排隊(duì)的緩沖區(qū)都被播放時(shí)幽七,異步停止音頻隊(duì)列景殷。 請(qǐng)參閱Audio Queue Control and State
    1. 在自定義結(jié)構(gòu)中設(shè)置一個(gè)標(biāo)志來(lái)指示播放完成澡屡。

5. A Full Playback Audio Queue Callback - 播放音頻隊(duì)列回調(diào)函數(shù)完整版

Listing 3-6顯示了完整播放音頻隊(duì)列回調(diào)的基本版本猿挚。 與本文檔中的其他代碼示例一樣,此列表不包括錯(cuò)誤處理驶鹉。

// Listing 3-6  A playback audio queue callback function

static void HandleOutputBuffer (
    void                *aqData,
    AudioQueueRef       inAQ,
    AudioQueueBufferRef inBuffer
) {
    AQPlayerState *pAqData = (AQPlayerState *) aqData;        // 1
    if (pAqData->mIsRunning == 0) return;                     // 2
    UInt32 numBytesReadFromFile;                              // 3
    UInt32 numPackets = pAqData->mNumPacketsToRead;           // 4
    AudioFileReadPackets (
        pAqData->mAudioFile,
        false,
        &numBytesReadFromFile,
        pAqData->mPacketDescs, 
        pAqData->mCurrentPacket,
        &numPackets,
        inBuffer->mAudioData 
    );
    if (numPackets > 0) {                                     // 5
        inBuffer->mAudioDataByteSize = numBytesReadFromFile;  // 6
       AudioQueueEnqueueBuffer ( 
            pAqData->mQueue,
            inBuffer,
            (pAqData->mPacketDescs ? numPackets : 0),
            pAqData->mPacketDescs
        );
        pAqData->mCurrentPacket += numPackets;                // 7 
    } else {
        AudioQueueStop (
            pAqData->mQueue,
            false
        );
        pAqData->mIsRunning = false; 
    }
}

以下是這段代碼的工作原理:

    1. 實(shí)例化時(shí)提供給音頻隊(duì)列的自定義數(shù)據(jù)绩蜻,包括表示要播放的文件的音頻文件對(duì)象(類(lèi)型AudioFileID)以及各種狀態(tài)數(shù)據(jù)。 請(qǐng)參閱 Define a Custom Structure to Manage State室埋。
    1. 如果音頻隊(duì)列停止办绝,立即返回。
    1. 保存正在播放的文件中讀取的音頻數(shù)據(jù)字節(jié)數(shù)的變量姚淆。
    1. 使用要從正在播放的文件中讀取的數(shù)據(jù)包數(shù)初始化numPackets變量孕蝉。
    1. 測(cè)試是否從文件中檢索到某些音頻數(shù)據(jù)。 如果是肉盹,排隊(duì)新填充的緩沖區(qū)昔驱。 如果不是,則停止音頻隊(duì)列上忍。
    1. 告訴音頻隊(duì)列緩沖區(qū)結(jié)構(gòu)讀取數(shù)據(jù)的字節(jié)數(shù)骤肛。
    1. 根據(jù)讀取的數(shù)據(jù)包數(shù)量遞增數(shù)據(jù)包索引纳本。

Write a Function to Derive Playback Audio Queue Buffer Size - 編寫(xiě)函數(shù)獲取播放音頻隊(duì)列緩沖區(qū)的大小

音頻隊(duì)列服務(wù)期望您的應(yīng)用程序指定您使用的音頻隊(duì)列緩沖區(qū)的大小。 Listing 3-7顯示了一種方法腋颠。 它產(chǎn)生足夠大的緩沖區(qū)大小來(lái)保存給定持續(xù)時(shí)間的音頻數(shù)據(jù)繁成。

在創(chuàng)建一個(gè)回放音頻隊(duì)列之后,您將在您的應(yīng)用程序中調(diào)用此DeriveBufferSize函數(shù)淑玫,作為要求音頻隊(duì)列分配緩沖區(qū)的先決條件巾腕。 請(qǐng)參閱Set Sizes for a Playback Audio Queue

與你在Write a Function to Derive Recording Audio Queue Buffer Size類(lèi)似的函數(shù)相比絮蒿,這里的代碼做了兩個(gè)額外的事情尊搬, 對(duì)于回放同樣如此:

    1. 每次您的回調(diào)調(diào)用AudioFileReadPackets函數(shù)時(shí),要讀取數(shù)據(jù)包的數(shù)量
    1. 設(shè)置緩沖區(qū)大小的下限土涝,以避免過(guò)度頻繁的磁盤(pán)訪問(wèn)

這里的計(jì)算考慮了您從磁盤(pán)讀取的音頻數(shù)據(jù)格式佛寿。 格式包括可能影響緩沖區(qū)大小的所有因素,例如音頻通道的數(shù)量但壮。

// Listing 3-7  Deriving a playback audio queue buffer size

void DeriveBufferSize (
    AudioStreamBasicDescription &ASBDesc,                            // 1
    UInt32                      maxPacketSize,                       // 2
    Float64                     seconds,                             // 3
    UInt32                      *outBufferSize,                      // 4
    UInt32                      *outNumPacketsToRead                 // 5
) {
    static const int maxBufferSize = 0x50000;                        // 6
    static const int minBufferSize = 0x4000;                         // 7
 
    if (ASBDesc.mFramesPerPacket != 0) {                             // 8
        Float64 numPacketsForTime =
            ASBDesc.mSampleRate / ASBDesc.mFramesPerPacket * seconds;
        *outBufferSize = numPacketsForTime * maxPacketSize;
    } else {                                                         // 9
        *outBufferSize =
            maxBufferSize > maxPacketSize ?
                maxBufferSize : maxPacketSize;
    }
 
    if (                                                             // 10
        *outBufferSize > maxBufferSize &&
        *outBufferSize > maxPacketSize
    )
        *outBufferSize = maxBufferSize;
    else {                                                           // 11
        if (*outBufferSize < minBufferSize)
            *outBufferSize = minBufferSize;
    }
 
    *outNumPacketsToRead = *outBufferSize / maxPacketSize;           // 12
}

以下是這段代碼的工作原理:

    1. 音頻隊(duì)列的AudioStreamBasicDescription結(jié)構(gòu)冀泻。
    1. 您正在播放的音頻文件中數(shù)據(jù)的估計(jì)最大數(shù)據(jù)包大小。您可以通過(guò)調(diào)用AudioFileGetProperty函數(shù)(在AudioFile.h頭文件中聲明)使用屬性ID kAudioFilePropertyPacketSizeUpperBound來(lái)確定此值蜡饵。請(qǐng)參閱Set Sizes for a Playback Audio Queue弹渔。
    1. 按照音頻的秒數(shù)指定每個(gè)音頻隊(duì)列緩沖區(qū)的大小。
    1. 輸出時(shí)溯祸,每個(gè)音頻隊(duì)列緩沖區(qū)的大兄ā(以字節(jié)為單位)。
    1. 輸出時(shí)您没,每次調(diào)用回放音頻隊(duì)列回調(diào)時(shí)從文件中讀取的音頻數(shù)據(jù)包的數(shù)量鸟召。
    1. 音頻隊(duì)列緩沖區(qū)大小的上限(以字節(jié)為單位)。在這個(gè)例子中氨鹏,上限設(shè)置為320 KB欧募。這對(duì)應(yīng)于大約五秒鐘的立體聲,采樣率為96kHz的24位音頻仆抵。
    1. 音頻隊(duì)列緩沖區(qū)大小的下限跟继,以字節(jié)為單位。在這個(gè)例子中镣丑,下限設(shè)置為16 KB舔糖。
    1. 對(duì)于定義每個(gè)數(shù)據(jù)包固定數(shù)量幀的音頻數(shù)據(jù)格式,獲取音頻隊(duì)列緩沖區(qū)大小莺匠。
    1. 對(duì)于沒(méi)有為每個(gè)數(shù)據(jù)包定義固定數(shù)量幀的音頻數(shù)據(jù)格式金吗,根據(jù)最大數(shù)據(jù)包大小和您設(shè)置的上限獲取合理的音頻隊(duì)列緩沖區(qū)大小。
    1. 如果導(dǎo)出的緩沖區(qū)大小高于您設(shè)置的上限,則根據(jù)估計(jì)的最大數(shù)據(jù)包大小調(diào)整邊界考慮摇庙。
    1. 如果導(dǎo)出的緩沖區(qū)大小低于您設(shè)置的下限旱物,則將其調(diào)整到界限。
    1. 計(jì)算每次調(diào)用回調(diào)時(shí)從音頻文件中讀取的數(shù)據(jù)包數(shù)量卫袒。

Open an Audio File for Playback - 打開(kāi)用于播放的音頻文件

現(xiàn)在您打開(kāi)一個(gè)音頻文件進(jìn)行播放宵呛,使用以下三個(gè)步驟:

    1. 獲取表示要播放的音頻文件的CFURL對(duì)象。
    1. 打開(kāi)文件夕凝。
    1. 獲取文件的音頻數(shù)據(jù)格式

1. Obtaining a CFURL Object for an Audio File - 從音頻文件中獲取CFURL對(duì)象

Listing 3-8演示了如何獲取要播放的音頻文件的CFURL對(duì)象宝穗。 在下一步中使用CFURL對(duì)象,打開(kāi)文件码秉。

// Listing 3-8  Obtaining a CFURL object for an audio file

CFURLRef audioFileURL =
    CFURLCreateFromFileSystemRepresentation (           // 1
        NULL,                                           // 2
        (const UInt8 *) filePath,                       // 3
        strlen (filePath),                              // 4
        false                                           // 5
    );

以下是這段代碼的工作原理:

    1. CFURL.h頭文件中聲明的CFURLCreateFromFileSystemRepresentation函數(shù)創(chuàng)建一個(gè)代表要播放文件的CFURL對(duì)象逮矛。
    1. 使用NULL(或kCFAllocatorDefault)來(lái)使用當(dāng)前的默認(rèn)內(nèi)存分配器。
    1. 您要轉(zhuǎn)換為CFURL對(duì)象的文件系統(tǒng)路徑转砖。 在生產(chǎn)代碼中橱鹏,通常會(huì)從用戶(hù)獲取filePath的值。
    1. 文件系統(tǒng)路徑中的字節(jié)數(shù)堪藐。
    1. 值為false表示filePath表示文件,而不是文件夾directory挑围。

2. Opening an Audio File - 打開(kāi)音頻文件

Listing 3-9演示了如何打開(kāi)一個(gè)音頻文件進(jìn)行播放礁竞。

// Listing 3-9  Opening an audio file for playback

AQPlayerState aqData;                                   // 1
 
OSStatus result =
    AudioFileOpenURL (                                  // 2
        audioFileURL,                                   // 3
        fsRdPerm,                                       // 4
        0,                                              // 5
        &aqData.mAudioFile                              // 6
    );
 
CFRelease (audioFileURL);                               // 7

以下是這段代碼的工作原理:

    1. 創(chuàng)建AQPlayerState自定義結(jié)構(gòu)的實(shí)例(請(qǐng)參閱Define a Custom Structure to Manage State)。 當(dāng)您打開(kāi)音頻文件進(jìn)行播放時(shí)杉辙,您可以使用此實(shí)例作為放置表示音頻文件的音頻文件對(duì)象(AudioFileID類(lèi)型)的位置模捂。
    1. AudioFile.h頭文件中聲明的AudioFileOpenURL函數(shù)打開(kāi)你想要播放的文件。
    1. 對(duì)要播放的文件的引用蜘矢。
    1. 您想要與您正在播放的文件一起使用的文件權(quán)限狂男。 可用權(quán)限在文件管理器的File Access Permission Constants枚舉中定義。 在這個(gè)例子中你要求讀取文件的權(quán)限品腹。
    1. 一個(gè)可選的文件類(lèi)型提示岖食。 此處的值為0表示該示例不使用此功能。
    1. 在輸出時(shí)舞吭,對(duì)音頻文件的引用被放置在自定義結(jié)構(gòu)的mAudioFile字段中泡垃。
    1. 釋放在步驟1中創(chuàng)建的CFURL對(duì)象。

3. Obtaining a File’s Audio Data Format - 獲取文件音頻數(shù)據(jù)格式

Listing 3-10顯示了如何獲取文件的音頻數(shù)據(jù)格式

Listing 3-10  Obtaining a file’s audio data format

UInt32 dataFormatSize = sizeof (aqData.mDataFormat);    // 1
 
AudioFileGetProperty (                                  // 2
    aqData.mAudioFile,                                  // 3
    kAudioFilePropertyDataFormat,                       // 4
    &dataFormatSize,                                    // 5
    &aqData.mDataFormat                                 // 6
);

以下是這段代碼的工作原理:

    1. 獲取預(yù)期的屬性值大小羡鸥,用于查詢(xún)音頻文件的音頻數(shù)據(jù)格式蔑穴。
    1. AudioFile.h頭文件中聲明的AudioFileGetProperty函數(shù)獲取音頻文件中指定屬性的值。
    1. 音頻文件對(duì)象(類(lèi)型為AudioFileID)惧浴,代表要獲取其音頻數(shù)據(jù)格式的文件存和。
    1. 用于獲取音頻文件的數(shù)據(jù)格式的值的屬性ID。
    1. 輸入時(shí),描述音頻文件數(shù)據(jù)格式的AudioStreamBasicDescription結(jié)構(gòu)的預(yù)期大小捐腿。 在輸出上纵朋,實(shí)際的大小。 您的回放應(yīng)用程序不需要使用此值叙量。
    1. 在輸出時(shí)倡蝙,以音頻文件的形式從AudioStreamBasicDescription結(jié)構(gòu)中獲得完整的音頻數(shù)據(jù)格式。 該行將文件的音頻數(shù)據(jù)格式存儲(chǔ)到音頻隊(duì)列的自定義結(jié)構(gòu)中绞佩,以將其應(yīng)用于音頻隊(duì)列嘴拢。

Create a Playback Audio Queue - 創(chuàng)建播放音頻隊(duì)列

Listing 3-11顯示了如何創(chuàng)建一個(gè)回放音頻隊(duì)列。 請(qǐng)注意涂滴,AudioQueueNewOutput函數(shù)使用前面步驟中配置的自定義結(jié)構(gòu)和回調(diào)街州,以及要播放的文件的音頻數(shù)據(jù)格式。

// Listing 3-11  Creating a playback audio queue

AudioQueueNewOutput (                                // 1
    &aqData.mDataFormat,                             // 2
    HandleOutputBuffer,                              // 3
    &aqData,                                         // 4
    CFRunLoopGetCurrent (),                          // 5
    kCFRunLoopCommonModes,                           // 6
    0,                                               // 7
    &aqData.mQueue                                   // 8
);

以下是這段代碼的工作原理:

    1. AudioQueueNewOutput函數(shù)創(chuàng)建一個(gè)新的播放音頻隊(duì)列肘交。
    1. 音頻隊(duì)列正在設(shè)置播放的文件的音頻數(shù)據(jù)格式笆载。 請(qǐng)參閱 Obtaining a File’s Audio Data Format
    1. 與回放音頻隊(duì)列一起使用的回調(diào)函數(shù)涯呻。 請(qǐng)參閱Write a Playback Audio Queue Callback凉驻。
    1. 播放音頻隊(duì)列的自定義數(shù)據(jù)結(jié)構(gòu)。 請(qǐng)參閱Define a Custom Structure to Manage State复罐。
    1. 當(dāng)前運(yùn)行循環(huán)涝登,以及將調(diào)用音頻隊(duì)列回放回調(diào)的循環(huán)。
    1. 可以調(diào)用回調(diào)的運(yùn)行循環(huán)模式效诅。 通常胀滚,在這里使用kCFRunLoopCommonModes常量。
    1. 保留乱投。 必須為0咽笼。
    1. 輸出時(shí),新分配的播放音頻隊(duì)列戚炫。

Set Sizes for a Playback Audio Queue - 設(shè)置回放音頻隊(duì)列的大小

接下來(lái)剑刑,您為播放音頻隊(duì)列設(shè)置一些大小。 在為音頻隊(duì)列分配緩沖區(qū)之前以及在開(kāi)始讀取音頻文件之前双肤,請(qǐng)使用這些大小叛甫。

本節(jié)中的代碼清單顯示如何設(shè)置:

  • 音頻隊(duì)列緩沖區(qū)大小
  • 每次調(diào)用回放音頻隊(duì)列回調(diào)時(shí)要讀取的數(shù)據(jù)包數(shù)量
  • 數(shù)組大小,用于保存一個(gè)緩沖區(qū)的音頻數(shù)據(jù)的數(shù)據(jù)包描述

1. Setting Buffer Size and Number of Packets to Read - 設(shè)置緩存大小和要讀取的包的數(shù)量

Listing 3-12演示了如何使用之前編寫(xiě)的DeriveBufferSize函數(shù)(請(qǐng)參閱 Write a Function to Derive Playback Audio Queue Buffer Size)杨伙。 此處的目標(biāo)是為每個(gè)音頻隊(duì)列緩沖區(qū)設(shè)置一個(gè)以字節(jié)為單位的大小其监,并確定每次調(diào)用回放音頻隊(duì)列回調(diào)時(shí)要讀取的數(shù)據(jù)包數(shù)量。

此代碼使用保守估計(jì)的最大數(shù)據(jù)包大小限匣,Core Audio通過(guò)kAudioFilePropertyPacketSizeUpperBound屬性提供抖苦。 在大多數(shù)情況下毁菱,與比花費(fèi)時(shí)間讀取整個(gè)音頻文件來(lái)獲得實(shí)際的最大數(shù)據(jù)包大小相比,最好使用這種技術(shù) - 這是近似而快速的 锌历。

// Listing 3-12  Setting playback audio queue buffer size and number of packets to read

UInt32 maxPacketSize;
UInt32 propertySize = sizeof (maxPacketSize);
AudioFileGetProperty (                               // 1
    aqData.mAudioFile,                               // 2
    kAudioFilePropertyPacketSizeUpperBound,          // 3
    &propertySize,                                   // 4
    &maxPacketSize                                   // 5
);
 
DeriveBufferSize (                                   // 6
    aqData.mDataFormat,                              // 7
    maxPacketSize,                                   // 8
    0.5,                                             // 9
    &aqData.bufferByteSize,                          // 10
    &aqData.mNumPacketsToRead                        // 11
);

以下是這段代碼的工作原理:

    1. AudioFile.h頭文件中聲明的AudioFileGetProperty函數(shù)獲取音頻文件的指定屬性的值贮庞。在這里,你用它來(lái)獲得一個(gè)保守的上限究西,以字節(jié)為單位窗慎,你想播放的文件中的音頻數(shù)據(jù)包的大小。
    1. 表示要播放的文件的音頻文件對(duì)象(類(lèi)型為AudioFileID)卤材。請(qǐng)參閱打Opening an Audio File遮斥。
    1. 用于獲取音頻文件中數(shù)據(jù)包大小保守上限的屬性ID。
    1. 在輸出上扇丛,kAudioFilePropertyPacketSizeUpperBound屬性的大惺趼稹(以字節(jié)為單位)。
    1. 在輸出上帆精,對(duì)于要播放的文件较屿,數(shù)據(jù)包大小的保守上限(以字節(jié)為單位)。
    1. Write a Function to Derive Playback Audio Queue Buffer Size中描述的DeriveBufferSize函數(shù)設(shè)置每次調(diào)用回放音頻隊(duì)列回調(diào)時(shí)要讀取的緩沖區(qū)大小和數(shù)據(jù)包的數(shù)量卓练。
    1. 您要播放的文件的音頻數(shù)據(jù)格式隘蝎。請(qǐng)參閱Obtaining a File’s Audio Data Format
    1. 音頻文件中估計(jì)的最大數(shù)據(jù)包大小襟企,來(lái)自此列表的第5行末贾。
    1. 每個(gè)音頻隊(duì)列緩沖區(qū)應(yīng)該容納的音頻的秒數(shù)。半秒鐘整吆,如這里設(shè)置,通常是一個(gè)不錯(cuò)的選擇辉川。
    1. 輸出時(shí)表蝙,每個(gè)音頻隊(duì)列緩沖區(qū)的大小(以字節(jié)為單位)乓旗。該值放置在音頻隊(duì)列的自定義結(jié)構(gòu)中府蛇。
    1. 輸出時(shí),每次調(diào)用播放音頻隊(duì)列回調(diào)時(shí)要讀取的數(shù)據(jù)包數(shù)量屿愚。該值也放置在音頻隊(duì)列的自定義結(jié)構(gòu)中汇跨。

2. Allocating Memory for a Packet Descriptions Array - 為包的描述數(shù)組分配內(nèi)存

現(xiàn)在,您為數(shù)組分配內(nèi)存妆距,以保存一個(gè)緩沖區(qū)的音頻數(shù)據(jù)的數(shù)據(jù)包描述穷遂。 恒定比特率數(shù)據(jù)不使用數(shù)據(jù)包描述,因此Listing 3-13中的CBR情況步驟3非常簡(jiǎn)單娱据。

// Listing 3-13  Allocating memory for a packet descriptions array

bool isFormatVBR = (                                       // 1
    aqData.mDataFormat.mBytesPerPacket == 0 ||
    aqData.mDataFormat.mFramesPerPacket == 0
);
 
if (isFormatVBR) {                                         // 2
    aqData.mPacketDescs =
      (AudioStreamPacketDescription*) malloc (
        aqData.mNumPacketsToRead * sizeof (AudioStreamPacketDescription)
      );
} else {                                                   // 3
    aqData.mPacketDescs = NULL;
}

以下是這段代碼的工作原理:

    1. 確定音頻文件的數(shù)據(jù)格式是VBR還是CBR蚪黑。 在VBR數(shù)據(jù)中,每個(gè)字節(jié)數(shù)據(jù)包或幀每個(gè)數(shù)據(jù)包值中的一個(gè)或兩個(gè)是可變的,因此在音頻隊(duì)列的AudioStreamBasicDescription結(jié)構(gòu)中將被列為0忌穿。
    1. 對(duì)于包含VBR數(shù)據(jù)的音頻文件抒寂,為數(shù)據(jù)包描述數(shù)組分配內(nèi)存。 根據(jù)每次調(diào)用回放回調(diào)時(shí)要讀取的音頻數(shù)據(jù)包的數(shù)量來(lái)計(jì)算所需的內(nèi)存掠剑。 請(qǐng)參閱Setting Buffer Size and Number of Packets to Read屈芜。
    1. 對(duì)于包含CBR數(shù)據(jù)(如線(xiàn)性PCM)的音頻文件,音頻隊(duì)列不使用數(shù)據(jù)包描述數(shù)組朴译。

Set a Magic Cookie for a Playback Audio Queue - 為播放音頻隊(duì)列設(shè)置Magic Cookie

某些壓縮音頻格式(如MPEG 4 AAC)利用結(jié)構(gòu)來(lái)包含音頻元數(shù)據(jù)井佑。 這些結(jié)構(gòu)被稱(chēng)為Magic Cookie。 當(dāng)您使用音頻隊(duì)列服務(wù)以這種格式播放文件時(shí)动分,您將從音頻文件中獲取Magic Cookie毅糟,并在開(kāi)始播放之前將其添加到音頻隊(duì)列中。

Listing 3-14展示了如何從一個(gè)文件中獲得一個(gè)Magic Cookie并將其應(yīng)用到一個(gè)音頻隊(duì)列中澜公。 開(kāi)始播放之前姆另,您的代碼會(huì)調(diào)用此函數(shù)。

// Listing 3-14  Setting a magic cookie for a playback audio queue

UInt32 cookieSize = sizeof (UInt32);                   // 1
bool couldNotGetProperty =                             // 2
    AudioFileGetPropertyInfo (                         // 3
        aqData.mAudioFile,                             // 4
        kAudioFilePropertyMagicCookieData,             // 5
        &cookieSize,                                   // 6
        NULL                                           // 7
    );
 
if (!couldNotGetProperty && cookieSize) {              // 8
    char* magicCookie =
        (char *) malloc (cookieSize);
 
    AudioFileGetProperty (                             // 9
        aqData.mAudioFile,                             // 10
        kAudioFilePropertyMagicCookieData,             // 11
        &cookieSize,                                   // 12
        magicCookie                                    // 13
    );
 
    AudioQueueSetProperty (                            // 14
        aqData.mQueue,                                 // 15
        kAudioQueueProperty_MagicCookie,               // 16
        magicCookie,                                   // 17
        cookieSize                                     // 18
    );
 
    free (magicCookie);                                // 19
}

以下是這段代碼的工作原理:

    1. 為magic cookie數(shù)據(jù)設(shè)置估計(jì)大小坟乾。
    1. 捕獲AudioFileGetPropertyInfo函數(shù)的結(jié)果迹辐。如果成功,則此函數(shù)返回NoErr的值甚侣,相當(dāng)于布爾值false明吩。
    1. AudioFile.h頭文件中聲明的AudioFileGetPropertyInfo函數(shù)獲取指定屬性值的大小。您可以使用它來(lái)設(shè)置保存屬性值的變量的大小殷费。
    1. 音頻文件對(duì)象(類(lèi)型為AudioFileID)印荔,表示要播放的音頻文件。
    1. 屬性ID代表音頻文件的magic cookie數(shù)據(jù)详羡。
    1. 在輸入時(shí)仍律,magic cookie數(shù)據(jù)的估計(jì)大小。在輸出上实柠,實(shí)際的大小水泉。
    1. 使用NULL來(lái)表示您不關(guān)心屬性的讀/寫(xiě)訪問(wèn)權(quán)限。
    1. 如果音頻文件包含一個(gè)magic cookie窒盐,分配內(nèi)存來(lái)持有它草则。
    1. AudioFile.h頭文件中聲明的AudioFileGetProperty函數(shù)獲取指定屬性的值。在這種情況下蟹漓,它會(huì)獲取音頻文件的magic cookie炕横。
    1. 音頻文件對(duì)象(類(lèi)型為AudioFileID),表示您要播放的音頻文件葡粒,以及您獲取的magic cookie看锉。
    1. 表示音頻文件magic cookie數(shù)據(jù)的屬性ID姿锭。
    1. 在輸入上,使用AudioFileGetPropertyInfo函數(shù)獲得的magicCookie變量的大小伯铣。在輸出上呻此,根據(jù)寫(xiě)入到magicCookie變量的字節(jié)數(shù)來(lái)計(jì)算magic cookie的實(shí)際大小。
    1. 輸出時(shí)腔寡,音頻文件的magic cookie焚鲜。
    1. AudioQueueSetProperty函數(shù)在音頻隊(duì)列中設(shè)置一個(gè)屬性。在這種情況下放前,它為音頻隊(duì)列設(shè)置一個(gè)magic cookie忿磅,匹配要播放的音頻文件中的magic cookie。
    1. 您要為其設(shè)置magic cookie的音頻隊(duì)列凭语。
    1. 屬性ID代表音頻隊(duì)列的magic cookie葱她。
    1. 您要播放的音頻文件中的magic cookie。
    1. magic cookie的大小似扔,以字節(jié)為單位吨些。
    1. 釋放為magic cookie分配的內(nèi)存。

Allocate and Prime Audio Queue Buffers - 分配和填充音頻隊(duì)列緩沖區(qū)

現(xiàn)在您可以詢(xún)問(wèn)您創(chuàng)建的音頻隊(duì)列(在Create a Playback Audio Queue中)以準(zhǔn)備一組音頻隊(duì)列緩沖區(qū)炒辉。 Listing 3-15演示了如何做到這一點(diǎn)豪墅。

// Listing 3-15  Allocating and priming audio queue buffers for playback

aqData.mCurrentPacket = 0;                                // 1
 
for (int i = 0; i < kNumberBuffers; ++i) {                // 2
    AudioQueueAllocateBuffer (                            // 3
        aqData.mQueue,                                    // 4
        aqData.bufferByteSize,                            // 5
        &aqData.mBuffers[i]                               // 6
    );
 
    HandleOutputBuffer (                                  // 7
        &aqData,                                          // 8
        aqData.mQueue,                                    // 9
        aqData.mBuffers[i]                                // 10
    );
}

以下是這段代碼的工作原理:

    1. 將數(shù)據(jù)包索引設(shè)置為0,以便當(dāng)音頻隊(duì)列回調(diào)開(kāi)始填充緩沖區(qū)時(shí)(步驟7)黔寇,它將從音頻文件的開(kāi)始處開(kāi)始偶器。
    1. 分配和填充一組音頻隊(duì)列緩沖區(qū)。 (您可以在Define a Custom Structure to Manage State缝裤。將此編號(hào)kNumberBuffers設(shè)置為3)屏轰。
    1. AudioQueueAllocateBuffer函數(shù)通過(guò)為其分配內(nèi)存來(lái)創(chuàng)建音頻隊(duì)列緩沖區(qū)。
    1. 正在分配音頻隊(duì)列緩沖區(qū)的音頻隊(duì)列憋飞。
    1. 新的音頻隊(duì)列緩沖區(qū)的大小(以字節(jié)為單位)搀崭。
    1. 在輸出上,將新的音頻隊(duì)列緩沖區(qū)添加到自定義結(jié)構(gòu)中的mBuffers數(shù)組中猾编。
    1. HandleOutputBuffer函數(shù)是你寫(xiě)的回放音頻隊(duì)列回調(diào)函數(shù)瘤睹。 請(qǐng)參閱 Write a Playback Audio Queue Callback
    1. 音頻隊(duì)列的自定義結(jié)構(gòu)答倡。
    1. 您正在調(diào)用的回調(diào)的音頻隊(duì)列轰传。
    1. 您傳遞給音頻隊(duì)列回調(diào)的音頻隊(duì)列緩沖區(qū)。

Set an Audio Queue’s Playback Gain - 設(shè)置音頻隊(duì)列的播放增益

在您告訴音頻隊(duì)列開(kāi)始播放之前瘪撇,您需要通過(guò)音頻隊(duì)列參數(shù)機(jī)制設(shè)置其增益获茬。 Listing 3-16顯示了如何做到這一點(diǎn)港庄。 有關(guān)參數(shù)機(jī)制的更多信息,請(qǐng)參閱Audio Queue Parameters恕曲。

// Listing 3-16  Setting an audio queue’s playback gain

Float32 gain = 1.0;                                       // 1
    // Optionally, allow user to override gain setting here
AudioQueueSetParameter (                                  // 2
    aqData.mQueue,                                        // 3
    kAudioQueueParam_Volume,                              // 4
    gain                                                  // 5
);

以下是這段代碼的工作原理:

    1. 在0(用于靜音)和1(用于單位增益)之間設(shè)置與音頻隊(duì)列一起使用的增益鹏氧。
    1. AudioQueueSetParameter函數(shù)設(shè)置音頻隊(duì)列的參數(shù)值。
    1. 您正在設(shè)置參數(shù)的音頻隊(duì)列佩谣。
    1. 您正在設(shè)置的參數(shù)的ID把还。 kAudioQueueParam_Volume常量讓您設(shè)置音頻隊(duì)列的增益。
    1. 您正在應(yīng)用到音頻隊(duì)列的增益設(shè)置茸俭。

Start and Run an Audio Queue - 開(kāi)始和運(yùn)行音頻隊(duì)列

所有前面的代碼都說(shuō)明了播放文件的過(guò)程吊履。 這包括啟動(dòng)音頻隊(duì)列并在文件播放時(shí)保持運(yùn)行循環(huán),如Listing 3-17所示

// Listing 3-17  Starting and running an audio queue

aqData.mIsRunning = true;                          // 1
 
AudioQueueStart (                                  // 2
    aqData.mQueue,                                 // 3
    NULL                                           // 4
);
 
do {                                               // 5
    CFRunLoopRunInMode (                           // 6
        kCFRunLoopDefaultMode,                     // 7
        0.25,                                      // 8
        false                                      // 9
    );
} while (aqData.mIsRunning);
 
CFRunLoopRunInMode (                               // 10
    kCFRunLoopDefaultMode,
    1,
    false
);

以下是這段代碼的工作原理:

    1. 在自定義結(jié)構(gòu)中設(shè)置一個(gè)標(biāo)志來(lái)指示音頻隊(duì)列正在運(yùn)行调鬓。
    1. AudioQueueStart函數(shù)在其自己的線(xiàn)程上啟動(dòng)音頻隊(duì)列艇炎。
    1. 音頻隊(duì)列開(kāi)始。
    1. 使用NULL來(lái)指示音頻隊(duì)列應(yīng)該立即開(kāi)始播放腾窝。
    1. 定期輪詢(xún)自定義結(jié)構(gòu)的mIsRunning字段缀踪,以檢查音頻隊(duì)列是否已停止。
    1. CFRunLoopRunInMode函數(shù)運(yùn)行包含音頻隊(duì)列線(xiàn)程的運(yùn)行循環(huán)燕锥。
    1. 使用運(yùn)行循環(huán)的默認(rèn)模式辜贵。
    1. 將運(yùn)行循環(huán)的運(yùn)行時(shí)間設(shè)置為0.25秒。
    1. 使用false來(lái)指示運(yùn)行循環(huán)應(yīng)該持續(xù)指定的全部時(shí)間归形。
    1. 音頻隊(duì)列停止后托慨,再運(yùn)行一次運(yùn)行循環(huán),以確保當(dāng)前正在播放的音頻隊(duì)列緩沖區(qū)有時(shí)間完成暇榴。

Clean Up After Playing - 播放完后的清理

當(dāng)您完成播放文件時(shí)厚棵,請(qǐng)釋放音頻隊(duì)列,關(guān)閉音頻文件并釋放剩余的資源蔼紧。 Listing 3-18說(shuō)明了這些步驟婆硬。

// Listing 3-18  Cleaning up after playing an audio file

AudioQueueDispose (                            // 1
    aqData.mQueue,                             // 2
    true                                       // 3
);
 
AudioFileClose (aqData.mAudioFile);            // 4
 
free (aqData.mPacketDescs);                    // 5

以下是這段代碼的工作原理:

    1. AudioQueueDispose函數(shù)銷(xiāo)毀音頻隊(duì)列及其所有資源,包括其緩沖區(qū)奸例。
    1. 您想要處理的音頻隊(duì)列彬犯。
    1. 使用true來(lái)同步銷(xiāo)毀音頻隊(duì)列。
    1. 關(guān)閉播放的音頻文件查吊。 AudioFileClose函數(shù)在AudioFile.h頭文件中聲明谐区。
    1. 釋放用來(lái)保存數(shù)據(jù)包描述的內(nèi)存。

后記

未完逻卖,待續(xù)~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末宋列,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子评也,更是在濱河造成了極大的恐慌炼杖,老刑警劉巖灭返,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異坤邪,居然都是意外死亡熙含,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén)罩扇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)婆芦,“玉大人,你說(shuō)我怎么就攤上這事喂饥∠迹” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵员帮,是天一觀的道長(zhǎng)或粮。 經(jīng)常有香客問(wèn)我,道長(zhǎng)捞高,這世上最難降的妖魔是什么氯材? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮硝岗,結(jié)果婚禮上氢哮,老公的妹妹穿的比我還像新娘。我一直安慰自己型檀,他們只是感情好冗尤,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著胀溺,像睡著了一般裂七。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仓坞,一...
    開(kāi)封第一講書(shū)人閱讀 51,763評(píng)論 1 307
  • 那天背零,我揣著相機(jī)與錄音,去河邊找鬼无埃。 笑死徙瓶,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的嫉称。 我是一名探鬼主播侦镇,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼澎埠!你這毒婦竟也來(lái)了虽缕?” 一聲冷哼從身側(cè)響起始藕,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蒲稳,失蹤者是張志新(化名)和其女友劉穎氮趋,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體江耀,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡剩胁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了祥国。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昵观。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖舌稀,靈堂內(nèi)的尸體忽然破棺而出啊犬,到底是詐尸還是另有隱情,我是刑警寧澤壁查,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布觉至,位于F島的核電站,受9級(jí)特大地震影響睡腿,放射性物質(zhì)發(fā)生泄漏语御。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一席怪、第九天 我趴在偏房一處隱蔽的房頂上張望应闯。 院中可真熱鬧,春花似錦挂捻、人聲如沸碉纺。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)惜辑。三九已至,卻和暖如春疫赎,著一層夾襖步出監(jiān)牢的瞬間盛撑,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工捧搞, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留抵卫,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓胎撇,卻偏偏與公主長(zhǎng)得像介粘,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子晚树,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

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