• 乐彩网官网

  • 乐彩网官网

  • 乐彩网官网

  • 乐彩网官网

乐彩网官网

作者︰  發布日期︰2020-02-23 15:13:28
Tag標簽︰音頻  音樂  
  • AudioQueue簡(jian)介 AudioStreamer說明 AudioQueue詳解 AudioQueue工作原理 AudioQueue主要(yao)接口(kou) AudioQueueNewOutput AudioQueueAllocateBuffer AudioQueueEnqueueBuffer AudioQueueStart Pause Stop Flush Reset Dispose AudioQueueFreeBuffer AudioQueueGetProperty AudioQueueSetProperty 音頻播放LocalAudioPlayer 播放器的(de)初始化 播放音頻 LocalAudioPlayer相關屬性(xing) 讀取並開始解析(xi)音頻 解析(xi)音頻信息 kAudioFileStreamProperty_DataFormat kAudioFileStreamProperty_FileFormat kAudioFileStreamProperty_AudioDataByteCount kAudioFileStreamProperty_BitRate kAudioFileStreamProperty_DataOffset kAudioFileStreamProperty_AudioDataPacketCount kAudioFileStreamProperty_ReadyToProducePackets 解析(xi)音頻幀 播放音頻數(shu)據 清(qing)理相關資源 結(jie)束

    iOS實現播放本地音樂,有很多種方法,例如AVAudioPlayer,這些都能很好的(de)勝任(ren),有人就奇e)至耍  裁匆yao)退而(er)求其(qi)次,使用更復雜的(de)AudioQueue來播放本地音樂呢?請繼續往下看(kan)

    乐彩网官网

    AudioQueue,在(zai)隻(ping)果的(de)開發者文(wen)檔上是(shi)這麼說的(de)

    'Audio Queue Services provides a straightforward, low overhead way to record and play audio in iOS and Mac OS X.'

    AudioQueue官(guan)方文(wen)檔

    AudioQueue服務(wu)提供一種直ben)擁de),低開銷的(de)方式以用于在(zai)iOS及Mac OS X上錄jia)艉筒?乓衾幀/p>

    使用AudioQueue播放音樂的(de)優(you)點就是(shi)開銷很小並且(qie)支持流式jiang)?牛 呦鹵 ?   shi)缺點就是(shi)開發難(nan)度大,所以有xing)繅羝悼udioStreamer,網上有很多講(jiang)AudioQueue的(de),但是(shi)有實例代(dai)碼(ma)說明的(de),實在(zai)是(shi)少之(zhi)又少,正(zheng)好公司項目有音頻需求,雖然項目中使用的(de)並非我(wo)自己寫的(de)音頻播放功(gong)能,但事後還是(shi)想自己來研究一下,這個(ge)在(zai)我(wo)看(kan)來比較神奇也(ye)比較有趣的(de)AudioQueue。

    乐彩网官网

    iOS上一個(ge)比較有名的(de)流媒體音頻播放庫是(shi)AudioStreamer,該mei)餳ji)使用了AudioQueue,不過該音頻庫並不支持本地音樂播放,我(wo)感覺很奇e)鄭  裁醋髡 恢?幀6er)且(qie)在(zai)使用過程中,我(wo)發現該mei)食故shi)有點問題,雖然我(wo)對音頻方面的(de)知識並不怎麼了解,也(ye)並不能與(yu)大師媲美並論,但我(wo)也(ye)希望通過自己的(de)學習(xi),最(zui)終完成一個(ge)類(lei)似AudioStreamer的(de)網絡音樂庫,目前也(ye)許(xu)只是(shi)qie)桓ge)設想,不管最(zui)後自己有沒(mei)有那(na)能力,起(qi)碼(ma)我(wo)曾(zeng)經也(ye)嘗試過,不過工作最(zui)近(jin)比較忙,加上自己知識的(de)na)qian)缺,不知何(he)時才能實現。本次就先來補上AudioStreamer沒(mei)有支持的(de),使用AudioQueue播放本地音樂。

    乐彩网官网

    AudioQueue工作原理

    我(wo)從(cong)Apple的(de)官(guan)方文(wen)檔上截下以下該圖(tu)︰
    AudioQueue

    該圖(tu)很好的(de)說明了AudioQueue的(de)工作原理,如下說明︰
    1. 用mei)?diao)用相應的(de)方法,將音頻數(shu)據從(cong)硬盤中讀入到(dao)AudioQueue的(de)緩沖區中,並將緩沖區送入音頻隊(dui)列(lie)。
    2. 用mei)pp通過AudioQueue提供的(de)接口(kou),告訴外放設備,緩沖區中已經有數(shu)據,可以yue)萌??擰br />3. 當(dang)一個(ge)緩沖區中的(de)音頻數(shu)據播放完畢之(zhi)後,AudioQueue告訴用mei)? dang)前有一個(ge)空的(de)緩沖區可以用來給你填充數(shu)據。
    4. 重復以上步驟,直至數(shu)據播放完畢。

    到(dao)這里(li),肯定有不少同學發現了,AudioQueue其(qi)實就是(shi)生產者消費者模(mo)型(xing)的(de)典型(xing)應用。

    AudioQueue主要(yao)接口(kou)

    乐彩网官网

    OSStatus AudioQueueNewOutput(const AudioStreamBasicDescription *inFormat, AudioQueueOutputCallback inCallbackProc, void *inUserData, CFRunLoopRef inCallbackRunLoop, CFStringRef inCallbackRunLoopMode, UInt32 inFlags, AudioQueueRef _Nullable *outAQ);

    該方法用于創建一個(ge)用于輸出音頻的(de)AudioQueue

    參數(shu)及返回說明如下︰
    1. inFormat︰該參數(shu)指明了即(ji)將播放的(de)音頻的(de)數(shu)據格(ge)式
    2. inCallbackProc︰該mei)氐diao)用于當(dang)AudioQueue已使用完一個(ge)緩沖區時通知用mei)? 沒(mei)?梢約絛畛湟羝凳shu)據
    3. inUserData︰由用mei)? 氳de)數(shu)據指針,用于傳遞(di)給回調(diao)函數(shu)
    4. inCallbackRunLoop︰指明回調(diao)事件發生在(zai)哪個(ge)RunLoop之(zhi)中,如果傳遞(di)NULL,表示(shi)在(zai)AudioQueue所在(zai)的(de)nan)叱躺現蔥懈沒(mei)氐diao)事件,一般(ban)情(qing)況下,傳遞(di)NULL即(ji)可。
    5. inCallbackRunLoopMode︰指明回調(diao)事件發生的(de)RunLoop的(de)模(mo)式,傳遞(di)NULL相當(dang)于kCFRunLoopCommonModes,通常情(qing)況下傳遞(di)NULL即(ji)可
    6. outAQ︰該AudioQueue的(de)引用實例,

    返回OSStatus,如果值為noErr,則表示(shi)沒(mei)有錯誤(wu),AudioQueue創建成功(gong)。

    乐彩网官网

    OSStatus AudioQueueAllocateBuffer(AudioQueueRef inAQ, UInt32 inBufferByteSize, AudioQueueBufferRef _Nullable *outBuffer);

    該方法的(de)作用是(shi)為存放音頻數(shu)據的(de)緩沖區開闢(bi)空間

    參數(shu)及返回說明如下︰
    1. inAQ︰AudioQueue的(de)引用實例
    2. inBufferByteSize︰需要(yao)開闢(bi)的(de)緩沖區的(de)大小
    3. outBuffer︰開闢(bi)的(de)緩沖區的(de)引用實例

    返回OSStatus,如果值為noErr,則表示(shi)緩沖區開闢(bi)成功(gong)。

    乐彩网官网

    OSStatus AudioQueueEnqueueBuffer(AudioQueueRef inAQ, AudioQueueBufferRef inBuffer, UInt32 inNumPacketDescs, const AudioStreamPacketDescription *inPacketDescs);

    該方法用于將已經填充數(shu)據的(de)AudioQueueBuffer入隊(dui)到(dao)AudioQueue

    參數(shu)及返回說明如下︰
    1. inAQ︰AudioQueue的(de)引用實例
    2. inBuffer︰需要(yao)入隊(dui)的(de)緩沖區實例
    3. inNumPacketDescs︰緩沖區中共存在(zai)有多少幀音頻數(shu)據
    4. inPacketDescs︰緩沖區中每(mei)一幀的(de)nan)喙匭畔  沒(mei) 枰yao)指明其(qi)中每(mei)一幀在(zai)緩沖區中數(shu)據的(de)偏移值,通過字段mStartOffset來指定

    返回OSStatus,如果值為noErr,則表示(shi)緩沖區已經成功(gong)入隊(dui)。等待播放

    乐彩网官网

    OSStatus AudioQueueStart(AudioQueueRef inAQ, const AudioTimeStamp *inStartTime);OSStatus AudioQueuePause(AudioQueueRef inAQ);OSStatus AudioQueueStop(AudioQueueRef inAQ, Boolean inImmediate);OSStatus AudioQueueFlush(AudioQueueRef inAQ);OSStatus AudioQueueReset(AudioQueueRef inAQ);OSStatus AudioQueueDispose(AudioQueueRef inAQ, Boolean inImmediate);

    顧名思jia)yi),前三個(ge)方法用于音頻的(de)播放,暫(zan)停及停止。後兩個(ge)方法用于在(zai)最(zui)後清(qing)洗及重置音頻隊(dui)列(lie),清(qing)洗確保隊(dui)列(lie)中的(de)數(shu)據完全輸出。AudioQueuDispose用于清(qing)理AudioQueue所佔資源chu)/p>

    參數(shu)及返回說明如下︰
    1. inAQ︰AudioQueue的(de)引用實例
    2. inStartTime︰指明要(yao)開始播放音頻的(de)時間,如果要(yao)立即(ji)開始,傳遞(di)NULL
    3. inImmediate︰指明是(shi)否要(yao)立即(ji)停止音頻播放,如是(shi),傳遞(di)true

    返回OSStatus表示(shi)相關的(de)操作是(shi)否成功(gong)執行。

    乐彩网官网

    OSStatus AudioQueueFreeBuffer(AudioQueueRef inAQ, AudioQueueBufferRef inBuffer);

    該方法用于在(zai)播放結(jie)束時,釋放清(qing)理緩沖區時bi)褂/p>

    乐彩网官网

    OSStatus AudioQueueGetProperty(AudioQueueRef inAQ, AudioQueuePropertyID inID, void *outData, UInt32 *ioDataSize);OSStatus AudioQueueSetProperty(AudioQueueRef inAQ, AudioQueuePropertyID inID, const void *inData, UInt32 inDataSize);

    此GET/SET方法,用于設置mei)袢udioQueue的(de)nan)喙厥糶xing),請參看(kan)AudioQueue.h頭文(wen)件中的(de)nan)喙廝得鰲/p>

    乐彩网官网

    使用AudioQueue播放音樂,一般(ban)需要(yao)配合AudioFileStream一起(qi),AudioFileStream負責解析(xi)音頻數(shu)據,AudioQueue負責播放解析(xi)到(dao)的(de)音頻數(shu)據。

    此次僅實現最(zui)基本的(de)本地音頻播放功(gong)能,旨在(zai)為以後打(da)下基礎(chu),不處理任(ren)何(he)相關的(de)狀(zhuang)態(如暫(zan)停、停止、SEEK),錯誤(wu)等。播放方式類(lei)似流式jiang)?牛 皇shi)qie)羝凳shu)據來源cong)詒鏡匚wen)件而(er)非網絡,需經過以下幾個(ge)步驟︰
    1. 持續不斷的(de)從(cong)文(wen)件中讀取部(bu)分(fen)數(shu)據,直到(dao)數(shu)據全部(bu)讀取結(jie)束
    2. 將文(wen)件中讀出的(de)數(shu)據,交(jiao)給AudioFileStream進行數(shu)據解析(xi)
    3. 創建AudioQueue,當(dang)bi)shu)據放入到(dao)AudioQueueBuffer中
    4. 將緩沖區放到(dao)到(dao)AudioQueue中,開始播放音頻
    5. 播放音頻dao)  qing)理相關資源

    播放器的(de)初始化

    播放器的(de)init主要(yao)用于指定要(yao)播放的(de)音頻文(wen)件,如下所示(shi)︰
    init方法
    讀取文(wen)件操作,使用NSFileHandle類(lei),audioInUseLock,是(shi)qie)桓ge)NSLock*類(lei)型(xing),用于在(zai)AudioQueue通知我(wo)們有空的(de)緩沖區可以使用時做標記。

    我(wo)們在(zai)用mei)?慊lay按鈕的(de)時候,初始化該播放器,並調(diao)用play方法進行播放
    初始化播放器實例

    播放音頻

    播放音頻dao) fen)為以下步驟︰
    1. 讀取並開始解析(xi)音頻
    2. 解析(xi)音頻信息
    3. 解析(xi)音頻幀
    4. 播放音頻數(shu)據
    5. 清(qing)理相關資源

    我(wo)們先定義(yi)幾個(ge)宏,用于指定一些緩沖區的(de)大小

    #define kNumberOfBuffers 3  //AudioQueueBuffer數(shu)量,一般(ban)指明為3#define kAQBufSize 128 * 1024  //每(mei)個(ge)AudioQueueBuffer的(de)大小#define kAudioFileBufferSize 2048 //文(wen)件讀取數(shu)據的(de)緩沖區大小#define kMaxPacketDesc 512  //最(zui)大的(de)AudioStreamPacketDescription個(ge)數(shu)

    乐彩网官网

    LocalAudioPlayer中定義(yi)的(de)屬性(xing)如下所示(shi)︰
    LocalAudioPlayer的(de)��nan)喙厥糶��xing)

    乐彩网官网

    我(wo)們使用AudioFileStream來解析(xi)音頻信息,在(zai)用mei)?diao)用play方法之(zhi)後,首先調(diao)用AudioFileStreamOpen,打(da)開AudioFileStream,如下所示(shi)︰
    打(da)開AudioFileStream

    extern OSStatus AudioFileStreamOpen (void *inClientData, AudioFileStream_PropertyListenerProc inPropertyListenerProc, AudioFileStream_PacketsProc  inPacketsProc, AudioFileTypeID    inFileTypeHint, AudioFileStreamID * outAudioFileStream);

    AudioFileStreamOpen的(de)參數(shu)說明如下︰
    1. inClientData︰用mei)?付 de)數(shu)據,用于傳遞(di)給回調(diao)函數(shu),這里(li)我(wo)們指定(__bridge LocalAudioPlayer*)self
    2. inPropertyListenerProc︰當(dang)ben) xi)到(dao)一個(ge)音頻信息時,將回調(diao)該方法
    3. inPacketsProc︰當(dang)ben) xi)到(dao)一個(ge)音頻幀時,將回調(diao)該方法
    4. inFileTypeHint︰指明音頻數(shu)據的(de)格(ge)式,如果你不知道音頻數(shu)據的(de)格(ge)式,可以傳0
    5. outAudioFileStream︰AudioFileStreamID實例,需保存e)└笮褂/p>

    讀取到(dao)數(shu)據之(zhi)後,調(diao)用AudioFileStreamParseBytes解析(xi)數(shu)據,其(qi)原型(xing)如下︰

    extern OSStatusAudioFileStreamParseBytes(AudioFileStreamID inAudioFileStream, UInt32 inDataByteSize, const void * inData, AudioFileStreamParseFlags inFlags);

    參數(shu)的(de)說明如下︰
    1. inAudioFileStream︰AudioFileStreamID實例,由AudioFileStreamOpen打(da)開
    2. inDataByteSize︰此次解析(xi)的(de)數(shu)據字節大小
    3. inData︰此次解析(xi)的(de)數(shu)據大小
    4. inFlags︰數(shu)據解析(xi)標志(zhi),其(qi)中只有一個(ge)值kAudioFileStreamParseFlag_Discontinuity = 1,表示(shi)解析(xi)的(de)數(shu)據是(shi)否是(shi)不連續的(de),目前我(wo)們可以傳0。

    當(dang)文(wen)件數(shu)據合部(bu)讀取結(jie)束的(de)時候,此時便可以關閉文(wen)件。

    乐彩网官网

    如果解析(xi)到(dao)音頻信息,那(na)麼將會調(diao)用之(zhi)前指定的(de)回調(diao)函數(shu),如下所示(shi)︰
    解析(xi)音頻信息1
    解析(xi)音頻信息2

    每(mei)個(ge)相關的(de)屬性(xing)都可以調(diao)用AudioFileStreamGetProperty來獲取到(dao)相應的(de)值,原型(xing)如下︰

    extern OSStatusAudioFileStreamGetProperty(AudioFileStreamID inAudioFileStream, AudioFileStreamPropertyID inPropertyID, UInt32 *ioPropertyDataSize, void *    outPropertyData);

    參數(shu)說明︰
    1. inAudioFileStream︰AudioFileStreamID實例,由AudioFileStreamOpen打(da)開
    2. inPropertyID︰要(yao)獲取的(de)屬性(xing)名稱(chen),參見AudioFileStream.h
    3. ioPropertyDataSize︰指明該屬性(xing)的(de)大小
    4. outPropertyData︰用于存放該屬性(xing)值的(de)空間

    kAudioFileStreamProperty_DataFormat

    該屬性(xing)指明了音頻數(shu)據的(de)格(ge)式信息,返回的(de)數(shu)據是(shi)qie)桓ge)AudioStreamBasicDescription結(jie)構。需保存用于AudioQueue的(de)使用

    kAudioFileStreamProperty_FileFormat

    該屬性(xing)指明了音頻數(shu)據的(de)編(bian)碼(ma)格(ge)式,如MPEG等。

    kAudioFileStreamProperty_AudioDataByteCount

    該屬性(xing)可獲取到(dao)音頻數(shu)據的(de)長度,可用于計算音頻時長,計算公式為︰
    時長 = (音頻數(shu)據字節大小 * 8) / 采樣率

    kAudioFileStreamProperty_BitRate

    該屬性(xing)可獲取到(dao)音頻的(de)采樣率,可用于計算音頻時長

    kAudioFileStreamProperty_DataOffset

    該屬性(xing)指明了音頻數(shu)據在(zai)整個(ge)音頻文(wen)件中的(de)偏移量︰
    音頻文(wen)件總大小 = 偏移量 + 音頻數(shu)據字節大小

    kAudioFileStreamProperty_AudioDataPacketCount

    該屬性(xing)指明了音頻文(wen)件中共有多少幀

    kAudioFileStreamProperty_ReadyToProducePackets

    該屬性(xing)告訴我(wo)們,已經解析(xi)到(dao)完整的(de)音頻幀數(shu)據,準備產生音頻幀,之(zhi)後會調(diao)用到(dao)另外一個(ge)回調(diao)函數(shu),我(wo)們在(zai)這里(li)創建音頻隊(dui)列(lie)AudioQueue,如果音頻數(shu)據中有Magic Cookie Data,則先調(diao)用AudioFileStreamGetPropertyInfo,獲取該數(shu)據是(shi)否可寫,如果可寫再(zai)取出該屬性(xing)值,並寫入到(dao)AudioQueue。之(zhi)後便是(shi)qie)羝凳shu)據幀的(de)解析(xi)。

    乐彩网官网

    音頻信息解析(xi)完畢之(zhi)後,就應該解析(xi)音頻數(shu)據幀了,代(dai)碼(ma)如下所示(shi)︰

    解析(xi)音頻數(shu)據幀1
    在(zai)這里(li),我(wo)們利用之(zhi)前設置的(de)inClientData,將該mei)氐diao)函數(shu),由C語(yu)言形(xing)式改為Objc的(de)形(xing)式,解析(xi)到(dao)音頻數(shu)據之(zhi)後的(de)處理代(dai)碼(ma)如下所示(shi)︰
    解析(xi)音頻數(shu)據幀2

    解析(xi)到(dao)音頻數(shu)據之(zhi)後,我(wo)們穴ky"http://www.it165.net/qq/" target="_blank" class="keylink">qq9q8r9vt3QtMjrtb1BdWRpb1F1ZXVlQnVmZmVy1tCjrMrXz8ijrLjDu9i197qvyv21xNSt0M3I58/Cy/nKvqO6PC9wPgo8cHJlIGNsYXNzPQ=="brush:java;">typedef void (*AudioFileStream_PacketsProc)(void * inClientData, UInt32 inNumberBytes,UInt32 inNumberPackets, const void *inInputData, AudioStreamPacketDescription *inPacketDescriptions);

    參數(shu)說明︰
    1. inClientData︰由AudioFileStreamOpen設置的(de)用mei) shu)據
    2. inNumberBytes︰音頻數(shu)據的(de)字節數(shu)
    3. inNumberPackets︰hang) xi)到(dao)的(de)音頻幀數(shu)
    4. inInputData︰包含這些音頻數(shu)據幀的(de)數(shu)據
    5. inPacketDescriptions︰AudioStreamPacketDescription數(shu)組,其(qi)中包含mStartOffset,指明了該幀相關數(shu)據的(de)起(qi)始位置,mDataByteSize指明了該幀數(shu)據的(de)大小。

    此時我(wo)們首先創建一個(ge)音頻隊(dui)列(lie),用以播放音頻,並為每(mei)個(ge)緩沖區分(fen)配空間,如下所示(shi)︰
    創建音頻隊(dui)列(lie)

    之(zhi)後我(wo)們遍歷(li)每(mei)一幀,獲取每(mei)一幀的(de)偏移位置及數(shu)據大小,如果當(dang)前幀的(de)數(shu)據大小,已經無法存放到(dao)當(dang)前的(de)緩沖區當(dang)中,此時,我(wo)們應該將當(dang)前的(de)緩沖區入隊(dui),修改當(dang)前使用的(de)緩沖區為下一個(ge),並重置相關的(de)數(shu)據填充信息及已包含的(de)數(shu)據幀信息。

    self.audioQueueCurrentBufferIndex = (++self.audioQueueCurrentBufferIndex) % kNumberOfBuffers;self.audioPacketsFilled = 0;self.audioDataBytesFilled = 0;

    如果此時bei)姑mei)有開始播放音樂,則可以開始播放音樂

    if(self.isPlaying == NO) { err = AudioQueueStart(audioQueueRef, NULL); self.isPlaying = YES;}

    若(ruo)下一個(ge)指定的(de)緩沖區已經在(zai)使用,即(ji)全部(bu)緩沖區已滿且(qie)入隊(dui),則應該進行等待

    while(inuse[self.audioQueueCurrentBufferIndex]);

    最(zui)後,如果緩沖區空間還能存放一幀數(shu)據,我(wo)們就使用memcpy將數(shu)據復制到(dao)緩沖區中相應的(de)位置上去,保存每(mei)一幀的(de)nan)喙匭畔  柚妹mei)一幀在(zai)緩沖區中的(de)偏移量(mStartOffset),設置當(dang)前緩沖區已存方的(de)數(shu)據大小,及已包含的(de)幀量。

    當(dang)一個(ge)緩沖區使用結(jie)束之(zhi)後,AudioQueue將會調(diao)用之(zhi)前由AudioQueueNewOutput設置的(de)回調(diao)函數(shu),如下所示(shi)︰

    空緩沖區回調(diao)
    在(zai)該mei)氐diao)中,我(wo)們遍歷(li)每(mei)個(ge)緩沖區,找到(dao)空緩沖區,將該mei)撼邇de)標記修改為未使用。

    乐彩网官网

    在(zai)處理數(shu)據幀的(de)時候,如果緩沖區已經滿(指緩沖區的(de)空間不足以存放下一幀數(shu)據),則此時可以開始播放音頻

    if(self.isPlaying == NO) { err = AudioQueueStart(audioQueueRef, NULL); self.isPlaying = YES;}

    我(wo)們還可以調(diao)用AudioQueuePause等相關方法來暫(zan)停和終止音頻播放,如下所示(shi)︰

    暫(zan)停和終止播放

    乐彩网官网

    最(zui)終,我(wo)們清(qing)理相關資源,在(zai)前面,我(wo)們利用了AudioQueueAddPropertyListener為kAudioQueueProperty_IsRunning屬性(xing)設置了一個(ge)監听器,當(dang)AudioQueue啟動或者是(shi)終止的(de)時候,會調(diao)用該函數(shu)︰
    設置監听器

    該mei)氐diao)函數(shu),如下所示(shi)︰
    AudioQueue啟動或終止回調(diao)

    在(zai)該方法中,我(wo)們使用AudioQueueReset重置播放隊(dui)列(lie),調(diao)用AudioQueueFreeBuffer釋放緩沖區空間,釋放AudioQueue所有資源,關閉AudioFileStream。

    不過在(zai)實際使用過程中,我(wo)發現數(shu)據通ky"http://www.it165.net/qq/" target="_blank" class="keylink">qq/1bXEyrG68kF1ZGlvUXVldWWyorK7u+HW97av1tXWuaOsvLSyu7vh1ve2r7X308O4w7vYtfejrMv50tTO0s/ro6zTprjDysfSqs7Sw8fX1Ly6u/HIobW9tbHHsLKlt8W1xL34tsijrLWxsqW3xc3qsc+1xMqxuvK199PDQXVkaW9RdWV1ZVN0b3DW1da5sqW3xbDJo6zV4rj2zsrM4sH018W0/dLUuvPU2bzM0PjR0L6/oaM8L3A+CjxoMyBpZD0="結(jie)束">結(jie)束

    終于完成了全部(bu)的(de)代(dai)碼(ma),用mei)?灰yao)點擊play,即(ji)可以听到(dao)《遙遠的(de)她》這首美妙(miao)的(de)音樂了。由于界(jie)面上只有一個(ge)play按鈕,不放演示(shi)圖(tu)了,戴上耳(er)機(ji),靜靜的(de)nan)硎shou)自己的(de)成果與(yu)這美妙(miao)的(de)音樂。。

About IT165 -廣告服務(wu) -隱(yin)私聲明 -版權申(shen)明 -免責條(tiao)款(kuan) -安徽彩票官网網站地圖(tu) -網友投稿 -聯系方式
本站內容來自于互聯網,僅供用于網絡ke)ji)術學習(xi),學習(xi)中請遵循相關法律(lv)法規(gui)
乐彩网官网 | 下一页