基于iOS平臺(tái)的最簡(jiǎn)單的FFmpeg視頻播放器(二)

上一篇寫了關(guān)于FFmpeg的對(duì)文件的處理以及初始化解碼器,算是為本片做下了很重要的基礎(chǔ)掉冶,需要看基礎(chǔ)的同學(xué)還是推薦看雷神的博客准夷。

基于iOS平臺(tái)的最簡(jiǎn)單的FFmpeg視頻播放器(一)
基于iOS平臺(tái)的最簡(jiǎn)單的FFmpeg視頻播放器(二)
基于iOS平臺(tái)的最簡(jiǎn)單的FFmpeg視頻播放器(三)
廢話不多說双抽,讓我們

正式開始

  • 粗略的來概括一下今天的內(nèi)容,分為兩步:
    1.使用上一篇文章初始化的解碼器琳要,將原始數(shù)據(jù)進(jìn)行解碼寡具。
    2.保存解碼后的數(shù)據(jù)到一個(gè)數(shù)組中。

1.1 熱身運(yùn)動(dòng)

  • 這是在解碼視頻之前的熱身運(yùn)動(dòng)
- (void)setMovieDecoder:(AieDecoder *)decoder
{
    if (decoder) {
        _decoder = decoder;
        _dispatchQueue = dispatch_queue_create("AieMovie", DISPATCH_QUEUE_SERIAL);
        _videoFrames = [NSMutableArray array];
    }
    
    _minBufferedDuration = LOCAL_MIN_BUFFERED_DURATION;
    _maxBufferedDuration = LOCAL_MAX_BUFFERED_DURATION;
    
    if (self.isViewLoaded) {
        [self setupPresentView];
    }
}
  • _dispatchQueue 手動(dòng)創(chuàng)建的一個(gè)串行隊(duì)列稚补,用于之后解碼的線程童叠。
  • _videoFrames 這個(gè)是一個(gè)用來存儲(chǔ)解碼后的數(shù)據(jù)的可變數(shù)組。
  • _minBufferedDuration课幕、_maxBufferedDuration這兩個(gè)函數(shù)是用來做什么的厦坛,我現(xiàn)在來簡(jiǎn)單解釋一下五垮,到后面有相關(guān)的代碼就了解了。其實(shí)就是用來控制是否開始解碼的兩個(gè)參數(shù)杜秸,當(dāng)小于_minBufferedDuration的時(shí)候放仗,就開始解碼,當(dāng)大于_maxBufferedDuration的時(shí)候撬碟,就停止解碼诞挨,當(dāng)處于兩者之間,那就一直解碼小作,不要停亭姥。

1.2 再次熱身(很重要)

  • 引用馬老師的話來說,這段代碼真的是顾稀,李時(shí)珍的皮
- (void)asyncDecodeFrames
{
   __weak Aie1Controller * weakSelf = self;
   __weak AieDecoder * weakDecoder = _decoder;
   
   dispatch_async(_dispatchQueue, ^{

       // 當(dāng)已經(jīng)解碼的視頻總時(shí)間大于_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 Aie1Controller * strongSelf = weakSelf;
                       
                       if (strongSelf) {
                           good = [strongSelf addFrames:frames];
                       }
                   }
               }
           }
       }
   });
}
  • 很多人看這段代碼的時(shí)候坝撑,可能看見__weak , __strong, dispatch_async, while , @autoreleasepool,組合在一起的時(shí)候就已經(jīng)蒙圈了静秆,那現(xiàn)在我們一句句來解釋。
  • 一開始我們定義了一個(gè)GCD_dispatchQueue巡李,現(xiàn)在就用到了抚笔,正因?yàn)橛玫搅?code>block,所以我們需要__weak來防止循環(huán)引用,__strong是相對(duì)應(yīng)的侨拦,因?yàn)樵?code>block中是一個(gè)延時(shí)的殊橙,持續(xù)的操作,所以如果不使用__strong的話狱从,會(huì)導(dǎo)致block中的對(duì)象被弱引用膨蛮,而提早釋放,所以需要__strong再次對(duì)block中的對(duì)象強(qiáng)引用季研。
  • while循環(huán)是為了讓解碼器可以持續(xù)的去解碼(如果不出現(xiàn)異常情況下)敞葛,就算跳出了循環(huán),還會(huì)有其他的地方調(diào)用asyncDecodeFrames与涡,再次進(jìn)入循環(huán)惹谐,所以可以一直不停解碼。
  • @autoreleasepool自動(dòng)釋放池驼卖,有些人會(huì)問氨肌,iOS項(xiàng)目main函數(shù)已經(jīng)有@autoreleasepool,為什么還要加一個(gè)呢酌畜,是不是畫蛇添足怎囚?當(dāng)然不是,我們看@autoreleasepool中的代碼檩奠,每一次的解碼都會(huì)產(chǎn)生一個(gè)數(shù)組桩了,所以如果不及時(shí)釋放的話附帽,內(nèi)存就會(huì)一直變大(視頻幀的數(shù)據(jù)量可不是開玩笑的),所以需要在這里加一個(gè)@autoreleasepool井誉。

2.1 開始解碼

  • 終于等到你蕉扮,本系列文章最重要的篇章
- (NSArray *)decodeFrames:(CGFloat)minDuration
{
    if (_videoStream == -1) {
        return nil;
    }
    
    NSMutableArray * result = [NSMutableArray array];
    AVPacket packet;
    CGFloat decodedDuration = 0;
    BOOL finished = NO;
    
    while (!finished) {
        if (av_read_frame(_formatCtx, &packet) < 0) {
            NSLog(@"讀取Frame失敗");
            break;
        }
        
        if (packet.stream_index == _videoStream) {

            int pktSize = packet.size;
            while (pktSize > 0) {
                int gotFrame = 0;
                int len = avcodec_decode_video2(_videoCodecCtx, _videoFrame, &gotFrame, &packet);
                if (len < 0) {
                    NSLog(@"解碼失敗");
                    break;
                }
                
                if (gotFrame) {
                    AieVideoFrame * frame = [self handleVideoFrame];
                    frame.type = AieFrameTypeVideo;
                    NSLog(@"當(dāng)前幀的時(shí)間戳:%f, 當(dāng)前幀的持續(xù)時(shí)間:%f", frame.position, frame.duration);
                    
                    if (frame) {
                        [result addObject:frame];
                        
                        _position = frame.position;
                        decodedDuration += frame.duration;
                        if (decodedDuration > minDuration) {
                            finished = YES;
                        }
                    }
                }
                
                if (0 == len) {
                    break;
                }
                pktSize -= len;
            }
        }
        av_free_packet(&packet);
    }
    return result;
}
  • 上面的代碼相對(duì)來說比較多一些,所以為了可以容易看一點(diǎn)颗圣,所以我們?cè)侔汛a細(xì)分一下喳钟。
  • 在解析之前我們先要弄清楚幾件事情。
    1.從哪里來?
    2.怎么來在岂?
    3.來干嘛奔则?
    4.到哪里去?
    5.怎么去蔽午?

2.1.1 從哪里來易茬?

AVPacket packet;
  • 數(shù)據(jù)就存在AVPacket里面,是解碼前的數(shù)據(jù)及老,壓縮過的數(shù)據(jù)抽莱。
  • AVPacket官方解釋是This structure stores compressed data. It is typically exported by demuxers and then passed as input to decoders, or received as output from encoders and then passed to muxers.,這次的解釋相對(duì)來說比較長(zhǎng)骄恶,說明這個(gè)是一個(gè)很重要的機(jī)構(gòu)體食铐。大致意思就是說這是一個(gè)用來存儲(chǔ)壓縮數(shù)據(jù)以及相關(guān)信息的機(jī)構(gòu)體,是一個(gè)把數(shù)據(jù)導(dǎo)入到解碼器的分配器僧鲁,也是用來接收編碼后的數(shù)據(jù)的結(jié)構(gòu)體虐呻。

2.1.2 怎么來?

if (av_read_frame(_formatCtx, &packet) < 0) {
   NSLog(@"讀取Frame失敗");
   break;
}
  • av_read_frame()作用是讀取一幀視頻幀或者是多幀音頻幀寞秃。AVPacket中的數(shù)據(jù)會(huì)一直有效斟叼,除非讀取到下一幀或者是AVFormatContext中的數(shù)據(jù)被徹底清空(調(diào)用avformat_close_input())。

2.1.3 來干嘛蜕该?(這就是最重要的解碼)

if (packet.stream_index == _videoStream) {

            int pktSize = packet.size;
            while (pktSize > 0) {
                int gotFrame = 0;
                int len = avcodec_decode_video2(_videoCodecCtx, _videoFrame, &gotFrame, &packet);
                
                if (len < 0) {
                    NSLog(@"解碼失敗");
                    break;
                }
                
                if (gotFrame) {
                }
                
                if (0 == len) {
                    break;
                }
                pktSize -= len;
                
            }
        }
  • 如果讀出的AVPacket中的流的位置和當(dāng)前這一幀的流的位置相同犁柜,那就開始解碼。
  • 接下來就開始解碼AVPacket中存儲(chǔ)的所有的數(shù)據(jù)堂淡,如果解碼成功一次就pktSize -= len;減去已經(jīng)解碼過的長(zhǎng)度馋缅,直到解碼完AVPacket中的所有數(shù)據(jù),就結(jié)束循環(huán)绢淀。
  • avcodec_decode_video2 ()就是把AVPacket中的視頻流數(shù)據(jù)解碼成圖片萤悴,解碼后的數(shù)據(jù)就存儲(chǔ)在之前定義的AVFrame中,返回的是已經(jīng)被解碼的數(shù)據(jù)的大薪缘摹(不是解碼后的數(shù)據(jù)大懈猜摹)。
  • 有興趣的朋友們可以去看看avcodec_decode_video2 ()的源碼,也是很簡(jiǎn)單易懂的硝全,以后有空可以單獨(dú)拿出來講講栖雾。

2.1.4 怎么去?

  • 到現(xiàn)在為止所有的解碼都結(jié)束了
AieVideoFrame * frame = [self handleVideoFrame];
frame.type = AieFrameTypeVideo;
  • AieVideoFrame是自定義的一個(gè)存放解碼后數(shù)據(jù)的類伟众,里面的操作就是把解碼后的數(shù)據(jù)AVFrame按照一定的格式存入自定義的frame析藕,然后再標(biāo)明它的類型。

2.1.5 去哪里凳厢?

if (frame) {
     [result addObject:frame];
     _position = frame.position;
     decodedDuration += frame.duration;
     if (decodedDuration > minDuration) {
         finished = YES;
     }
}
  • 把數(shù)據(jù)存到之前定義的數(shù)組中账胧,然后返回這個(gè)數(shù)組。
  • 最后幾句代碼我來解釋一下先紫,decodedDuration存的是當(dāng)前這個(gè)一幀中解碼后數(shù)據(jù)的時(shí)間的總和治泥,如果這個(gè)總和大于minDuration,那就停止解碼遮精。在項(xiàng)目中我傳入的是0.1居夹,意思就是我這一幀的最大時(shí)長(zhǎng)是0.1秒,如果幀長(zhǎng)度很大仑鸥,我也只解碼0.1秒的數(shù)據(jù)吮播。

結(jié)尾

  • 解碼相關(guān)的都已經(jīng)講完了,AieVideoFrame是關(guān)于顯示的類眼俊,里面的具體操作,到時(shí)候一起說粟关。
  • 由于放了FFmpeg庫疮胖,所以Demo會(huì)很大,下載的時(shí)候比較費(fèi)時(shí)闷板。
  • 謝謝閱讀
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末澎灸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子遮晚,更是在濱河造成了極大的恐慌性昭,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,331評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件县遣,死亡現(xiàn)場(chǎng)離奇詭異糜颠,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)萧求,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門其兴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人夸政,你說我怎么就攤上這事元旬。” “怎么了?”我有些...
    開封第一講書人閱讀 167,755評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵匀归,是天一觀的道長(zhǎng)坑资。 經(jīng)常有香客問我,道長(zhǎng)穆端,這世上最難降的妖魔是什么袱贮? 我笑而不...
    開封第一講書人閱讀 59,528評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮徙赢,結(jié)果婚禮上字柠,老公的妹妹穿的比我還像新娘。我一直安慰自己狡赐,他們只是感情好窑业,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,526評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著枕屉,像睡著了一般常柄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上搀擂,一...
    開封第一講書人閱讀 52,166評(píng)論 1 308
  • 那天西潘,我揣著相機(jī)與錄音,去河邊找鬼哨颂。 笑死喷市,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的威恼。 我是一名探鬼主播品姓,決...
    沈念sama閱讀 40,768評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼箫措!你這毒婦竟也來了腹备?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,664評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤斤蔓,失蹤者是張志新(化名)和其女友劉穎植酥,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弦牡,經(jīng)...
    沈念sama閱讀 46,205評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡友驮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,290評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了喇伯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片喊儡。...
    茶點(diǎn)故事閱讀 40,435評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖稻据,靈堂內(nèi)的尸體忽然破棺而出艾猜,到底是詐尸還是另有隱情买喧,我是刑警寧澤,帶...
    沈念sama閱讀 36,126評(píng)論 5 349
  • 正文 年R本政府宣布匆赃,位于F島的核電站淤毛,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏算柳。R本人自食惡果不足惜低淡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,804評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瞬项。 院中可真熱鬧蔗蹋,春花似錦、人聲如沸囱淋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,276評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽妥衣。三九已至皂吮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間税手,已是汗流浹背蜂筹。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留芦倒,地道東北人艺挪。 一個(gè)月前我還...
    沈念sama閱讀 48,818評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像兵扬,于是被迫代替她去往敵國(guó)和親闺属。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,442評(píng)論 2 359

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