目录

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

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

1、创建播放器

2、创建解码器

3、确定问题及解决


接上一篇: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

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

注意:如果是iPhone 7以下的手机 或者是 iOS版本低于11.0的,本身就不支持硬解。

这是本篇文章分析的重头戏,我们在梳理播放器核心流程的过程中一步步定位并解决问题:

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 *)aUrlStringwithOptions:(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*))
{//这是我们要关注的核心方法1IjkMediaPlayer *mp = ijkmp_create(msg_loop);if (!mp)goto fail;mp->ffplayer->vout = SDL_VoutIos_CreateForGLES2();if (!mp->ffplayer->vout)goto fail;//这是我们要关注的核心方法2mp->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
{//设置播放urlijkmp_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_loopijkmp_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)
{...//ZS:启动read_thread 线程,在线程中会根据读取的文件或者流的信息去判断是否存在音频流和视频流,然后通过 stream_component_open 方法找到对应的解码器,启动解码线程。is->read_tid = SDL_CreateThreadEx(&is->_read_tid, read_thread, ffp, "ff_read");
...
}

进入到 ff_ffplay.c ---> read thread 看下:

/* this thread gets the stream from the disk or the network */
//ZS:读取文件或网络流的信息
static int read_thread(void *arg)
{.../* open the streams */if (st_index[AVMEDIA_TYPE_AUDIO] >= 0) {/* ZS:音频存在 去查找对应的解码器 启动解码线程*/stream_component_open(ffp, st_index[AVMEDIA_TYPE_AUDIO]);} else {ffp->av_sync_type = AV_SYNC_VIDEO_MASTER;is->av_sync_type  = ffp->av_sync_type;}ret = -1;if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) {/* ZS:视频存在 去查找对应的解码器 启动解码线程*/ret = stream_component_open(ffp, st_index[AVMEDIA_TYPE_VIDEO]);}...
}

这里我们只关注视频,进入到 ff_ffplay.c ---> stream_component_open方法

/* open a given stream. Return 0 if OK */
static int stream_component_open(FFPlayer *ffp, int stream_index)
{
...case AVMEDIA_TYPE_VIDEO:if (ffp->async_init_decoder) {while (!is->initialized_decoder) {SDL_Delay(5);}if (ffp->node_vdec) {is->viddec.avctx = avctx;ret = ffpipeline_config_video_decoder(ffp->pipeline, ffp);}if (ret || !ffp->node_vdec) {decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread);ffp->node_vdec = ffpipeline_open_video_decoder(ffp->pipeline, ffp);if (!ffp->node_vdec)goto fail;}} else {decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread);//打开视频解码器,我们这里是同步的情况ffp->node_vdec = ffpipeline_open_video_decoder(ffp->pipeline, ffp);if (!ffp->node_vdec)goto fail;}...
}

跟进到ff_ffpipeline.c --->ffpipeline_open_video_decoder

IJKFF_Pipenode* ffpipeline_open_video_decoder(IJKFF_Pipeline *pipeline, FFPlayer *ffp)
{return pipeline->func_open_video_decoder(pipeline, ffp);
}

继续跟进,进入到具体实现:ffpipeline_ioc.c ---- >ffpipeline_create_from_ios

看下 ffpipeline_ioc.c ---- >func_open_video_decoder的具体实现:

static IJKFF_Pipenode *func_open_video_decoder(IJKFF_Pipeline *pipeline, FFPlayer *ffp)
{IJKFF_Pipenode* node = NULL;IJKFF_Pipeline_Opaque *opaque = pipeline->opaque;if (ffp->videotoolbox) {//如果配置了硬解 即 ffp->videotoolbox 是 1 就走这里node = ffpipenode_create_video_decoder_from_ios_videotoolbox(ffp);if (!node)ALOGE("vtb fail!!! switch to ffmpeg decode!!!! \n");}if (node == NULL) {//没配置硬解 或者不支持硬解 默认使用软解node = ffpipenode_create_video_decoder_from_ffplay(ffp);ffp->stat.vdec_type = FFP_PROPV_DECODER_AVCODEC;opaque->is_videotoolbox_open = false;} else {//配置了硬解 并且支持硬解 就走硬解流程ffp->stat.vdec_type = FFP_PROPV_DECODER_VIDEOTOOLBOX;opaque->is_videotoolbox_open = true;}ffp_notify_msg2(ffp, FFP_MSG_VIDEO_DECODER_OPEN, opaque->is_videotoolbox_open);return node;
}

由于我们配置了硬解,所以跟进到到 ffpipenode_ios_videotoolbox_vdec.m 文件的ffpipenode_create_video_decoder_from_ios_videotoolbox 方法来看下;

IJKFF_Pipenode *ffpipenode_create_video_decoder_from_ios_videotoolbox(FFPlayer *ffp)
{...switch (opaque->avctx->codec_id) {case AV_CODEC_ID_H264:case AV_CODEC_ID_HEVC:if (ffp->vtb_async){opaque->context = Ijk_VideoToolbox_Async_Create(ffp, opaque->avctx);} else {//这里走同步方式opaque->context = Ijk_VideoToolbox_Sync_Create(ffp, opaque->avctx);}break;...
}

跟进到:IJKVideoToolBox.m ----> Ijk_VideoToolbox_Sync_Create

跟进到:IJKVideoToolBoxSync.m ----> videotoolbox_sync_create

Ijk_VideoToolBox_Opaque* videotoolbox_sync_create(FFPlayer* ffp, AVCodecContext* avctx)
{...//问题出在这里面ret = vtbformat_init(&context_vtb->fmt_desc, context_vtb->codecpar);...
}

跟进到:IJKVideoToolBoxSync.m ----> vtbformat_init

static int vtbformat_init(VTBFormatDesc *fmt_desc, AVCodecParameters *codecpar)
{...switch (codec) {case AV_CODEC_ID_HEVC:format_id = kCMVideoCodecType_HEVC;if (@available(iOS 11.0, *)) {//iOS 11.0及以上才支持硬解 然后进一步进行判断isHevcSupported = VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC);} else {// Fallback on earlier versionsisHevcSupported = false;}if (!isHevcSupported) {goto fail;}break;case AV_CODEC_ID_H264:format_id = kCMVideoCodecType_H264;break;default:goto fail;}...
}

3、确定问题及解决

来看iPhone 7及以上并且iOS版本大于11.0的情况:

跟进到:IJKVideoToolBoxSync.m ----> vtbformat_init

static int vtbformat_init(VTBFormatDesc *fmt_desc, AVCodecParameters *codecpar)
{
...switch (codec) {case AV_CODEC_ID_HEVC:format_id = kCMVideoCodecType_HEVC;if (@available(iOS 11.0, *)) {isHevcSupported = VTIsHardwareDecodeSupported(kCMVideoCodecType_HEVC);} else {// Fallback on earlier versionsisHevcSupported = false;}if (!isHevcSupported) {goto fail;}break;}...//此处出现重大错误,//264的流调用ff_isom_write_avcc方法,h265(hevc)应该调用hevc.c里面的ff_isom_write_hvcc 才对        ff_isom_write_avcc(pb, extradata, extrasize);...//h265(hevc)情况下不需要按264方式进行校验if (!validate_avcC_spc(extradata, extrasize, &fmt_desc->max_ref_frames,         &sps_level, &sps_profile)) {av_free(extradata);goto fail;}}

这里是真正出错的地方:

  • 错误1、ff_isom_write_avcc 方法是针对264流的,不能用于265流
  • 错误2、h265下不需要调用 validate_avcC_spc 方法进行校验

修改方式:

先说错误2、这个比较简单,不在直接执行校验,而是增加判断非hevc的情况下才执行,如下:

if (codec == AV_CODEC_ID_HEVC) {printf("---- zs log ----- vtbformat_init no check h265\n");
} else {if (!validate_avcC_spc(extradata, extrasize,&fmt_desc->max_ref_frames, &sps_level, &sps_profile)) {av_free(extradata);printf("---- zs log ----- vtbformat_init 11 \n");goto fail;}
}

针对错误1,请将 ff_isom_write_avcc(pb, extradata, extrasize); 这行代码 替换为:

//ff_isom_write_hvcc的作用是将extradata转为HEVCDecoderConfigurationRecord结构并写入。
if (codec == AV_CODEC_ID_HEVC) {printf("---- zs log ----- ff_isom_write_hvcc h265\n");ff_isom_write_hvcc(pb, extradata, extrasize, 1);
} else {ff_isom_write_avcc(pb, extradata, extrasize);
}

此时编译会报错,因为ff_isom_write_hvcc方法调用不到,没有暴露出来,因此需要先暴露出此方法,具体步骤如下:

第一步:打开ijkplayer-ios --->extra---->ffmpeg---->libavformat 下得Makefile文件

在 HEADERS 这组里面加入:hevc.h \

在 OBJS 这组里面加入 hevc.o \

第二步:在以下文件中重复此操作

  • ijkplayer-ios/ios/ffmpeg-arm64/libavformat/Makefile
  • ijkplayer-ios/ios/ffmpeg-armv7/libavformat/Makefile
  • ijkplayer-ios/ios/ffmpeg-i386/libavformat/Makefile
  • ijkplayer-ios/ios/ffmpeg-x86_64/libavformat/Makefile

第三步:别忘了在IJKVideoToolBoxSync.m文件中头部加入

#include "libavformat/hevc.h"

第四步:clean 后重新编译即可

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

以上就是iOS 下 ijkplayer 对 h265(hevc)4k 硬解的全部修改过程。

iOS ijkplayer 硬解H265(hevc)4k视频问题解决相关推荐

  1. 龙芯php,龙芯平台硬解1080和4k视频

    环境 操作系统:Fedora 28 适用架构:MIPS64EL 显卡 首先,要有一块amd的独立显卡(集成显卡比较弱).龙芯支持的具体型号请参考官方链接. 龙芯3.10内核对比社区4.4版本对GPU驱 ...

  2. iOS h264 硬解

    记录. http://www.voidcn.com/blog/dongtinghong/article/p-5047279.html 首先要把 VideoToolbox.framework 添加到工程 ...

  3. 在我的S5pv210 wince6.0 上做个支持硬解的wince播放器,播放1080P视频堪比android

    作者:gooogleman@foxmail.com 以及P网友合作开发 Sate210 wince6.0 硬解播放器测试视频 http://v.youku.com/v_show/id_XNTIwNTk ...

  4. 【保凌】Dante AV之软解和硬解

    上期手术示教网络方案清晰展示出,在已有的Dante音频里加入Dante AV云台摄像机后,实现飞跃拥有更加完善的 "Dante AV视频+音频". 我们也正在将Dante AV不断 ...

  5. ffmpeg解码的软解及硬解(cuda和qsv)使用方法

    对ffmpeg不是很熟悉,在使用的过程中遇到了很多坑,总结下,避免以后再遇到类似情况 版本兼容问题: 本次使用的ffmpeg版本是4.2,解码的调用方式为: int32_t iRet = -1;// ...

  6. ffmepg实践系列之--硬解接口实现

    闲话 知道ffmpeg很久了,可是一直没有深入研究.最近在研究一款显卡的ffmpeg下的硬解,因此想记录下自己研究所得.关于ffmpeg的基本知识,推荐雷神博客,感谢雷神.废话少说,开始填坑. 思路 ...

  7. 视频解码之软解与硬解

    视频解码之软解与硬解 硬解:从字面意思上理解就是用硬件来进行解码,通过显卡的视频加速功能对高清视频进行解码,很明显就是一个专门的电路板(这样好理解...)来进行视频的解码,是依靠显卡GPU的. 软解: ...

  8. Chrome已实现对H.265/HEVC的硬解支持

    H.265/HEVC作为ITU-T VCEG继H.264/AVC之后所制定的新视频编码标准,能够在有限带宽下传输质量更高的视频.超高清视频的普及与流行,使得各大网站不得不用H.265来代替老旧的H.2 ...

  9. iOS VideoToolbox硬编H.265(HEVC)H.264(AVC):1 概述

    本文档尝试用Video Toolbox进行H.265(HEVC)硬件编码,视频源为iPhone后置摄像头.去年做完硬解H.264,没做编码,技能上感觉有些缺失.正好刚才发现CMFormatDescri ...

  10. N5105 软路由安装 ESXi 7 直通核显给 Debian / Ubuntu 虚拟机通过 Docker 实现 jellyfin 硬件转码视频文件(硬解/编码)

    摘要 在ESXi 7.0u3e里直通N5105的核显给虚拟机Debian 11/Ubuntu 22.04(更新到5.18内核),再套用Docker镜像nyanmisaka/jellyfin (10.8 ...

最新文章

  1. Centos 配置JAVA_HOME
  2. Java数组与List 相互转换方法详解
  3. android 随手记代码,用ExpandableListView写的随手记实例
  4. python filter map区别_python中filter、map、reduce的区别
  5. antd 设置表头属性_使用表数据自定义React Antd表头
  6. 7-56 家庭房产 (25 分)
  7. 萌新学python(输入与输出)
  8. Python+numpy实现蒙特卡罗方法估计圆周率近似值
  9. 2018上IEC计算机高级语言(C)作业 第1次作业 。
  10. 《无线通信与网络》第二章 信号传输基础
  11. 弹性波波长计算公式_固体中的弹性波
  12. 测试开发工程师面试题目
  13. uni-app开发微信公众号H5网页,用微信开发者工具调试公众号
  14. 十进制转二进制,短除法与位运算两种方法
  15. php设置系统时区,php 设置时区
  16. (C/C++)数据结构所需的程序语言基础(一)数据类型、运算符及表达式
  17. knn sklearn
  18. siri语音功能测试点
  19. 计算机网络基础课内实验报告答案,计算机网络基础课内实验报告-20210418131414.docx-原创力文档...
  20. git 命令 简单介绍

热门文章

  1. vscode推荐编程字体
  2. 条码软件如何自定义设置条形码尺寸
  3. revit 转换ifc_revit怎么导ifc?如何使用FME在Revit中导出IFC
  4. 焦作机器人编程比赛_焦作市山阳区东环小学在第二十届全国中小学电脑制作活动“机器人竞赛”焦作选拔赛中创佳绩...
  5. 贵州小学计算机编程比赛,2019年贵阳市中小学电脑制作活动成功举办
  6. 在VS2012集成Fortran95(Ftn95)
  7. 云计算核心技术的基本理解
  8. 电子工程专业用得最多的17种软件,你哪个用得好?
  9. chinakr的推荐软件列表2007版
  10. html怎么设置光线,vray渲染器太阳光参数怎么设置?