<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          iOS:制作簡易的 AAC 播放器 —— 了解音頻的播放流程

          共 10211字,需瀏覽 21分鐘

           ·

          2021-09-12 05:56

          ????關(guān)注后回復(fù) “進(jìn)群” ,拉你進(jìn)程序員交流群????


          作者丨千帆直播
          來源丨搜狐技術(shù)產(chǎn)品(ID:sohu-tech)

          本文字?jǐn)?shù):1872

          預(yù)計(jì)閱讀時(shí)間:8分鐘

              常用的播放文件,如 mp3、aac 都是已經(jīng)封裝的音頻格式,將它們的文件提供到系統(tǒng)音頻庫或者第三方音頻庫,如 AVPlayer、IJKPlayer 等這些框架和播放器,然后聲音就會(huì)由揚(yáng)聲器或耳機(jī)播放出來。如果讀者對(duì)這些神奇的過程有興趣,那就進(jìn)入本次的了解旅程。

                 《iOS 視音頻入門系列》

              之前的都是針對(duì)圖像,而本次來聊聊音頻。

          前言

              本次將基于 iOS 實(shí)現(xiàn)對(duì)aac(ADTS)文件的播放來講解音頻的播放流程。它將包括如下內(nèi)容,可能大伙對(duì)這些名稱有所耳聞:解封裝、解編碼等。流媒體的播放其實(shí)就是基于這些步驟來實(shí)現(xiàn)的。

           _______              ______________              ________
          |       |            |              |            |        |
          |  aac  |  demuxer   |    audio     |  decoder   | audio  |
          | file  | ---------> | encoded data | ---------> | raw    |
          |_______|            |______________|            |________|

          AAC

              aac 的應(yīng)用很廣泛,在直播 rtmp 和 http-flv 中也使用的此音頻格式。

          ?

          AAC,全稱Advanced Audio Coding,是一種專為聲音數(shù)據(jù)設(shè)計(jì)的文件壓縮格式。與MP3不同,它采用了全新的算法進(jìn)行編碼,更加高效,具有更高的“性價(jià)比”。利用AAC格式,可使人感覺聲音質(zhì)量沒有明顯降低的前提下,更加小巧。

              Demo 使用的 aac 音頻信息如下:

          Input #0, aac, from 'video.aac':
            Duration: 00:00:30.45, bitrate: 133 kb/s
            Stream #0:0: Audio: aac (LC), 44100 Hz, stereo, fltp, 133 kb/s

          解封裝

              解封裝的最重要的目的是將 音頻 的 音頻的信息 & 音頻的編碼數(shù)據(jù) 分離出來。

              而 aac 是由多個(gè) header + es body 組成。header 固定 7 個(gè)字節(jié),所以只需每次讀取 7 個(gè)字節(jié),然后再讀出 size,就能得到得到對(duì)應(yīng)塊的數(shù)據(jù),從而得到 raw 數(shù)據(jù)。

          // 讀取 header
          int head_buf_size = 7;
          int *head_buf = malloc(head_buf_size);
          fread(head_buf, 1, head_buf_size, _in_file);
              
          // 讀取 size
          int s1 = ((int)(*(((uint8_t *)head_buf) + 3))&0x3) << 11;
          int s2 = ((int)(*(((uint8_t *)head_buf) + 4))) << 3;
          int s3 = (int)(*(((uint8_t *)head_buf) + 5)) >> 5;
          int size = s1 + s2 + s3;

          // 讀取 raw
          int raw_buf_size = size - head_buf_size;
          int *raw_buf = malloc(raw_buf_size);
          fread(raw_buf, 1, raw_buf_size, _in_file);

          header 還包含其他音頻信息,它們是解碼時(shí)需要的,如采樣率、聲道數(shù)等

          int head_buf_size = 7;
          int *head_buf = malloc(head_buf_size);
          fread(head_buf, 1, head_buf_size, file);
              
          // 采樣率標(biāo)識(shí)
          int freqIdx = ((int)(*(((uint8_t *)head_buf) + 2))&0x3C) >> 2;
          // 聲道數(shù)
          int c1 = ((int)(*(((uint8_t *)head_buf) + 2))&0x1) << 2;
          int c2 = ((int)(*(((uint8_t *)head_buf) + 3))&0xC0) >> 6;
          int chanCfg = c1 + c2;
              
          // 返回
          complete(freqIdx == 3 ? 48000 : 44100, chanCfg);

              上面的格式是 aac 的格式協(xié)議制定的,只要按照要求讀取即可,就是哪幾個(gè)字節(jié),哪幾個(gè)bit 對(duì)應(yīng)的哪些內(nèi)容,它的值表示什么都是約定的。

              Demo 只讀出了 aac 的 raw、sample_rate、channels。

          解碼

              解碼的是使用 AudioConverterRef,可能有人問這里的細(xì)節(jié),但這里不會(huì)展開,因?yàn)橛悬c(diǎn)超綱,大伙可以搜搜 AAC解碼原理 之類的標(biāo)題(PS:別問,問就是不廢??)。

              而使用 AudioConverterRef 就可以輕松的完成解碼,只需配置輸入&輸出的 AudioStreamBasicDescription,然后將讀取的 raw 寫入即可得到解碼后的數(shù)據(jù)。

          // 輸入
          - (AudioStreamBasicDescription)createAACAduioDes {
              UInt32 channels = _channels;
              
              AudioStreamBasicDescription audioDes ={0};
              audioDes.mSampleRate = _sampleRate;
              audioDes.mFormatID = kAudioFormatMPEG4AAC;
              audioDes.mFormatFlags = kMPEG4Object_AAC_LC;
              audioDes.mBytesPerPacket = 0;
              audioDes.mFramesPerPacket = 1024;
              audioDes.mBytesPerFrame = 0;
              audioDes.mChannelsPerFrame = channels;
              audioDes.mBitsPerChannel = 0;
              audioDes.mReserved = 0;
              
              return audioDes;
          }

          // 輸出
          - (AudioStreamBasicDescription)createPCMAduioDes {
              UInt32 bytesPerSample = sizeof(SInt32);
              UInt32 channels = _channels;
              
              AudioStreamBasicDescription audioDes ={0};
              audioDes.mSampleRate = _sampleRate;
              audioDes.mFormatID = kAudioFormatLinearPCM;
              audioDes.mFormatFlags = kLinearPCMFormatFlagIsNonInterleaved | kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked;
              audioDes.mBytesPerPacket = bytesPerSample;
              audioDes.mFramesPerPacket = 1;
              audioDes.mBytesPerFrame = bytesPerSample;
              audioDes.mChannelsPerFrame = channels;
              audioDes.mBitsPerChannel = 8 * bytesPerSample;
              audioDes.mReserved = 0;
              
              return audioDes;
          }
          // decode
          AudioConverterFillComplexBuffer(self->_audioConverter, 
                    inputDataProc,/*輸入數(shù)據(jù)的函數(shù)*/ 
                    (__bridge void * _Nullable)(self), 
                    &ioOutputDataPacketSize, 
                    outAudioBufferList,/*構(gòu)造返回的數(shù)據(jù)*/ 
                    NULL);

              以上代碼的過程就是將上一步解封裝后的 data 轉(zhuǎn)成 AudioBufferList,再通過 AudioConverter 解碼,從輸出的 AudioBufferList 獲取 pcm 數(shù)據(jù)。

          播放

              播放使用AudioUnit & AUGraph,這是 iOS 底層的音頻框架,通過它還可以進(jìn)行很多有趣的自定義,如變音。對(duì)于 AudioUnit 的學(xué)習(xí)內(nèi)容就很多,可以自行搜索學(xué)習(xí)即可。此處播放,只需按 Demo 操作固定步驟即可。

          OSStatus status;
          status = NewAUGraph(&_auGraph);
          [self addAUNode];
          status = AUGraphOpen(_auGraph);
          [self getAUsFromNodes];
          [self setAUProperties];
          [self makeAUConnects];
          CAShow(_auGraph);
          status = AUGraphInitialize(_auGraph);

              到這一步,只要把 encode raw 給到 AudioUnit 就可以播放。此音頻播放器屬于消費(fèi)者模式,它是反向跟前面要數(shù)據(jù)來播放,如下:

           ___________              ______________  
          |           | 1.playback |              |  
          |  demuxer  | <--------- |  AudioUnit   |  
          |  decoder  | ---------> |         |  
          |___________|   2.pcm   |______________|  


          1. AudioUnit 向 decoder 要 pcm 數(shù)據(jù)
          2. decoder 將解碼數(shù)據(jù)給過去,數(shù)據(jù)夠直接提供,數(shù)據(jù)不夠向 demuxer 要
          3. demuxer 去解封裝更多源數(shù)據(jù),返回給 decoder 解碼

          // 數(shù)據(jù)不夠
          if (self.data.length < size) {
           // 解封裝
              NSData *d = [_reader read_aac_raw_buf];
              if (d == nil) {
                  return nil;
              }
              // 解碼
              AudioBufferList *b = [_decoder decodeAudioSamepleBuffer:d];
              
              // 保存
              [self.data appendBytes:b->mBuffers[0].mData length:b->mBuffers[0].mDataByteSize];
          }

          // 返回 AudioUint 需要的數(shù)據(jù)   
          NSData *b = [self.data subdataWithRange:NSMakeRange(0, size)];

              這樣處理可以避免一次性將 aac 解碼,封裝的目的就是降低文件大小,方便傳輸和保存。如果全部解碼出來,對(duì)于高音質(zhì)的音頻文件就會(huì)生成很大的文件,占用大量的內(nèi)存,并且播放時(shí),快進(jìn)或者直接結(jié)束,沒有播放的部分即浪費(fèi)空間又浪費(fèi)算力。因此需要播放的部分才去解封裝 & 解碼更好。

          ?

          這里的 video.aac 文件大概是 500kb,解碼后的 pcm 是 5.5M

          播放器 Demo

          • QHAudioConverterMan: acc 播放器
            https://gitee.com/chenqihui/qhaudio-converter-man

             Demo 代碼只供參考,暫沒優(yōu)化。

          轉(zhuǎn)碼

              由于近期直播需要字幕識(shí)別,而語音的識(shí)別對(duì)音頻格式有要求限制,搜狗和訊飛的格式是一樣的。以訊飛官方要求為例:
          ?

          訊飛語音能力一般情況下支持如下音頻格式:

          pcm(pcm_s16le),wav,speex(speex-wb) 采樣率為16000 或者 8000. 推薦使用16000,比特率為16bit 單聲道

              而這里可以使用 AudioConverterRef 進(jìn)行轉(zhuǎn)碼,讓 pcm 符合要求

              下面是 44100_32f_1 to 16000_16f_1 的波形圖:

              雖然波形圖看著差不多,但其實(shí)還是有很大的不同。導(dǎo)致轉(zhuǎn)碼失真,應(yīng)該是某些參數(shù)設(shè)置有誤差或者本人寫的讀取測(cè)試代碼有誤。由于這里只是順便寫的轉(zhuǎn)碼代碼,暫時(shí)忽略它。

              而使用 ffmpeg 命令的轉(zhuǎn)碼,如下:

              效果相當(dāng)?shù)睾冒?,肉眼可見的,不過也有小小的失真,你們切換頻譜圖看看就知道哈,這里就不展示了。
          ffmpeg -ac 1 -ar 44100 -f f32le -i of111.pcm -ac 1 -ar 16000 -f s16le ff_out.pcm

          參考

          • 音頻屬性相關(guān):聲道、采樣率、采樣位數(shù)、樣本格式、比特率https://www.cnblogs.com/yongdaimi/p/10722355.html#_label5

          • AAC ADTS格式分析

            https://zhuanlan.zhihu.com/p/162998699
          • AAC ADTS格式分析
            https://blog.csdn.net/tantion/article/details/82743942

          -End-

          最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

          點(diǎn)擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取

          在看點(diǎn)這里好文分享給更多人↓↓

          瀏覽 64
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲精品suv视频 | 樱桃视频91 | 中文字幕在线观看的视频 | av中文在线 | 欧美人兽在线 |