基于iOS平臺的最簡單的FFmpeg音頻播放器(三)

  • 在前面兩個篇章中悯搔,已經(jīng)把解碼和播放兩個重要的模塊講完了吃谣,剩下的只有一些處理中間邏輯的代碼了,看來這一篇是又要水了鲫惶,但是也是重要要的處理部分蜈首。
  • 音頻解碼邏輯和之前的視頻解碼邏輯一樣樣的,下面我們可能只會列舉出一些不相同的部分欠母。

基于iOS平臺的最簡單的FFmpeg音頻播放器(一)
基于iOS平臺的最簡單的FFmpeg音頻播放器(二)
基于iOS平臺的最簡單的FFmpeg音頻播放器(三)

正式開始

1. 初始化音頻播放器

AieAudioManager * audioManager = [AieAudioManager audioManager];
[audioManager activateAudioSession];
  • 音頻播放器理論上應(yīng)該先初始化欢策,獲取和設(shè)置硬件參數(shù)。

2.初始化解碼庫

- (void)start
{
    _path = [[NSBundle mainBundle] pathForResource:@"薛之謙 - 摩天大樓" ofType:@"m4a"];
    
    __weak AudioPlayController * weakSelf = self;
    
    AieDecoder * decoder = [[AieDecoder alloc] init];
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        NSError * error = nil;
        [decoder openFile:_path error:&error];
        
        __strong AudioPlayController * strongSelf = weakSelf;
        if (strongSelf)
        {
            dispatch_async(dispatch_get_main_queue(), ^{
                [strongSelf setMovieDecoder:decoder];
            });
        }
        
    });
}
  • 初始化解碼庫赏淌,然后使用FFmpeg相關(guān)的函數(shù)去打開文件踩寇,解析音頻文件中的流信息等。

3.開始解碼六水,并打開音頻

- (void)play
{
    // 解碼音頻 并把音頻存儲到_audioFrames
    [self asyncDecodeFrames];
    
    // 打開音頻
    [self enableAudio:YES];
}

- (void)enableAudio: (BOOL) on
{
    AieAudioManager * audioManager = [AieAudioManager audioManager];
    if (on) {
        audioManager.outputBlock = ^(float *outData, UInt32 numFrames, UInt32 numChannels) {
            [self audioCallbackFillData: outData numFrames:numFrames numChannels:numChannels];
        };
        [audioManager play];
    }
}
  • asyncDecodeFrames這個函數(shù)就就不說了俺孙,和視頻解碼的時候一毛一樣的辣卒。
  • AieAudioManager是一個單例類,所以重復(fù)獲取都是同一個對象睛榄,相比Kxmovie的音頻播放類荣茫,我把這個類簡化了許多,畢竟是最簡單的音頻播放器场靴。
  • audioManager.outputBlock這個回調(diào)就是用來接收即將解碼的數(shù)據(jù)的啡莉。

4.解碼成功,并返回音頻數(shù)據(jù)

- (void)asyncDecodeFrames
{
    __weak AudioPlayController * weakSelf = self;
    __weak AieDecoder * weakDecoder = _decoder;
    
    dispatch_async(_dispatchQueue, ^{
        
        // 當(dāng)已經(jīng)解碼的視頻總時間大于_maxBufferedDuration 停止解碼
        BOOL good = YES;
        while (good) {
            good = NO;
            
            @autoreleasepool {
                __strong AieDecoder * strongDecoder = weakDecoder;
                
                if (strongDecoder) {
                    NSArray * frames = [strongDecoder decodeFrames:0.1];
                    
                    if (frames.count) {
                        __strong AudioPlayController * strongSelf = weakSelf;
                        
                        if (strongSelf) {
                            good = [strongSelf addFrames:frames];
                        }
                    }
                }
            }
        }
    });
}
  • 這一部分是和視頻解碼的流程是一樣的旨剥,還是不做解釋了

5.緩存解碼后的音頻數(shù)據(jù)

- (BOOL) addFrames:(NSArray *)frames
{
    @synchronized (_audioFrames)
    {
        for (AieFrame * frame in frames)
        {
            if (frame.type == AieFrameTypeAudio)
            {
                [_audioFrames addObject:frame];
                
                _bufferedDuration += frame.duration;
            }
        }
    }
    return _bufferedDuration < _maxBufferedDuration;
}
  • 緩存解碼后的音頻數(shù)據(jù)咧欣,并計算出緩存中音頻數(shù)據(jù)的總時間,用來合理的控制解碼速度轨帜。

6.填充將要解碼的數(shù)據(jù)

- (void) audioCallbackFillData: (float *) outData
                     numFrames: (UInt32) numFrames
                   numChannels: (UInt32) numChannels
  • 這個函數(shù)是本文的重點(diǎn)魄咕,是一個從音頻播放器回調(diào)出來的函數(shù),然后又使用block回調(diào)到這個類蚌父。
    -numFrames是音頻數(shù)據(jù)采樣幀的數(shù)量哮兰,這個之前有說過。
  • numChannels是音頻的通道數(shù)梢什。

6.1 獲取音頻數(shù)據(jù)

    NSUInteger count = _audioFrames.count;
    
    if (count > 0) {
        
        AieAudioFrame *frame = _audioFrames[0];
        
        [_audioFrames removeObjectAtIndex:0];
        _moviePosition = frame.position;
        _bufferedDuration -= frame.duration;
        
        _currentAudioFramePos = 0;
        _currentAudioFrame = frame.samples;
    }
    
    if (!count || !(_bufferedDuration > _minBufferedDuration)) {
        [self asyncDecodeFrames];
    }
  • 以上代碼的主要作用是從_audioFrames緩存區(qū)中逐個取出數(shù)據(jù)奠蹬,然后記錄當(dāng)前的播放位置和時間朝聋,當(dāng)緩存區(qū)沒有數(shù)據(jù)或者是緩沖時間少于最小的時間的時候嗡午,就繼續(xù)解碼。

6.1 填充數(shù)據(jù)

if (_currentAudioFrame) {
    
    const void *bytes = (Byte *)_currentAudioFrame.bytes + _currentAudioFramePos;
    const NSUInteger bytesLeft = (_currentAudioFrame.length - _currentAudioFramePos);
    const NSUInteger frameSizeOf = numChannels * sizeof(float);
    const NSUInteger bytesToCopy = MIN(numFrames * frameSizeOf, bytesLeft);
    const NSUInteger framesToCopy = bytesToCopy / frameSizeOf;
    
    memcpy(outData, bytes, bytesToCopy);
    numFrames -= framesToCopy;
    outData += framesToCopy * numChannels;
    
    if (bytesToCopy < bytesLeft)
        _currentAudioFramePos += bytesToCopy;
    else
        _currentAudioFrame = nil;
    
} else {
    
    memset(outData, 0, numFrames * numChannels * sizeof(float));
    //LoggerStream(1, @"silence audio");
    break;
}
  • 如果_currentAudioFrame中存在數(shù)據(jù)冀痕,那就一幀一幀數(shù)據(jù)的存進(jìn)outData中荔睹,如果_currentAudioFrame中的音頻數(shù)據(jù)大于一幀,那就分為多次存言蛇。
  • 從這個bytesToCopy的計算方式可以看出僻他,numFrames * numChannels * sizeof(float)就是一幀的采樣數(shù)、通道數(shù)和一次采樣點(diǎn)大小的乘積就是實(shí)際上總的一幀音頻數(shù)據(jù)的大小腊尚。
  • 如果_currentAudioFrame中不存在數(shù)據(jù)吨拗,那就直接存0

結(jié)尾

  • 到這里我們關(guān)于最簡單的音頻播放器的內(nèi)容就全部結(jié)束了婿斥。
  • 由于放了FFmpeg庫劝篷,所以Demo會很大,下載的時候比較費(fèi)時民宿。
  • 謝謝閱讀
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末娇妓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子活鹰,更是在濱河造成了極大的恐慌哈恰,老刑警劉巖只估,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異着绷,居然都是意外死亡蛔钙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門荠医,熙熙樓的掌柜王于貴愁眉苦臉地迎上來夸楣,“玉大人,你說我怎么就攤上這事子漩≡バ” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵幢泼,是天一觀的道長紧显。 經(jīng)常有香客問我,道長缕棵,這世上最難降的妖魔是什么孵班? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮招驴,結(jié)果婚禮上篙程,老公的妹妹穿的比我還像新娘缕粹。我一直安慰自己叉跛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布绷跑。 她就那樣靜靜地躺著触趴,像睡著了一般氮发。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上冗懦,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天爽冕,我揣著相機(jī)與錄音,去河邊找鬼披蕉。 笑死颈畸,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的没讲。 我是一名探鬼主播眯娱,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼食零!你這毒婦竟也來了困乒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤贰谣,失蹤者是張志新(化名)和其女友劉穎娜搂,沒想到半個月后迁霎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡百宇,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年考廉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片携御。...
    茶點(diǎn)故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡昌粤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出啄刹,到底是詐尸還是另有隱情涮坐,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布誓军,位于F島的核電站袱讹,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏昵时。R本人自食惡果不足惜捷雕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望壹甥。 院中可真熱鬧救巷,春花似錦、人聲如沸句柠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽俄占。三九已至管怠,卻和暖如春淆衷,著一層夾襖步出監(jiān)牢的瞬間缸榄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工祝拯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留甚带,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓佳头,卻偏偏與公主長得像鹰贵,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子康嘉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評論 2 355

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