kxmovie 源碼分析(1)

上一篇讓我們能運(yùn)行起來(lái)kxmovie 項(xiàng)目.接下來(lái)我們就應(yīng)該分析下如何播放音頻和視頻了.


文件概覽

KxMovieViewController.h
KxMovieViewController.m
KxMovieGLView.h
KxMovieGLView.m
KxMovieDecoder.h
KxMovieDecoder.m
KxAudioManager.h
KxAudioManager.m
KxLogger.h


類(lèi)概覽

KxMovieViewController
KxMovieFrame
KxAudioFrame
KxVideoFrame
KxVideoFrameRGB
KxVideoFrameYUV
KxArtworkFrame
KxSubtitleFrame
KxMovieDecoder
KxMovieSubtitleASSParser
KxAudioManager
KxAudioManagerImpl
KxMovieGLView
KxMovieGLRenderer_RGB
KxMovieGLRenderer_YUV


類(lèi)結(jié)構(gòu)圖

KxMovie結(jié)構(gòu)圖.png

類(lèi)分析

KxMovieViewController

屬性和變量

類(lèi)圖.png

該類(lèi)相當(dāng)于播放器,屬性比較多,比較龐大.

入口方法

+(id) movieViewControllerWithContentPath: (NSString *) path
parameters: (NSDictionary *) parameters;
該類(lèi)是通過(guò)該方法進(jìn)行創(chuàng)建類(lèi)的

static NSMutableDictionary * gHistory; 
+ (void)initialize
{
    if (!gHistory)
        gHistory = [NSMutableDictionary dictionary];
}

該類(lèi)在加載的時(shí)候,初始化了一個(gè)全局變量gHistory

+ (id) movieViewControllerWithContentPath: (NSString *) path
                               parameters: (NSDictionary *) parameters
{    
    id<KxAudioManager> audioManager = [KxAudioManager audioManager];
    [audioManager activateAudioSession];    
    return [[KxMovieViewController alloc] initWithContentPath: path parameters: parameters];
}

初始化的時(shí)候,我們初始化了類(lèi)KxAudioManager.該類(lèi)是單利類(lèi).
接著調(diào)用方法 - (id) initWithContentPath: (NSString *) path parameters: (NSDictionary *) parameters



- (id) initWithContentPath: (NSString *) path
                parameters: (NSDictionary *) parameters
{
    NSAssert(path.length > 0, @"empty path");
    
    self = [super initWithNibName:nil bundle:nil];
    if (self) {
        
        _moviePosition = 0;
//        self.wantsFullScreenLayout = YES;

        _parameters = parameters;
        
        __weak KxMovieViewController *weakSelf = self;
        
        KxMovieDecoder *decoder = [[KxMovieDecoder alloc] init];
        
        decoder.interruptCallback = ^BOOL(){
            
            __strong KxMovieViewController *strongSelf = weakSelf;
            return strongSelf ? [strongSelf interruptDecoder] : YES;
        };
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
    
            NSError *error = nil;
            [decoder openFile:path error:&error];
                        
            __strong KxMovieViewController *strongSelf = weakSelf;
            if (strongSelf) {
                
                dispatch_sync(dispatch_get_main_queue(), ^{
                    
                    [strongSelf setMovieDecoder:decoder withError:error];                    
                });
            }
        });
    }
    return self;
}

1 .該類(lèi)初始化了KxMovieDecoder(解碼器).并且設(shè)置是否需要中斷回調(diào)函數(shù).
2.開(kāi)啟全局線程 解碼器打開(kāi)文件
3 調(diào)用- (void) setMovieDecoder: (KxMovieDecoder *) decoder withError: (NSError *) error
4 .返回 self

- (void) setMovieDecoder: (KxMovieDecoder *) decoder
               withError: (NSError *) error
{
    LoggerStream(2, @"setMovieDecoder");
            
    if (!error && decoder) {
        
        _decoder        = decoder;
        _dispatchQueue  = dispatch_queue_create("KxMovie", DISPATCH_QUEUE_SERIAL);
        _videoFrames    = [NSMutableArray array];
        _audioFrames    = [NSMutableArray array];
        
        if (_decoder.subtitleStreamsCount) {
            _subtitles = [NSMutableArray array];
        }
    
        if (_decoder.isNetwork) {
            
            _minBufferedDuration = NETWORK_MIN_BUFFERED_DURATION;
            _maxBufferedDuration = NETWORK_MAX_BUFFERED_DURATION;
            
        } else {
            
            _minBufferedDuration = LOCAL_MIN_BUFFERED_DURATION;
            _maxBufferedDuration = LOCAL_MAX_BUFFERED_DURATION;
        }
        
        if (!_decoder.validVideo)
            _minBufferedDuration *= 10.0; // increase for audio
                
        // allow to tweak some parameters at runtime
        if (_parameters.count) {
            
            id val;
            
            val = [_parameters valueForKey: KxMovieParameterMinBufferedDuration];
            if ([val isKindOfClass:[NSNumber class]])
                _minBufferedDuration = [val floatValue];
            
            val = [_parameters valueForKey: KxMovieParameterMaxBufferedDuration];
            if ([val isKindOfClass:[NSNumber class]])
                _maxBufferedDuration = [val floatValue];
            
            val = [_parameters valueForKey: KxMovieParameterDisableDeinterlacing];
            if ([val isKindOfClass:[NSNumber class]])
                _decoder.disableDeinterlacing = [val boolValue];
            
            if (_maxBufferedDuration < _minBufferedDuration)
                _maxBufferedDuration = _minBufferedDuration * 2;
        }
        
        LoggerStream(2, @"buffered limit: %.1f - %.1f", _minBufferedDuration, _maxBufferedDuration);
        
        if (self.isViewLoaded) {
            
            [self setupPresentView];
            
            _progressLabel.hidden   = NO;
            _progressSlider.hidden  = NO;
            _leftLabel.hidden       = NO;
            _infoButton.hidden      = NO;
            
            if (_activityIndicatorView.isAnimating) {
                
                [_activityIndicatorView stopAnimating];
                // if (self.view.window)
                [self restorePlay];
            }
        }
        
    } else {
        
         if (self.isViewLoaded && self.view.window) {
        
             [_activityIndicatorView stopAnimating];
             if (!_interrupted)
                 [self handleDecoderMovieError: error];
         }
    }
}

該函數(shù)根據(jù)解碼器解析數(shù)據(jù)的結(jié)構(gòu)設(shè)置相關(guān)變量
在該函數(shù)里調(diào)用- (void) setupPresentView.該函數(shù)是設(shè)置播放視頻的view的
- (void) restorePlay 函數(shù)是對(duì)視頻進(jìn)行播放

- (void) setupPresentView
{
    CGRect bounds = self.view.bounds;
    
    if (_decoder.validVideo) {
        _glView = [[KxMovieGLView alloc] initWithFrame:bounds decoder:_decoder];
    } 
    
    if (!_glView) {
        
        LoggerVideo(0, @"fallback to use RGB video frame and UIKit");
        [_decoder setupVideoFrameFormat:KxVideoFrameFormatRGB];
        _imageView = [[UIImageView alloc] initWithFrame:bounds];
        _imageView.backgroundColor = [UIColor blackColor];
    }
    
    UIView *frameView = [self frameView];
    frameView.contentMode = UIViewContentModeScaleAspectFit;
    frameView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleBottomMargin;
    
    [self.view insertSubview:frameView atIndex:0];
        
    if (_decoder.validVideo) {
    
        [self setupUserInteraction];
    
    } else {
       
        _imageView.image = [UIImage imageNamed:@"kxmovie.bundle/music_icon.png"];
        _imageView.contentMode = UIViewContentModeCenter;
    }
    
    self.view.backgroundColor = [UIColor clearColor];
    
    if (_decoder.duration == MAXFLOAT) {
        
        _leftLabel.text = @"\u221E"; // infinity
        _leftLabel.font = [UIFont systemFontOfSize:14];
        
        CGRect frame;
        
        frame = _leftLabel.frame;
        frame.origin.x += 40;
        frame.size.width -= 40;
        _leftLabel.frame = frame;
        
        frame =_progressSlider.frame;
        frame.size.width += 40;
        _progressSlider.frame = frame;
        
    } else {
        
        [_progressSlider addTarget:self
                            action:@selector(progressDidChange:)
                  forControlEvents:UIControlEventValueChanged];
    }
    
    if (_decoder.subtitleStreamsCount) {
        
        CGSize size = self.view.bounds.size;
        
        _subtitlesLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, size.height, size.width, 0)];
        _subtitlesLabel.numberOfLines = 0;
        _subtitlesLabel.backgroundColor = [UIColor clearColor];
        _subtitlesLabel.opaque = NO;
        _subtitlesLabel.adjustsFontSizeToFitWidth = NO;
        _subtitlesLabel.textAlignment = NSTextAlignmentCenter;
        _subtitlesLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
        _subtitlesLabel.textColor = [UIColor whiteColor];
        _subtitlesLabel.font = [UIFont systemFontOfSize:16];
        _subtitlesLabel.hidden = YES;

        [self.view addSubview:_subtitlesLabel];
    }
}

該函數(shù)就是設(shè)置播放視頻的view和UI繪制

- (void) restorePlay
{
    NSNumber *n = [gHistory valueForKey:_decoder.path];
    if (n)
        [self updatePosition:n.floatValue playMode:YES];
    else
        [self play];
}

播放視頻

-(void) play
{
    if (self.playing)
        return;
    
    if (!_decoder.validVideo &&
        !_decoder.validAudio) {
        
        return;
    }
    
    if (_interrupted)
        return;

    self.playing = YES;
    _interrupted = NO;
    _disableUpdateHUD = NO;
    _tickCorrectionTime = 0;
    _tickCounter = 0;

#ifdef DEBUG
    _debugStartTime = -1;
#endif

    [self asyncDecodeFrames];
    [self updatePlayButton];

    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC);
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
        [self tick];
    });

    if (_decoder.validAudio)
        [self enableAudio:YES];

    LoggerStream(1, @"play movie");
}

該函數(shù) 調(diào)用asyncDecodeFrames 解碼視頻
調(diào)用- (void) enableAudio: (BOOL) on 解碼音頻

- (void) asyncDecodeFrames
{
    if (self.decoding)
        return;
    
    __weak KxMovieViewController *weakSelf = self;
    __weak KxMovieDecoder *weakDecoder = _decoder;
    
    const CGFloat duration = _decoder.isNetwork ? .0f : 0.1f;
    
    self.decoding = YES;
    dispatch_async(_dispatchQueue, ^{
        
        {
            __strong KxMovieViewController *strongSelf = weakSelf;
            if (!strongSelf.playing)
                return;
        }
        
        BOOL good = YES;
        while (good) {
            
            good = NO;
            
            @autoreleasepool {
                
                __strong KxMovieDecoder *decoder = weakDecoder;
                
                if (decoder && (decoder.validVideo || decoder.validAudio)) {
                    
                    NSArray *frames = [decoder decodeFrames:duration];
                    if (frames.count) {
                        
                        __strong KxMovieViewController *strongSelf = weakSelf;
                        if (strongSelf)
                            good = [strongSelf addFrames:frames];
                    }
                }
            }
        }
                
        {
            __strong KxMovieViewController *strongSelf = weakSelf;
            if (strongSelf) strongSelf.decoding = NO;
        }
    });
}

在該函數(shù)中讓解碼器decoder對(duì)間隔時(shí)間進(jìn)行解碼.獲取解碼數(shù)組圖像
調(diào)用 - (BOOL) addFrames: (NSArray *)frames.將解碼的視頻增加到緩存中

- (BOOL) addFrames: (NSArray *)frames
{
    if (_decoder.validVideo) {
        
        @synchronized(_videoFrames) {
            
            for (KxMovieFrame *frame in frames)
                if (frame.type == KxMovieFrameTypeVideo) {
                    [_videoFrames addObject:frame];
                    _bufferedDuration += frame.duration;
                }
        }
    }
    
    if (_decoder.validAudio) {
        
        @synchronized(_audioFrames) {
            
            for (KxMovieFrame *frame in frames)
                if (frame.type == KxMovieFrameTypeAudio) {
                    [_audioFrames addObject:frame];
                    if (!_decoder.validVideo)
                        _bufferedDuration += frame.duration;
                }
        }
        
        if (!_decoder.validVideo) {
            
            for (KxMovieFrame *frame in frames)
                if (frame.type == KxMovieFrameTypeArtwork)
                    self.artworkFrame = (KxArtworkFrame *)frame;
        }
    }
    
    if (_decoder.validSubtitles) {
        
        @synchronized(_subtitles) {
            
            for (KxMovieFrame *frame in frames)
                if (frame.type == KxMovieFrameTypeSubtitle) {
                    [_subtitles addObject:frame];
                }
        }
    }
    
    return self.playing && _bufferedDuration < _maxBufferedDuration;
}

將音視頻對(duì)象加入到指定緩存中


- (void) enableAudio: (BOOL) on
{
    id<KxAudioManager> audioManager = [KxAudioManager audioManager];
            
    if (on && _decoder.validAudio) {
                
        audioManager.outputBlock = ^(float *outData, UInt32 numFrames, UInt32 numChannels) {
            
            [self audioCallbackFillData: outData numFrames:numFrames numChannels:numChannels];
        };
        
        [audioManager play];
        
        LoggerAudio(2, @"audio device smr: %d fmt: %d chn: %d",
                    (int)audioManager.samplingRate,
                    (int)audioManager.numBytesPerSample,
                    (int)audioManager.numOutputChannels);
        
    } else {
        
        [audioManager pause];
        audioManager.outputBlock = nil;
    }
}

播放音頻

這就是完整的播放邏輯. UML時(shí)序圖

KxMovieViewController時(shí)序圖.png

時(shí)序圖中只列舉了相關(guān)關(guān)鍵函數(shù)的調(diào)用.許多變量的初始化都沒(méi)詳細(xì)書(shū)寫(xiě)

變量
_dispatchQueue 解碼隊(duì)列
_videoFrames 視頻幀數(shù)組
_audioFrames 音頻鎮(zhèn)數(shù)組

解碼器 KxMovieDecoder

屬性和變量
KxMovieDecoder屬性和變量

入口方法

+ (id) movieDecoderWithContentPath: (NSString *) path
                             error: (NSError **) perror;

- (BOOL) openFile: (NSString *) path
            error: (NSError **) perror;
-init;

其實(shí)第一個(gè)方法就是下面兩個(gè)方法的組合體

+ (id) movieDecoderWithContentPath: (NSString *) path
                             error: (NSError **) perror
{
    KxMovieDecoder *mp = [[KxMovieDecoder alloc] init];
    if (mp) {
        [mp openFile:path error:perror];
    }
    return mp;
}

該類(lèi)在加載 的時(shí)候初始化了ffmpeg

+ (void)initialize
{
    av_log_set_callback(FFLog);
    av_register_all();
    avformat_network_init();
}
  • av_log_set_callback 設(shè)置log回調(diào)函數(shù)
  • av_register_all 注冊(cè)ffmpeg組件
  • avformat_network_init 默認(rèn)開(kāi)始網(wǎng)絡(luò)模塊

我們知道av_register_all 是在文件av_format.h文件中.我們看看這個(gè)類(lèi)的介紹

av_format.h頭文件有該類(lèi)的基本使用
該庫(kù)的主要作用是I/O 和 Muxing/Demuxing
該庫(kù)有兩個(gè)目的就是編解碼音視頻
使用該庫(kù)我們首先必須要調(diào)用av_register_all()函數(shù)注冊(cè)所有的編解碼組件,如果我們需要網(wǎng)絡(luò)模塊,那么需要調(diào)用avformat_network_init()
AVInputFormat struct 代表input format .AVOutputFormat struct 代表 output format. 我們可以通過(guò)av_iformat_next()av_oformat_next() 查看我們注冊(cè)的所有的input 和output format
我們可以通過(guò)avio_enum_protocols()查看該庫(kù)支持的協(xié)議
AVFormatContext 是muxing 的和 demuxing 橋梁,用來(lái)讀寫(xiě)文件的.
AVFormatContext 必須通過(guò)avformat_alloc_context()來(lái)創(chuàng)建.不能在棧上或者 用av_malloc()來(lái)創(chuàng)建.

AVFormatContext.iformat 是對(duì)輸入的引用
AVFormatContext.oformat 是對(duì)輸出的引用
input(iformat) 可以自動(dòng)設(shè)置或者用戶(hù)設(shè)置.但是output(oformat) 總是用戶(hù)設(shè)置
AVFormatContext.streams 是個(gè)數(shù)組,裝有AVStream.該屬性一般是通過(guò)下標(biāo)來(lái)訪問(wèn)的
AVFormatContext.pb 是I/o 的上下文,可以打開(kāi)該庫(kù)和用戶(hù)設(shè)置input和output.

url 字符串是代表scheme/協(xié)議. url需要有:.如果沒(méi)有:,那么認(rèn)為是本地文件,但是這種寫(xiě)法被廢棄了.想代表本地文件,需要使用file:格式
請(qǐng)注意,一些scheme/protocol非常強(qiáng)大,允許訪問(wèn)本地和遠(yuǎn)程文件、部分文件、本地音頻和視頻設(shè)備等。

解碼 Demuxing

Demuxers 從音視頻文件中讀取數(shù)據(jù)并將其分成chunks 數(shù)據(jù) ,該分割的數(shù)據(jù)用AVPacket代表. AVPacket包含一幀或者多幀.
我們通過(guò)avformat_open_input() 打開(kāi)輸入源.
av_read_frame()讀取AVPacket
avformat_close_input() 關(guān)閉數(shù)據(jù)源

代碼片段打開(kāi)媒體文件

const char    *url = "file:in.mp3";
 AVFormatContext *s = NULL;
 int ret = avformat_open_input(&s, url, NULL, NULL);
 if (ret < 0)
    abort();

首先該函數(shù)先給AVFormatContext 分配內(nèi)存,然后打開(kāi)文件讀取頭文件,自動(dòng)將其賦值到AVFormatContext 的iformat上.對(duì)于沒(méi)有頭文件的輸入格式或者信息不夠的,我們可以調(diào)用avformat_find_stream_info 函數(shù),該函數(shù)能試著讀取和解碼一些frame,從中找到我們丟失的信息.
如果預(yù)先通過(guò)avformat_alloc_context函數(shù)創(chuàng)建了AVFormatContext.我們?cè)趥鬟f給avformat_open_input之前需要做一些調(diào)整. 如果我們想用自定義函數(shù)來(lái)讀取輸入的數(shù)據(jù).那么我們需要通過(guò)函數(shù)avio_alloc_context創(chuàng)建自己的AVIOContext,然后通過(guò)callbacks 函數(shù)來(lái)完成自定義函數(shù)對(duì)數(shù)據(jù)的解析.我們需要通過(guò)AVFormatContext.pb 來(lái)指向我們創(chuàng)建的新的AVIOContext

許多輸入格式在avformat_open_input 返回之前我們是不知道具體的格式信息的.因此我們?cè)谝呀?jīng)預(yù)先創(chuàng)建的AVFormatContext不能設(shè)置demuxer private. 因此想要設(shè)置這些參數(shù),我們需要通過(guò)avformat_open_input 函數(shù)將這些參數(shù)設(shè)定進(jìn)去.

 AVDictionary *options = NULL;
av_dict_set(&options, "video_size", "640x480", 0);
av_dict_set(&options, "pixel_format", "rgb24", 0);
if (avformat_open_input(&s, url, NULL, &options) < 0)
   abort();
av_dict_free(&options);

上述代碼我們給demuxer設(shè)置私有參數(shù)video_size 和pixel_format .
對(duì)于the rawvideo demuxer 是必須的,要不他不知道怎么解釋視頻源數(shù)據(jù). 如果這些參數(shù)設(shè)置與原始數(shù)據(jù)的不同,那么這些參數(shù)也就不起作用了.不會(huì)識(shí)別的選項(xiàng)會(huì)在可選字典中返回(識(shí)別的會(huì)在字典中刪除).
下面的代碼片段是處理不認(rèn)識(shí)的可選參數(shù)

 AVDictionaryEntry *e;
 if (e = av_dict_get(options, "", NULL, AV_DICT_IGNORE_SUFFIX)) {
     fprintf(stderr, "Option %s not recognized by the demuxer.\n", e->key);
     abort();
}

如何讀取數(shù)據(jù)
我們可以反復(fù)調(diào)用av_read_frame函數(shù)獲取數(shù)據(jù).調(diào)用該函數(shù),要是成功就返回AVPacket數(shù)據(jù).我們可以通過(guò)調(diào)用 avcodec_send_packet 或者avcodec_decode_subtitle2 來(lái)解碼AVPacket
AVPacket.pts, AVPacket.dts 和 AVPacket.duration 的時(shí)間信息會(huì)在解碼的時(shí)候設(shè)置.(解碼的時(shí)候知道的前提).如果流中沒(méi)有提供這些信息,這些值可能被重新設(shè)置.這些時(shí)間信息的基本單位是 AVStream.time_base(都是該值的整數(shù)倍).需要和該值相乘換算成秒
要是AVPacket.buf的在返回的packet中被設(shè)置.那么該空間是動(dòng)態(tài)分配的,用戶(hù)可以無(wú)限期的持有它.如果AVPacket.buf是NULL,那么返回的是一個(gè)在demuxer中的靜態(tài)區(qū)域.該AVPacket有效期到下一次調(diào)用av_read_frame.如果調(diào)用者需要一個(gè)長(zhǎng)的時(shí)間周期保持AVPacket.那么我們可以通過(guò)調(diào)用av_dup_packet 來(lái)復(fù)制一份出來(lái)
我們需要調(diào)用av_packet_unref來(lái)釋放該空間.

解碼 Muxing
Muxers 可以解碼AVPacket 數(shù)據(jù)將其寫(xiě)入文件或者指定的輸出格式數(shù)據(jù)
avformat_write_header() 寫(xiě)文件的頭文件
av_write_frame() / av_interleaved_write_frame() 寫(xiě)packets數(shù)據(jù)到文件
av_write_trailer()寫(xiě)文件結(jié)尾
開(kāi)啟解碼Muxing ,首先要調(diào)用avformat_alloc_context創(chuàng)建上下文.
我們需要給AVFormatContext.oformat附上該值
并且我們也要設(shè)置AVFormatContext.pb 的值,該值必須是avio_open2()返回的上下文或者自定義的上下文
我們需要通過(guò)avformat_new_stream() 創(chuàng)建一個(gè)stream.我們應(yīng)該給創(chuàng)建好的steam的屬性codecpar 賦值(該參數(shù)是流編碼需要的參數(shù)).
AVStream.time_base 也需要設(shè)置.

大概流程如下


官網(wǎng)截圖

這里是真正的初始化函數(shù)

- (BOOL) openFile: (NSString *) path
            error: (NSError **) perror
{
    NSAssert(path, @"nil path");
    NSAssert(!_formatCtx, @"already open");
    
    _isNetwork = isNetworkPath(path);
    
    static BOOL needNetworkInit = YES;
    if (needNetworkInit && _isNetwork) {
        
        needNetworkInit = NO;
        avformat_network_init();
    }
    
    _path = path;
    
    kxMovieError errCode = [self openInput: path];
    
    if (errCode == kxMovieErrorNone) {
        
        kxMovieError videoErr = [self openVideoStream];
        kxMovieError audioErr = [self openAudioStream];
        
        _subtitleStream = -1;
        
        if (videoErr != kxMovieErrorNone &&
            audioErr != kxMovieErrorNone) {
         
            errCode = videoErr; // both fails
            
        } else {
            
            _subtitleStreams = collectStreams(_formatCtx, AVMEDIA_TYPE_SUBTITLE);
        }
    }
    
    if (errCode != kxMovieErrorNone) {
        
        [self closeFile];
        NSString *errMsg = errorMessage(errCode);
        LoggerStream(0, @"%@, %@", errMsg, path.lastPathComponent);
        if (perror)
            *perror = kxmovieError(errCode, errMsg);
        return NO;
    }
        
    return YES;
}

分步解釋

1 isNetworkPath 是判斷是否是需要網(wǎng)絡(luò)模塊. 需要網(wǎng)絡(luò)模塊加載avformat_network_init

  1. 調(diào)用函數(shù)- (kxMovieError) openInput: (NSString *) path 打開(kāi)文件
    3 打開(kāi)openVideoStream
    4 打開(kāi)openAudioStream
    5 _subtitleStreams 參數(shù)設(shè)置

流程很清晰,分別看

- (kxMovieError) openInput: (NSString *) path
{
    AVFormatContext *formatCtx = NULL;
    
    if (_interruptCallback) {
        
        formatCtx = avformat_alloc_context();
        if (!formatCtx)
            return kxMovieErrorOpenFile;
        
        AVIOInterruptCB cb = {interrupt_callback, (__bridge void *)(self)};
        formatCtx->interrupt_callback = cb;
    }
    
    if (avformat_open_input(&formatCtx, [path cStringUsingEncoding: NSUTF8StringEncoding], NULL, NULL) < 0) {
        
        if (formatCtx)
            avformat_free_context(formatCtx);
        return kxMovieErrorOpenFile;
    }
    
    if (avformat_find_stream_info(formatCtx, NULL) < 0) {
        
        avformat_close_input(&formatCtx);
        return kxMovieErrorStreamInfoNotFound;
    }

    av_dump_format(formatCtx, 0, [path.lastPathComponent cStringUsingEncoding: NSUTF8StringEncoding], false);
    
    _formatCtx = formatCtx;
    return kxMovieErrorNone;
}

這里我們?cè)谕饨缭O(shè)置了_interruptCallback 參數(shù),因此,這里我們采用的是用戶(hù)創(chuàng)建AVFormatContext 結(jié)構(gòu)體

1.創(chuàng)建AVFormatContext 對(duì)象,并且設(shè)置interrupt_callback回調(diào)函數(shù)
2.調(diào)用avformat_open_input打開(kāi)文件
3.調(diào)用函數(shù)avformat_find_stream_info檢查對(duì)象AVFormatContext 是否有對(duì)應(yīng)的解碼器
4.調(diào)用av_dump_format 打印信息

- (kxMovieError) openVideoStream
{
    kxMovieError errCode = kxMovieErrorStreamNotFound;
    _videoStream = -1;
    _artworkStream = -1;
    _videoStreams = collectStreams(_formatCtx, AVMEDIA_TYPE_VIDEO);
    for (NSNumber *n in _videoStreams) {
        
        const NSUInteger iStream = n.integerValue;

        if (0 == (_formatCtx->streams[iStream]->disposition & AV_DISPOSITION_ATTACHED_PIC)) {
        
            errCode = [self openVideoStream: iStream];
            if (errCode == kxMovieErrorNone)
                break;
            
        } else {
            
            _artworkStream = iStream;
        }
    }
    
    return errCode;
}

1 從_formatCtx->stream中查找包含AVMEDIA_TYPE_VIDEO的AVStream
2.依次判斷AVStream的disposition 是否支持 AV_DISPOSITION_ATTACHED_PIC(圖像)
3 支持圖像調(diào)用- (kxMovieError) openVideoStream: (NSInteger) videoStream

- (kxMovieError) openVideoStream: (NSInteger) videoStream
{    
    // get a pointer to the codec context for the video stream
    AVCodecContext *codecCtx = _formatCtx->streams[videoStream]->codec;
    
    // find the decoder for the video stream
    AVCodec *codec = avcodec_find_decoder(codecCtx->codec_id);
    if (!codec)
        return kxMovieErrorCodecNotFound;
    
    // inform the codec that we can handle truncated bitstreams -- i.e.,
    // bitstreams where frame boundaries can fall in the middle of packets
    //if(codec->capabilities & CODEC_CAP_TRUNCATED)
    //    _codecCtx->flags |= CODEC_FLAG_TRUNCATED;
    
    // open codec
    if (avcodec_open2(codecCtx, codec, NULL) < 0)
        return kxMovieErrorOpenCodec;
        
    _videoFrame = av_frame_alloc();

    if (!_videoFrame) {
        avcodec_close(codecCtx);
        return kxMovieErrorAllocateFrame;
    }
    
    _videoStream = videoStream;
    _videoCodecCtx = codecCtx;
    
    // determine fps
    
    AVStream *st = _formatCtx->streams[_videoStream];
    avStreamFPSTimeBase(st, 0.04, &_fps, &_videoTimeBase);
    
    LoggerVideo(1, @"video codec size: %d:%d fps: %.3f tb: %f",
                self.frameWidth,
                self.frameHeight,
                _fps,
                _videoTimeBase);
    
    LoggerVideo(1, @"video start time %f", st->start_time * _videoTimeBase);
    LoggerVideo(1, @"video disposition %d", st->disposition);
    
    return kxMovieErrorNone;
}

1獲取AVCodecContext 對(duì)象
2 調(diào)用avcodec_find_decoder 查找解碼器
3.avcodec_open2 打開(kāi)解碼器

  1. 調(diào)用avStreamFPSTimeBase 設(shè)基準(zhǔn)時(shí)間
- (kxMovieError) openAudioStream
{
    kxMovieError errCode = kxMovieErrorStreamNotFound;
    _audioStream = -1;
    _audioStreams = collectStreams(_formatCtx, AVMEDIA_TYPE_AUDIO);
    for (NSNumber *n in _audioStreams) {
    
        errCode = [self openAudioStream: n.integerValue];
        if (errCode == kxMovieErrorNone)
            break;
    }    
    return errCode;
}

音頻也是一樣的查找過(guò)程只不過(guò)查找的格式是AVMEDIA_TYPE_AUDIO

- (kxMovieError) openAudioStream: (NSInteger) audioStream
{   
    AVCodecContext *codecCtx = _formatCtx->streams[audioStream]->codec;
    SwrContext *swrContext = NULL;
                   
    AVCodec *codec = avcodec_find_decoder(codecCtx->codec_id);
    if(!codec)
        return kxMovieErrorCodecNotFound;
        
    if (avcodec_open2(codecCtx, codec, NULL) < 0)
         return kxMovieErrorOpenCodec;
    
    if (!audioCodecIsSupported(codecCtx)) {

        id<KxAudioManager> audioManager = [KxAudioManager audioManager];
        swrContext = swr_alloc_set_opts(NULL,
                                        av_get_default_channel_layout(audioManager.numOutputChannels),
                                        AV_SAMPLE_FMT_S16,
                                        audioManager.samplingRate,
                                        av_get_default_channel_layout(codecCtx->channels),
                                        codecCtx->sample_fmt,
                                        codecCtx->sample_rate,
                                        0,
                                        NULL);
        
        if (!swrContext ||
            swr_init(swrContext)) {
            
            if (swrContext)
                swr_free(&swrContext);
             avcodec_close(codecCtx);

            return kxMovieErroReSampler;
        }
    }
    
    _audioFrame = av_frame_alloc();

    if (!_audioFrame) {
        if (swrContext)
            swr_free(&swrContext);
        avcodec_close(codecCtx);
        return kxMovieErrorAllocateFrame;
    }
    
    _audioStream = audioStream;
    _audioCodecCtx = codecCtx;
    _swrContext = swrContext;
    
    AVStream *st = _formatCtx->streams[_audioStream];
    avStreamFPSTimeBase(st, 0.025, 0, &_audioTimeBase);
    
    LoggerAudio(1, @"audio codec smr: %.d fmt: %d chn: %d tb: %f %@",
                _audioCodecCtx->sample_rate,
                _audioCodecCtx->sample_fmt,
                _audioCodecCtx->channels,
                _audioTimeBase,
                _swrContext ? @"resample" : @"");
    
    return kxMovieErrorNone; 
}

1.調(diào)用avcodec_find_decoder查找音頻解碼器
2.調(diào)用avcodec_open2 打開(kāi)音頻解碼器

  1. 設(shè)置音頻到KxAudioManager對(duì)象
  2. 設(shè)置片段基本時(shí)間

流程框圖如下


KxMovieDecoder時(shí)序圖.png

從上面的時(shí)序圖我們能看看出來(lái)avformat 解碼的基本流程

注冊(cè)所有組件 -> 打開(kāi)網(wǎng)絡(luò)組件-> 打開(kāi)文件-> 查找解碼器->解碼

這里我們需要看下函數(shù)static void avStreamFPSTimeBase(AVStream *st, CGFloat defaultTimeBase, CGFloat *pFPS, CGFloat *pTimeBase)設(shè)置解碼片段時(shí)間

static void avStreamFPSTimeBase(AVStream *st, CGFloat defaultTimeBase, CGFloat *pFPS, CGFloat *pTimeBase)
{
    CGFloat fps, timebase;
    
    if (st->time_base.den && st->time_base.num)
        timebase = av_q2d(st->time_base);
    else if(st->codec->time_base.den && st->codec->time_base.num)
        timebase = av_q2d(st->codec->time_base);
    else
        timebase = defaultTimeBase;
        
    if (st->codec->ticks_per_frame != 1) {
        LoggerStream(0, @"WARNING: st.codec.ticks_per_frame=%d", st->codec->ticks_per_frame);
        //timebase *= st->codec->ticks_per_frame;
    }
         
    if (st->avg_frame_rate.den && st->avg_frame_rate.num)
        fps = av_q2d(st->avg_frame_rate);
    else if (st->r_frame_rate.den && st->r_frame_rate.num)
        fps = av_q2d(st->r_frame_rate);
    else
        fps = 1.0 / timebase;
    
    if (pFPS)
        *pFPS = fps;
    if (pTimeBase)
        *pTimeBase = timebase;
}

AVStream.time_base 解碼過(guò)程中是被avformat設(shè)置的代表時(shí)間戳
AVStream. avg_frame_rate 設(shè)置解碼的fps

以上沒(méi)有真正的解碼過(guò)程.這里我們看看視頻的真正解碼

- (NSArray *) decodeFrames: (CGFloat) minDuration
{
    if (_videoStream == -1 &&
        _audioStream == -1)
        return nil;

    NSMutableArray *result = [NSMutableArray array];
    
    AVPacket packet;
    
    CGFloat decodedDuration = 0;
    
    BOOL finished = NO;
    
    while (!finished) {
        
        if (av_read_frame(_formatCtx, &packet) < 0) {
            _isEOF = YES;
            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) {
                    LoggerVideo(0, @"decode video error, skip packet");
                    break;
                }
                
                if (gotframe) {
                    
                    if (!_disableDeinterlacing &&
                        _videoFrame->interlaced_frame) {

//                        avpicture_deinterlace((AVPicture*)_videoFrame,
//                                              (AVPicture*)_videoFrame,
//                                              _videoCodecCtx->pix_fmt,
//                                              _videoCodecCtx->width,
//                                              _videoCodecCtx->height);
                    }
                    
                    KxVideoFrame *frame = [self handleVideoFrame];
                    if (frame) {
                        
                        [result addObject:frame];
                        
                        _position = frame.position;
                        decodedDuration += frame.duration;
                        if (decodedDuration > minDuration)
                            finished = YES;
                    }
                }
                                
                if (0 == len)
                    break;
                
                pktSize -= len;
            }
            
        } else if (packet.stream_index == _audioStream) {
                        
            int pktSize = packet.size;
            
            while (pktSize > 0) {
                
                int gotframe = 0;
                int len = avcodec_decode_audio4(_audioCodecCtx,
                                                _audioFrame,                                                
                                                &gotframe,
                                                &packet);
                
                if (len < 0) {
                    LoggerAudio(0, @"decode audio error, skip packet");
                    break;
                }
                
                if (gotframe) {
                    
                    KxAudioFrame * frame = [self handleAudioFrame];
                    if (frame) {
                        
                        [result addObject:frame];
                                                
                        if (_videoStream == -1) {
                            
                            _position = frame.position;
                            decodedDuration += frame.duration;
                            if (decodedDuration > minDuration)
                                finished = YES;
                        }
                    }
                }
                
                if (0 == len)
                    break;
                
                pktSize -= len;
            }
            
        } else if (packet.stream_index == _artworkStream) {
            
            if (packet.size) {

                KxArtworkFrame *frame = [[KxArtworkFrame alloc] init];
                frame.picture = [NSData dataWithBytes:packet.data length:packet.size];
                [result addObject:frame];
            }
            
        } else if (packet.stream_index == _subtitleStream) {
            
            int pktSize = packet.size;
            
            while (pktSize > 0) {
                
                AVSubtitle subtitle;
                int gotsubtitle = 0;
                int len = avcodec_decode_subtitle2(_subtitleCodecCtx,
                                                  &subtitle,
                                                  &gotsubtitle,
                                                  &packet);
                
                if (len < 0) {
                    LoggerStream(0, @"decode subtitle error, skip packet");
                    break;
                }
                
                if (gotsubtitle) {
                    
                    KxSubtitleFrame *frame = [self handleSubtitle: &subtitle];
                    if (frame) {
                        [result addObject:frame];
                    }
                    avsubtitle_free(&subtitle);
                }
                
                if (0 == len)
                    break;
                
                pktSize -= len;
            }
        }

        av_free_packet(&packet);
    }

    return result;
}

這個(gè)函數(shù)的調(diào)用是在KxMovieViewController類(lèi)的專(zhuān)有_dispatchQueue中進(jìn)行解碼的.并且執(zhí)行該函數(shù),我們已經(jīng)找到了音視頻的專(zhuān)有的解碼器了.這里講解我們?nèi)绾潍@取音視頻的AVPacket

調(diào)用av_read_frame 獲取AVPacket
判斷AVPacket是否是我們需要解碼的包,不是就繼續(xù)讀取包
對(duì)于視頻調(diào)用avcodec_decode_video2對(duì)視頻解碼.數(shù)據(jù)幀存放在_videoFrame變量 中. 調(diào)用handleVideoFrame獲取KxVideoFrame對(duì)象存入到數(shù)組中

avcodec_decode_video2 在最新的4.3 版本被廢棄了.可以使用avcodec_send_packet() and avcodec_receive_frame()來(lái)解碼
音頻和視頻都是同樣的道理

這里我們看如何處理vidoeFrame的

- (KxVideoFrame *) handleVideoFrame
{
    if (!_videoFrame->data[0])
        return nil;
    
    KxVideoFrame *frame;
    
    if (_videoFrameFormat == KxVideoFrameFormatYUV) {
            
        KxVideoFrameYUV * yuvFrame = [[KxVideoFrameYUV alloc] init];
        
        yuvFrame.luma = copyFrameData(_videoFrame->data[0],
                                      _videoFrame->linesize[0],
                                      _videoCodecCtx->width,
                                      _videoCodecCtx->height);
        
        yuvFrame.chromaB = copyFrameData(_videoFrame->data[1],
                                         _videoFrame->linesize[1],
                                         _videoCodecCtx->width / 2,
                                         _videoCodecCtx->height / 2);
        
        yuvFrame.chromaR = copyFrameData(_videoFrame->data[2],
                                         _videoFrame->linesize[2],
                                         _videoCodecCtx->width / 2,
                                         _videoCodecCtx->height / 2);
        
        frame = yuvFrame;
    
    } else {
    
        if (!_swsContext &&
            ![self setupScaler]) {
            
            LoggerVideo(0, @"fail setup video scaler");
            return nil;
        }
        
        sws_scale(_swsContext,
                  (const uint8_t **)_videoFrame->data,
                  _videoFrame->linesize,
                  0,
                  _videoCodecCtx->height,
                  _picture.data,
                  _picture.linesize);
        
        
        KxVideoFrameRGB *rgbFrame = [[KxVideoFrameRGB alloc] init];
        
        rgbFrame.linesize = _picture.linesize[0];
        rgbFrame.rgb = [NSData dataWithBytes:_picture.data[0]
                                    length:rgbFrame.linesize * _videoCodecCtx->height];
        frame = rgbFrame;
    }    
    
    frame.width = _videoCodecCtx->width;
    frame.height = _videoCodecCtx->height;
    frame.position = av_frame_get_best_effort_timestamp(_videoFrame) * _videoTimeBase;
    
    const int64_t frameDuration = av_frame_get_pkt_duration(_videoFrame);
    if (frameDuration) {
        
        frame.duration = frameDuration * _videoTimeBase;
        frame.duration += _videoFrame->repeat_pict * _videoTimeBase * 0.5;
        
        //if (_videoFrame->repeat_pict > 0) {
        //    LoggerVideo(0, @"_videoFrame.repeat_pict %d", _videoFrame->repeat_pict);
        //}
        
    } else {
        
        // sometimes, ffmpeg unable to determine a frame duration
        // as example yuvj420p stream from web camera
        frame.duration = 1.0 / _fps;
    }    
    
#if 0
    LoggerVideo(2, @"VFD: %.4f %.4f | %lld ",
                frame.position,
                frame.duration,
                av_frame_get_pkt_pos(_videoFrame));
#endif
    
    return frame;
}

這里我們采用的是_videoFrameFormat 變量的值是KxVideoFrameFormatYUV.在KxMovieGLView 中設(shè)置的

KxVideoFrameFormatYUV 對(duì)應(yīng)的avformat中的AV_PIX_FMT_YUV420P
AV_PIX_FMT_YUV420P 的排列格式是< planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples)

我們對(duì)_videoFrame中的數(shù)據(jù)解析到到KxVideoFrameYUV 中

這里我們需要了解下yuv420 的視頻流格式.可以參考這里

bpp 位圖的單位
16 bit per pixel
每個(gè)像素點(diǎn)分解為16個(gè)字節(jié)來(lái)表示

接下來(lái)看音頻編碼

- (KxAudioFrame *) handleAudioFrame
{
    if (!_audioFrame->data[0])
        return nil;
    
    id<KxAudioManager> audioManager = [KxAudioManager audioManager];
    
    const NSUInteger numChannels = audioManager.numOutputChannels;
    NSInteger numFrames;
    
    void * audioData;
    
    if (_swrContext) {
        
        const NSUInteger ratio = MAX(1, audioManager.samplingRate / _audioCodecCtx->sample_rate) *
                                 MAX(1, audioManager.numOutputChannels / _audioCodecCtx->channels) * 2;
        
        const int bufSize = av_samples_get_buffer_size(NULL,
                                                       audioManager.numOutputChannels,
                                                       _audioFrame->nb_samples * ratio,
                                                       AV_SAMPLE_FMT_S16,
                                                       1);
        
        if (!_swrBuffer || _swrBufferSize < bufSize) {
            _swrBufferSize = bufSize;
            _swrBuffer = realloc(_swrBuffer, _swrBufferSize);
        }
        
        Byte *outbuf[2] = { _swrBuffer, 0 };
        
        numFrames = swr_convert(_swrContext,
                                outbuf,
                                _audioFrame->nb_samples * ratio,
                                (const uint8_t **)_audioFrame->data,
                                _audioFrame->nb_samples);
        
        if (numFrames < 0) {
            LoggerAudio(0, @"fail resample audio");
            return nil;
        }
        
        //int64_t delay = swr_get_delay(_swrContext, audioManager.samplingRate);
        //if (delay > 0)
        //    LoggerAudio(0, @"resample delay %lld", delay);
        
        audioData = _swrBuffer;
        
    } else {
    
        if (_audioCodecCtx->sample_fmt != AV_SAMPLE_FMT_S16) {
            NSAssert(false, @"bucheck, audio format is invalid");
            return nil;
        }
        
        audioData = _audioFrame->data[0];
        numFrames = _audioFrame->nb_samples;
    }
    
    const NSUInteger numElements = numFrames * numChannels;
    NSMutableData *data = [NSMutableData dataWithLength:numElements * sizeof(float)];
    
    float scale = 1.0 / (float)INT16_MAX ;
    vDSP_vflt16((SInt16 *)audioData, 1, data.mutableBytes, 1, numElements);
    vDSP_vsmul(data.mutableBytes, 1, &scale, data.mutableBytes, 1, numElements);
    
    KxAudioFrame *frame = [[KxAudioFrame alloc] init];
    frame.position = av_frame_get_best_effort_timestamp(_audioFrame) * _audioTimeBase;
    frame.duration = av_frame_get_pkt_duration(_audioFrame) * _audioTimeBase;
    frame.samples = data;
    
    if (frame.duration == 0) {
        // sometimes ffmpeg can't determine the duration of audio frame
        // especially of wma/wmv format
        // so in this case must compute duration
        frame.duration = frame.samples.length / (sizeof(float) * numChannels * audioManager.samplingRate);
    }
    
#if 0
    LoggerAudio(2, @"AFD: %.4f %.4f | %.4f ",
                frame.position,
                frame.duration,
                frame.samples.length / (8.0 * 44100.0));
#endif
    
    return frame;
}

- (NSArray *) decodeFrames: (CGFloat) minDuration 函數(shù)就是對(duì)AVPacket 數(shù)據(jù)進(jìn)行解碼,保存到各自的音頻和視頻解碼數(shù)組中

KxMovieGLView

ffmeng支持協(xié)議列表

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容