iOS ijkplayer 硬解H265(hevc)4k视频问题解决

Posted 安卓开发-顺

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS ijkplayer 硬解H265(hevc)4k视频问题解决相关的知识,希望对你有一定的参考价值。

目录

一、mp2 音频格式下无声音问题的解决

二、视频卡顿、音视频不同步问题(硬解失败导致的)解决


接上一篇:Android、iOS ijkplayer编译步骤及相关问题解决

上一篇基于B站开源项目:https://github.com/bilibili/ijkplayer

编译成功ios版本的ijkplayer以后,进行了h265并且是4K(3840x2160)码流的播放测试,发现不管怎么尝试上层暴露的软硬解、丢帧等参数的配置

- (void)viewWillAppear:(BOOL)animated 
    ...

    // 尝试硬解 0 是软解 1是硬解
    // 这里提前剧透一下:这里的硬解设置没有生效,下面会详细解释
    IJKFFOptions *options = [IJKFFOptions optionsByDefault];
    [options setPlayerOptionIntValue:1 forKey:@"videotoolbox"];

    // 尝试通过增加丢帧数来让视频尽快追上音频
    [options setPlayerOptionIntValue:5 forKey:@"framedrop"];

    printf("---- zs log ----IJKMoviePlayerViewController prepareToPlay \\n");
    [self.player prepareToPlay];


都无法正常播放,具体表现为:

  • aac 格式音频播放正常
  • mp2 格式音频 无声音
  • 视频播放非常卡顿
  • 在aac 音频格式下,音频正常速度播放,视频由于卡顿,播放越来越慢从而音视频不同步现象随播放时间的增加而越来越明显

因此不得不从源码入手来分析解决问题:

一、mp2 音频格式下无声音问题的解决

这个问题相对比较好解决,因为源码是支持此格式的,只需要在配置中加入即可:

我这里用的配置文件是module-lite.sh,如果你使用的是module-lite-hevc.sh 或其他就改对应的配置文件就可以了。

第一步:打开ijkplayer-ios/config/module-lite.sh

第二步:在 # ./configure --list-decoders 这组下面添加:

export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-decoder=mpga"
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-decoder=mp2"

第三步:在 # ./configure --list-muxers 这组下面添加:

export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-muxer=mpga"
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-muxer=mp2"

第四步:在 # ./configure --list-demuxers 这组下面添加:

export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-demuxer=mpga"
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-demuxer=mp2"

第五步:在 # ./configure --list-parsers 这组下面添加:

export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-parser=mpga"
export COMMON_FF_CFG_FLAGS="$COMMON_FF_CFG_FLAGS --enable-parser=mp2"

最后贴一张截图:

第六步:到config路径下删除module.sh ,然后执行:

ln -s module-lite.sh module.sh

     或者 到 根目录下执行:

./init-config.sh(此步骤会自动拷贝module-lite.sh文件内容到module.sh)

说白了你也可以不删除module.sh直接手动复制module-lite.sh里面的所有内容覆盖到module.sh里面

第七步:clean之后重新编译即可

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

二、视频卡顿、音视频不同步问题(硬解失败导致的)解决

这是本片文章分析的重头戏,分析此问题之前,我们有必要梳理下播放器执行的核心流程:

1、创建播放器

从上层调用 IJKFFMoviePlayerControll 的 initWithContentURL方法开始,传入播放地址:

- (id)initWithContentURL:(NSURL *)aUrl withOptions:(IJKFFOptions *)options

...
    NSString *aUrlString = [aUrl isFileURL] ? [aUrl path] : [aUrl absoluteString];
    return [self initWithContentURLString:aUrlString withOptions:options];

继续调用到:initWithContentURLString方法

- (id)initWithContentURLString:(NSString *)aUrlString
                   withOptions:(IJKFFOptions *)options

    if (aUrlString == nil)
        return nil;

    self = [super init];
    if (self) 
        ijkmp_global_init();
        ijkmp_global_set_inject_callback(ijkff_inject_callback);

        [IJKFFMoviePlayerController checkIfFFmpegVersionMatch:NO];

        if (options == nil)
            options = [IJKFFOptions optionsByDefault];

        ...
        // init player --- 关键方法 初始化播放器
        _mediaPlayer = ijkmp_ios_create(media_player_msg_loop);
        ...

进行播放器的初始化:    _mediaPlayer = ijkmp_ios_create(media_player_msg_loop);

这行代码会调用到 ijkplayer_ios.m文件中

IjkMediaPlayer *ijkmp_ios_create(int (*msg_loop)(void*))

    //这是我们要关注的核心方法1
    IjkMediaPlayer *mp = ijkmp_create(msg_loop);
    if (!mp)
        goto fail;

    mp->ffplayer->vout = SDL_VoutIos_CreateForGLES2();
    if (!mp->ffplayer->vout)
        goto fail;

    //这是我们要关注的核心方法2
    mp->ffplayer->pipeline = ffpipeline_create_from_ios(mp->ffplayer);
    if (!mp->ffplayer->pipeline)
        goto fail;

    return mp;

fail:
    ijkmp_dec_ref_p(&mp);
    return NULL;

ijkmp_ios_create方法主要干了两件事:

  • 通过ijkmp_create方法创建了IjkMediaPlayer 播放器
  • 通过ffpipeline_create_from_ios 创建了ffpipeline(可以理解为解码器和音频输出的提供者)

创建过程到此结束。

2、创建解码器

还是从上层的调用入手:

IJKFFMoviePlayerController.m  --- >  prepareToPlay

- (void)prepareToPlay

    //设置播放url
    ijkmp_set_data_source(_mediaPlayer, [_urlString UTF8String]);
    ...    
    //异步准备
    ijkmp_prepare_async(_mediaPlayer);

跟进到:ijkplayer.c --- > ijkmp_prepare_async

int ijkmp_prepare_async(IjkMediaPlayer *mp)

    assert(mp);
    MPTRACE("ijkmp_prepare_async()\\n");
    pthread_mutex_lock(&mp->mutex);
    //关键流程方法
    int retval = ijkmp_prepare_async_l(mp);
    pthread_mutex_unlock(&mp->mutex);
    MPTRACE("ijkmp_prepare_async()=%d\\n", retval);
    return retval;

继续看 ijkplayer.c --- > ijkmp_prepare_async_l 方法

static int ijkmp_prepare_async_l(IjkMediaPlayer *mp)

...
    // released in msg_loop
    ijkmp_inc_ref(mp);
    ...
    //关键方法
    int retval = ffp_prepare_async_l(mp->ffplayer, mp->data_source);
    ...
    return 0;

跟进到:ff_ffplay.c ---> ffp_prepare_async_l 方法

int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name)

    ...
    //关键方法
    VideoState *is = stream_open(ffp, file_name, NULL);

进入:ff_ffplay.c ---> stream_open方法

static VideoState *stream_open(FFPlayer *ffp, const char *filename, AVInputFormat *iformat)

 
    /* start video display */
    if (frame_queue_init(&is->pictq, &is->videoq, ffp->pictq_size, 1) < 0)
        goto fail;
    if (frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0) < 0)
        goto fail;
    if (frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1) < 0)
        goto fail;

    if (packet_queue_init(&is->videoq) < 0 ||
        packet_queue_init(&is->audioq) < 0 ||
        packet_queue_init(&is->subtitleq) < 0)
        goto fail;

    if (!(is->continue_read_thread = SDL_CreateCond())) 
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\\n", SDL_GetError());
        goto fail;
    

    if (!(is->video_accurate_seek_cond = SDL_CreateCond())) 
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\\n", SDL_GetError());
        ffp->enable_accurate_seek = 0;
    

    if (!(is->audio_accurate_seek_cond = SDL_CreateCond())) 
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\\n", SDL_GetError());
        ffp->enable_accurate_seek = 0;
    

    init_clock(&is->vidclk, &is->videoq.serial);
    init_clock(&is->audclk, &is->audioq.serial);
    init_clock(&is->extclk, &is->extclk.serial);
    is->audio_clock_serial = -1;
    if (ffp->startup_volume < 0)
        av_log(NULL, AV_LOG_WARNING, "-volume=%d < 0, setting to 0\\n", ffp->startup_volume);
    if (ffp->startup_volume > 100)
        av_log(NULL, AV_LOG_WARNING, "-volume=%d > 100, setting to 100\\n", ffp->startup_volume);
    ffp->startup_volume = av_clip(ffp->startup_volume, 0, 100);
    ffp->startup_volume = av_clip(SDL_MIX_MAXVOLUME * ffp->startup_volume / 100, 0, SDL_MIX_MAXVOLUME);
    is->audio_volume = ffp->startup_volume;
    is->muted = 0;
    is->av_sync_type = ffp->av_sync_type;

    is->play_mutex = SDL_CreateMutex();
    is->accurate_seek_mutex = SDL_CreateMutex();
    ffp->is = is;
    is->pause_req = !ffp->start_on_prepared;

    is->video_refresh_tid = SDL_CreateThreadEx(&is->_video_refresh_tid, video_refresh_thread, ffp, "ff_vout");
    if (!is->video_refresh_tid) 
        av_freep(&ffp->is);
        return NULL;
    

    is->initialized_decoder = 0;
    //ZS:启动read_thread 线程,在线程中会根据读取的文件或者流的信息去判断是否存在音频流和视频流,然后通过 stream_component_open 方法找到对应的解码器,启动解码线程。
    is->read_tid = SDL_CreateThreadEx(&is->_read_tid, read_thread, ffp, "ff_read");
    if (!is->read_tid) 
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateThread(): %s\\n", SDL_GetError());
        goto fail;
    

    if (ffp->async_init_decoder && !ffp->video_disable && ffp->video_mime_type && strlen(ffp->video_mime_type) > 0
                    && ffp->mediacodec_default_name && strlen(ffp->mediacodec_default_name) > 0) 
        if (ffp->mediacodec_all_videos || ffp->mediacodec_avc || ffp->mediacodec_hevc || ffp->mediacodec_mpeg2) 
            decoder_init(&is->viddec, NULL, &is->videoq, is->continue_read_thread);
            ffp->node_vdec = ffpipeline_init_video_decoder(ffp->pipeline, ffp);
        
    
    is->initialized_decoder = 1;

    return is;
fail:
    is->initialized_decoder = 1;
    is->abort_request = true;
    if (is->video_refresh_tid)
        SDL_WaitThread(is->video_refresh_tid, NULL);
    stream_close(ffp);
    return NULL;

未完待续 20220921

以上是关于iOS ijkplayer 硬解H265(hevc)4k视频问题解决的主要内容,如果未能解决你的问题,请参考以下文章

iOS ijkplayer 硬解H265(hevc)4k视频问题解决

iOS利用ffmpeg 转码hevc到h264 ,以及 保存h265 h264流

[超详细] 在Edge/Chrome浏览器上为B站开启HEVC硬解和AV1硬解(支持4K120Hz8KHDR真彩,杜比视界杜比全景声)

[树莓派]aarch64编译静态的ffmpeg 可硬解h264/hevc

如何推送和播放RTMP H265流 (RTMP HEVC)

MediaFoundation HEVC H265 编码