視頻編解碼基礎(chǔ)篇4-FFmpeg解碼播放

音視頻編解碼:視頻編解碼基礎(chǔ)1-FFmpeg結(jié)構(gòu)與API摘要
音視頻編解碼:視頻編解碼基礎(chǔ)2-FFmpeg解碼實踐
音視頻編解碼:視頻編解碼基礎(chǔ)篇3-FFmpeg轉(zhuǎn)碼實踐
音視頻編解碼:視頻編解碼基礎(chǔ)篇4-FFmpeg解碼播放
音視頻編解碼:視頻編解碼基礎(chǔ)篇5-FFmpeg水印添加

一.FFmpeg解碼播放

FFmpeg音視頻編碼流程.jpg

1首先接到上篇基礎(chǔ)篇3的轉(zhuǎn)碼基礎(chǔ)上繼續(xù).
在正常打開音視頻流后開啟讀取音視頻流線程(注意線程安全加解鎖的多個細(xì)節(jié)點)

預(yù)制關(guān)鍵類和基本數(shù)據(jù)
 @interface DJMediaFrame : NSObject
 @property (nonatomic) CGFloat position;
 @property (nonatomic) CGFloat duration;
 @end

 @interface DJAudioFrame : DJMediaFrame
 @property (nonatomic)NSData *samples;
 @end

 @interface DJVideoFrame : DJMediaFrame
 @property (nonatomic) NSInteger width;
 @property (nonatomic) NSInteger height;
 @end
 
 @interface DJVideoFrameYUV : DJVideoFrame
 @property (nonatomic)NSInteger dataLenth;
 @property (nonatomic)UInt8* buffer;
/**
     *  互斥鎖
     */
    NSCondition*        _condition;
/**
     *  用于裝解碼后的音視頻數(shù)據(jù)
     */
    AVFrame             *_videoFrame;
    AVFrame             *_audioFrame;
/**
     *  解碼上下文
     */
    AVFormatContext     *_inFormatCtx;
/**
     *  音視頻數(shù)據(jù)流標(biāo)志
     */
    NSInteger           _videoStream;
    NSInteger           _audioStream;
/**
     *  非直播流控制播放位置進(jìn)度
     */
    CGFloat             _duration;
/**
     *  主被動退出線程條件標(biāo)志
     */
    BOOL                _isErrorOccur;
    BOOL  volatile      _closeStream;
    BOOL                _decoderRunning;
讀取數(shù)據(jù)流線程操作
-(BOOL)startDecodeThread{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSInvocationOperation *readOperation = [[NSInvocationOperation alloc] initWithTarget:wself selector:@selector(readFramesThreadProc) object:nil];
    NSInvocationOperation *popOperation  = [[NSInvocationOperation alloc] initWithTarget:wself selector:@selector(popFramesThreadProc) object:nil];
    [queue addOperation:readOperation];
    [queue addOperation:popOperation];
}

/**
 *  讀流數(shù)據(jù),讀數(shù)據(jù)的同時解碼
 */
-(void)readFramesThreadProc{
    
    @autoreleasepool {
        
        _decoderRunning = true;
        _isErrorOccur   = false;
        AVPacket packet;
        av_init_packet(&packet);
        NSLog(@"讀數(shù)據(jù)流線程開始") UTF8String]);
        while (!(_closeStream || _isErrorOccur)) {
            _readFrameStartTime = av_gettime();
            [_condition lock];
            //具體問題具體分析可活動配置視頻幀率
            while (_decodeFrames.count > 0x30)
                [_condition wait];
            
            if (_closeStream) {
                [_condition unlock];
                break;
            }
            //讀取數(shù)據(jù)
            int result = av_read_frame(_inFormatCtx, &packet);
            if (result < 0) {
                _isErrorOccur = true;
                [_condition unlock];
                NSLog(@"數(shù)據(jù)讀取錯誤 error->,%s", av_err2str(result));
                break;
            }
            
            if (packet.stream_index ==_videoStream) {
                [self decodeVideo:&packet];//處理視頻數(shù)據(jù)包
            } else if (packet.stream_index == _audioStream) {
                [self decodeAudio:&packet];//處理音頻數(shù)據(jù)包
            }
            [_condition unlock];
            av_packet_unref(&packet);
        }
        _decoderRunning = false;
        if (_isErrorOccur){
            //如果出錯做相應(yīng)處理通知外部控制器
        }
       NSLog(@"讀取數(shù)據(jù)線程結(jié)束,是否正常結(jié)束:(1:正常結(jié)束  0:網(wǎng)絡(luò)錯誤結(jié)束) %d", _closeStream);
    }
}
/**
 *  取流數(shù)據(jù)
 */
-(void)popFramesThreadProc{
    
    int64_t popCounts = 0;
    int64_t prePopCounts = 0;
    CGFloat playPosition = 0;
    int spaceCount = 15;
    @autoreleasepool {
        NSLog(@"送數(shù)據(jù)線程開始");
        while (!(_closeStream || _isErrorOccur)) {
            @synchronized(_decodeFrames) {
                if (_decodeFrames.count) {
                    DJMediaFrame* frame = [_decodeFrames firstObject];
                    if (enumFrameTypeAudio == frame.type) {
                        [self popAudioFrame:(DJAudioFrame*)frame];
                        [_decodeFrames removeObject:frame];
                    } else if (enumFrameTypeVideo == frame.type) {
                            popCounts ++;
                            playPosition = frame.position;
                            //音視頻流同步操作暫不做說明
                           // [self checkAVPosition:playPosition];
                            [self popVideoFrame:(DJVideoFrame *)frame];
                            @synchronized (_videoFramesRecycle) {
                                [_videoFramesRecycle addObject:frame];
                            }
                            [_decodeFrames removeObject:frame];
                    }
                }
                //鎖信號標(biāo)志與解碼同步30/s幀
                if (_decodeFrames.count < 0x30){
                    [_condition signal];
                }
            }
        }
        NSLog(@"送數(shù)據(jù)線程結(jié)束");
    }
}
音視頻數(shù)據(jù)解碼avcodec_send_packet avcodec_receive_frame

ffmpeg3版本的解碼接口做了不少調(diào)整,之前的視頻解碼接口avcodec_decode_video2和avcodec_decode_audio4音頻解碼被設(shè)置為deprecated,對這兩個接口做了合并,使用統(tǒng)一的接口。并且將音視頻解碼步驟分為了兩步,第一步avcodec_send_packet,第二步avcodec_receive_frame,通過接口名字我們就可以知道第一步是發(fā)送編碼數(shù)據(jù)包,第二步是接收解碼后數(shù)據(jù)。
avcodec_send_packet avcodec_receive_frame成對出現(xiàn)但不是一一對應(yīng)

-(void)decodeVideo:(AVPacket*)packet{

    @autoreleasepool {
        
        int result = avcodec_send_packet(_videoCodecCtx, packet);
        if (result < 0) {
             NSLog(@"發(fā)送視頻編碼數(shù)據(jù)包錯誤 error-> %s", av_err2str(result));
            return;
        }
        while (avcodec_receive_frame(_videoCodecCtx, _videoFrame) >= 0) {
//視頻錄制處理
/*            if (self.recorderbool) {
                _errorOccur = ![self.assetWriter writeVideoFrame:_videoFrame];
                if (_errorOccur) {
                }else{
                }
            }*/
             DJXVideoFrame *frame = [self handleVideoFrame];
            if (frame) {
                [self addDecodeDJMediaFrame:frame];
            }
        }
    }
}

-(void)decodeAudio:(AVPacket*)packet{
    @autoreleasepool {
        int result         = avcodec_send_packet(_audioCodecCtx, packet);
        if (result < 0){
            NSLog(@"發(fā)送音頻編碼數(shù)據(jù)包錯誤 error-> %s", av_err2str(result));
            return;
        }
        while (avcodec_receive_frame(_audioCodecCtx, _audioFrame) >= 0) {
            DJMediaFrame * frame = [self handleAudioFrame];
            if (frame){
                [self addDecodeDJMediaFrame:frame];
            }
        }
    }
}
 
-(void)addDecodeDJMediaFrame:(DJMediaFrame*)frame{
    @synchronized (_decodeFrames) {
        NSInteger index = _decodeFrames.count - 1;
        for (; index >= 0; index --) {
            DJMediaFrame* tempFrame = [_decodeFrames objectAtIndex:index];
            if (tempFrame.position < frame.position) break;
        }
        //向緩存容器添加視頻幀
        [_decodeFrames insertObject:frame atIndex:index + 1];
    }
}
//不加 @autoreleasepool ,內(nèi)存會得不到及時釋放
-(void)popAudioFrame:(DJAudioFrame*)frame{
    
    @autoreleasepool {
       //1通過音頻單元 AudioUnit 播放
      //2通過音頻隊列AudioQueueRef播放
    }
}

-(void)popVideoFrame:(DJVideoFrame*)frame{
    @autoreleasepool {
       //1通過YUV轉(zhuǎn)RGB->UIimage 直接播放
      //2將YUV處理通過OpenGLES渲染播放
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,990評論 2 374