上一篇寫了關(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í)闷板。
- 謝謝閱讀