IOS IJKPlayer RTSP播放編譯

1.IJKPlayer介紹

ijkplayer 是B站開源的一款做視頻直播的播放器框架, 基于ffmpeg, 支持 Android 和 iOS 地址:https://github.com/Bilibili/ijkplayer.git

2.前期準(zhǔn)備工作

2.1 克隆ijkplayer.git到本地目錄(mac編譯)
2.2 修改module-lite.sh (ijk 本身不支持rtp流媒體)

export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-protocol=rtp"
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-protocol=tcp"
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-demuxer=rtsp"
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-demuxer=sdp"
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-demuxer=rtp"

2.3 修改ff_ffplay.c 中的packet_queue_get_or_buffering (處理rtsp中丟幀問題)

while (1) {
        int new_packet = packet_queue_get(q, pkt, 0, serial);
        if (new_packet < 0)
            return -1;
        else if (new_packet == 0) {
//            if (q->is_buffer_indicator && !*finished)
//                ffp_toggle_buffering(ffp, 1);
            new_packet = packet_queue_get(q, pkt, 1, serial);
            if (new_packet < 0)
                return -1;
        }

        if (*finished == *serial) {
            av_packet_unref(pkt);
            continue;
        }
        else
            break;
    }

    return 1;

2.4 修改ff_ffplay.c 中的vp_duration (處理延時(shí)問題)

static double vp_duration(VideoState *is, Frame *vp, Frame *nextvp) {
//    if (vp->serial == nextvp->serial) {
//        double duration = nextvp->pts - vp->pts;
//        if (isnan(duration) || duration <= 0 || duration > is->max_frame_duration)
//            return vp->duration;
//        else
//            return duration;
//    } else {
//        return 0.0;
//    }
    return vp->duration;
}

2.5 修改ff_ffplay.c 添加 拍照 錄像 保存文件功能 添加完成之后需要在頭文件聲明方法

// 開始錄制函數(shù):file_name是保存路徑
int ffp_start_record(FFPlayer *ffp, const char *file_name)
{
    assert(ffp);
    ALOGD("start filename is %s",file_name);
    VideoState *is = ffp->is;
    
    ffp->m_ofmt_ctx = NULL;
    ffp->m_ofmt = NULL;
    ffp->is_record = 0;
    ffp->record_error = 0;
    
    if (!file_name || !strlen(file_name)) { // 沒有路徑
        av_log(ffp, AV_LOG_ERROR, "filename is invalid");
        ALOGD("filename is invalid");
        goto end;
    }
    
    if (!is || !is->ic|| is->paused || is->abort_request) { // 沒有上下文频伤,或者上下文已經(jīng)停止
        av_log(ffp, AV_LOG_ERROR, "is,is->ic,is->paused is invalid");
        ALOGD("is,is->ic,is->paused is invalid");
        goto end;
    }
    
    if (ffp->is_record) { // 已經(jīng)在錄制
        av_log(ffp, AV_LOG_ERROR, "recording has started");
         ALOGD("recording has started");
        goto end;
    }
    
    // 初始化一個(gè)用于輸出的AVFormatContext結(jié)構(gòu)體
    avformat_alloc_output_context2(&ffp->m_ofmt_ctx, NULL, "mp4", file_name);
    if (!ffp->m_ofmt_ctx) {
        av_log(ffp, AV_LOG_ERROR, "Could not create output context filename is %s\n", file_name);
         ALOGD("Could not create output context filename is %s\n", file_name);
        goto end;
    }
    ffp->m_ofmt = ffp->m_ofmt_ctx->oformat;
    
    for (int i = 0; i < is->ic->nb_streams; i++) {
        // 對(duì)照輸入流創(chuàng)建輸出流通道
        AVStream *in_stream = is->ic->streams[i];
        AVStream *out_stream = avformat_new_stream(ffp->m_ofmt_ctx, in_stream->codec->codec);
        if (!out_stream) {
            av_log(ffp, AV_LOG_ERROR, "Failed allocating output stream\n");
            ALOGD("Failed allocating output stream");
            goto end;
        }
        
        // 將輸入視頻/音頻的參數(shù)拷貝至輸出視頻/音頻的AVCodecContext結(jié)構(gòu)體
        av_log(ffp, AV_LOG_DEBUG, "in_stream->codec;%@\n", in_stream->codec);
        if (avcodec_copy_context(out_stream->codec, in_stream->codec) < 0) {
            av_log(ffp, AV_LOG_ERROR, "Failed to copy context from input to output stream codec context\n");
            ALOGD("Failed to copy context from input to output stream codec context");
            goto end;
        }
        
        out_stream->codec->codec_tag = 0;
        if (ffp->m_ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
            out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
        }
    }
    
    av_dump_format(ffp->m_ofmt_ctx, 0, file_name, 1);
    
    // 打開輸出文件
    if (!(ffp->m_ofmt->flags & AVFMT_NOFILE)) {
        if (avio_open(&ffp->m_ofmt_ctx->pb, file_name, AVIO_FLAG_WRITE) < 0) {
            av_log(ffp, AV_LOG_ERROR, "Could not open output file '%s'", file_name);
            ALOGD("Could not open output file '%s'", file_name);
            goto end;
        }
    }
    
    // 寫視頻文件頭
    if (avformat_write_header(ffp->m_ofmt_ctx, NULL) < 0) {
        av_log(ffp, AV_LOG_ERROR, "Error occurred when opening output file\n");
        ALOGD("Error occurred when opening output file\n");
        goto end;
    }
    
    ffp->is_record = 1;
    ffp->record_error = 0;
    pthread_mutex_init(&ffp->record_mutex, NULL);
    
    return 0;
end:
    ffp->record_error = 1;
    return -1;
}
//*停止錄播

int ffp_stop_record(FFPlayer *ffp)
{
    ALOGD("stopRecord");
    assert(ffp);
    if (ffp->is_record) {
        ffp->is_record = 0;
        pthread_mutex_lock(&ffp->record_mutex);
        if (ffp->m_ofmt_ctx != NULL) {
            av_write_trailer(ffp->m_ofmt_ctx);
            if (ffp->m_ofmt_ctx && !(ffp->m_ofmt->flags & AVFMT_NOFILE)) {
                avio_close(ffp->m_ofmt_ctx->pb);
            }
            avformat_free_context(ffp->m_ofmt_ctx);
            ffp->m_ofmt_ctx = NULL;
            ffp->is_first = 0;
        }
        pthread_mutex_unlock(&ffp->record_mutex);
        pthread_mutex_destroy(&ffp->record_mutex);
        av_log(ffp, AV_LOG_DEBUG, "stopRecord ok\n");
        ALOGD("stopRecord ok\n");
    } else {
        av_log(ffp, AV_LOG_ERROR, "don't need stopRecord\n");
        ALOGD("don't need stopRecord\n");
    }
    return 0;
}
//*截圖
void ffp_get_current_frame_l(FFPlayer *ffp, uint8_t *frame_buf)
{
  ALOGD("=============>start snapshot\n");

  VideoState *is = ffp->is;
  Frame *vp;
  int i = 0, linesize = 0, pixels = 0;
  uint8_t *src;

  vp = &is->pictq.queue[is->pictq.rindex];
  int height = vp->bmp->h;
  int width = vp->bmp->w;

  ALOGD("=============>%d X %d === %d\n", width, height, vp->bmp->pitches[0]);

  // copy data to bitmap in java code
  linesize = vp->bmp->pitches[0];
  src = vp->bmp->pixels[0];
  pixels = width * 4;
  for (i = 0; i < height; i++) {
      memcpy(frame_buf + i * pixels, src + i * linesize, pixels);
  }
  
  ALOGD("=============>end snapshot\n");
}
//*保存
int ffp_record_file(FFPlayer *ffp, AVPacket *packet)
{
    assert(ffp);
    VideoState *is = ffp->is;
    int ret = 0;
    AVStream *in_stream;
    AVStream *out_stream;
    
    if (ffp->is_record) {
        if (packet == NULL) {
            ffp->record_error = 1;
            av_log(ffp, AV_LOG_ERROR, "packet == NULL");
            return -1;
        }
        
        AVPacket *pkt = (AVPacket *)av_malloc(sizeof(AVPacket)); // 與看直播的 AVPacket分開拗小,不然卡屏
        av_new_packet(pkt, 0);
        if (0 == av_packet_ref(pkt, packet)) {
            pthread_mutex_lock(&ffp->record_mutex);
            
            if (!ffp->is_first) { // 錄制的第一幀,時(shí)間從0開始
                ffp->is_first = 1;
                pkt->pts = 0;
                pkt->dts = 0;
            } else { // 之后的每一幀都要減去顶吮,點(diǎn)擊開始錄制時(shí)的值聪富,這樣的時(shí)間才是正確的
//                pkt->pts = llabs(pkt->pts - ffp->start_pts);
//                pkt->dts = llabs(pkt->dts - ffp->start_dts);
                if (pkt->stream_index == AVMEDIA_TYPE_AUDIO) {
                   ALOGD("=============> AVMEDIA_TYPE_AUDIO\n");
                }
                else if (pkt->stream_index == AVMEDIA_TYPE_VIDEO) {
                    pkt->pts = llabs(pkt->pts - ffp->start_pts);
                    pkt->dts = llabs(pkt->dts - ffp->start_dts);
                }
            }
        

            in_stream  = is->ic->streams[pkt->stream_index];
            out_stream = ffp->m_ofmt_ctx->streams[pkt->stream_index];
            
            // 轉(zhuǎn)換PTS/DTS
            pkt->pts = av_rescale_q_rnd(pkt->pts, in_stream->time_base, out_stream->time_base, (AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
            pkt->dts = av_rescale_q_rnd(pkt->dts, in_stream->time_base, out_stream->time_base, (AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
            pkt->duration = av_rescale_q(pkt->duration, in_stream->time_base, out_stream->time_base);
            pkt->pos = -1;
                  
            // 寫入一個(gè)AVPacket到輸出文件
            if ((ret = av_interleaved_write_frame(ffp->m_ofmt_ctx, pkt)) < 0) {
                av_log(ffp, AV_LOG_ERROR, "Error muxing packet\n");
            }
            
            av_packet_unref(pkt);
            pthread_mutex_unlock(&ffp->record_mutex);
        } else {
            av_log(ffp, AV_LOG_ERROR, "av_packet_ref == NULL");
        }
    }
    return ret;
}

3.開始編譯

3.1修改支持module-lite.sh 終端進(jìn)入config目錄

cd config
rm module.sh
ln -s module-lite.sh module.sh

3.2 下載ffmpeg

./init-ios.sh

3.3 編譯ffmpeg

./compile-ffmpeg.sh clean
./compile-ffmpeg.sh all

ps:最新的 Xcode 已經(jīng)弱化了對(duì) 32 位的支持, 解決方法:
在 compile-ffmpeg.sh 中刪除 armv7 , 修改如:
FF_ALL_ARCHS_IOS8_SDK="arm64 i386 x86_64"
再重新執(zhí)行出現(xiàn)錯(cuò)誤的命令: ./compile-ffmpeg.sh all

4.IJKMediaFramework 生成

4.1 IJKMediaPlayback.h中添加如下方法

- (void)stopRecord;
- (void)startRecordWithFileName:(NSString *)fileName;
- (BOOL)isRecording ;

4.2 IJKFFMoviePlayerController.m中添加如下方法

- (void)stopRecord {
    ijkmp_stop_record(_mediaPlayer);
}

- (void)startRecordWithFileName:(NSString *)fileName {
    // 視頻存儲(chǔ)的路徑
    const char *path = [fileName cStringUsingEncoding:NSUTF8StringEncoding];
    ijkmp_start_record (_mediaPlayer, path);
}

- (BOOL)isRecording {
    return ijkmp_isRecording(_mediaPlayer);
}

4.3點(diǎn)擊IJKMediaFramework項(xiàng)目進(jìn)行編譯 生成framework文件,并將build 文件下的都copy到項(xiàng)目即可

5.IJK使用

[IJKFFMoviePlayerController setLogReport:YES];
    [IJKFFMoviePlayerController setLogLevel:k_IJK_LOG_DEBUG];

    // 調(diào)整參數(shù)
    IJKFFOptions *options = [IJKFFOptions optionsByDefault];
    // 如果是rtsp協(xié)議斧账,可以優(yōu)先用tcp(默認(rèn)是用udp)
    [options setFormatOptionValue:@"tcp" forKey:@"rtsp_transport"];
    // 播放前的探測(cè)Size,默認(rèn)是1M, 改小一點(diǎn)會(huì)出畫面更快
    [options setFormatOptionIntValue:1024 * 16 forKey:@"probsize"];
    // 播放前的探測(cè)時(shí)間
    [options setFormatOptionIntValue:50000 forKey:@"analyzeduration"];
    // 開啟硬解碼 1是硬解 0是軟解
    [options setPlayerOptionIntValue:0 forKey:@"videotoolbox"];
    // 播放重連次數(shù)
    [options setPlayerOptionIntValue:5 forKey:@"reconnect"];
    
    // 設(shè)置最大fps
    [options setPlayerOptionIntValue:30  forKey:@"max-fps"];//30
    // 跳幀開關(guān)冠桃,如果cpu解碼能力不足命贴,可以設(shè)置成5,否則
    // 會(huì)引起音視頻不同步食听,也可以通過設(shè)置它來跳幀達(dá)到倍速播放
    [options setPlayerOptionIntValue:1 forKey:@"framedrop"];
    // 解碼參數(shù)胸蛛,畫面更清晰
    [options setCodecOptionIntValue:IJK_AVDISCARD_DEFAULT forKey:@"skip_loop_filter"];
    [options setCodecOptionIntValue:IJK_AVDISCARD_DEFAULT forKey:@"skip_frame"];
    
    // 最大緩存大小是3秒,可以依據(jù)自己的需求修改
    [options setPlayerOptionIntValue:3000 forKey:@"max_cached_duration"];
    // 無限讀
    [options setPlayerOptionIntValue:1 forKey:@"infbuf"];
    // 關(guān)閉播放器緩沖
    [options setPlayerOptionIntValue:0 forKey:@"packet-buffering"];
    
    //靜音設(shè)置
    [options setPlayerOptionValue:@"1" forKey:@"an"];
    //幀數(shù)
    [options setPlayerOptionIntValue:5  forKey:@"min-frames"];
    // 幀速率(fps) (可以改樱报,確認(rèn)非標(biāo)準(zhǔn)楨率會(huì)導(dǎo)致音畫不同步葬项,所以只能設(shè)定為15或者29.97)
    [options setPlayerOptionIntValue:15 forKey:@"r"];
    [options setPlayerOptionIntValue:5  forKey:@"reconnect"];
    [IJKFFMoviePlayerController checkIfFFmpegVersionMatch:YES];

    self.ijkPlayer = [[IJKFFMoviePlayerController alloc] initWithContentURLString:@"rtsp://192.168.99.1:554/11/" withOptions:options];
    self.ijkPlayer.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    self.ijkPlayer.view.frame = self.videoBgView.bounds;
    self.ijkPlayer.view.backgroundColor = [UIColor clearColor];
    self.ijkPlayer.shouldAutoplay = YES;

    UIView *playerView = [self.ijkPlayer view];
    playerView.backgroundColor = [UIColor clearColor];
    [self.videoBgView insertSubview:playerView atIndex:1];
    [self.ijkPlayer setScalingMode:IJKMPMovieScalingModeAspectFill];
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市迹蛤,隨后出現(xiàn)的幾起案子民珍,更是在濱河造成了極大的恐慌襟士,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嚷量,死亡現(xiàn)場(chǎng)離奇詭異陋桂,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蝶溶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門嗜历,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抖所,你說我怎么就攤上這事梨州。” “怎么了田轧?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵暴匠,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我涯鲁,道長(zhǎng),這世上最難降的妖魔是什么有序? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任抹腿,我火速辦了婚禮,結(jié)果婚禮上旭寿,老公的妹妹穿的比我還像新娘警绩。我一直安慰自己,他們只是感情好盅称,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布肩祥。 她就那樣靜靜地躺著,像睡著了一般缩膝。 火紅的嫁衣襯著肌膚如雪混狠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天疾层,我揣著相機(jī)與錄音将饺,去河邊找鬼。 笑死痛黎,一個(gè)胖子當(dāng)著我的面吹牛予弧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播湖饱,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼掖蛤,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了井厌?” 一聲冷哼從身側(cè)響起蚓庭,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤致讥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后彪置,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拄踪,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年拳魁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了惶桐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡潘懊,死狀恐怖姚糊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情授舟,我是刑警寧澤救恨,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站释树,受9級(jí)特大地震影響肠槽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜奢啥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一秸仙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧桩盲,春花似錦寂纪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至柬姚,卻和暖如春拟杉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背量承。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工捣域, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宴合。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓焕梅,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親卦洽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子贞言,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359