avcodec_receive_frame始终返回EAGAIN

article/2025/10/6 13:52:36

今天我们研究一个问题:

avcodec_receive_frame()始终返回EAGAIN

根本的解决方案还需要深入debug,但是这个函数很太复杂,需要些时间和耐心;

目前在不考虑编解码性能的情况下,能work around的方法只有一个,那就是取消硬解码,使用libx264进行软解码:

  • 删除所有有关mediacodec相关的选项  

  • 增加如下编译选项:

--enable-libx264 \--enable-encoder=libx264 \--enable-parser=h264 \--enable-encoder=h264 \--enable-decoder=h264 \--enable-muxer=h264 \--enable-demuxer=h264 \
  • 代码方面,不可以再使用这句,这样是会去找硬解码的:

dec = avcodec_find_decoder_by_name("h264_mediacodec")

而要使用下面这句,去使用软解码:

AVCodec *dec = avcodec_find_decoder(stream->codecpar->codec_id);

其实上面的codec_id就是AV_CODEC_ID_H264

但是规避问题不是一个好程序员,就像你炒股亏钱了还要一直扳本直到把所有的钱都套进去了才甘心一样,做程序就要这种死磕到底的精神,不吃饭不睡觉通宵达旦也要找出根本原因,掉头发秃顶肩周炎腰椎间盘突出也在所不惜,你做到了吗,你没有!

关于EAGAIN这个问题,网上查到的更多的是说要循环调用avcodec_send_packet来进行喂数据,特里同学当然是这么做的:

while (1) {    ret = avcodec_send_packet(stream->decCtx, pkt_in);if (ret < 0) {if (ret == AVERROR(EAGAIN)) {av_packet_unref(pkt_in);continue;}av_log(NULL, AV_LOG_ERROR, "Error while sending a packet to the decoder\n");break;}while (ret >= 0) {ret = avcodec_receive_frame(stream->decCtx, stream->decFrame);... //代码省略}... //代码省略
}

但是这样做了也还是一直返回EAGAIN,它不是前面几帧返回EAGAIN,是所有的帧都EAGAIN,整个mp4文件H264码流发完了还是EAGAIN,所以我的这个问题另有蹊跷,头痛啊!

怀疑是AVCodec或者AVCodecContext的问题:

AVCodecsoft        hard
capabilities         0x3022     0x60020                         
priv_data_size       53304      112                               
decode               pointer    NULL
receive_frame        NULL       pointer     
caps_interal         0x53       0x4                             
bsfs                 NULL       h264_mp4toannexb                AVCodecContextsoft        hard
ticks_per_frame       2             1

经过调试发现,上面这些参数,在使用软硬解码时的值是不一样的,于是我就试着将硬解码时的值改为软解码时的值会不会有用呢,于是我在avcodec_open2正式调用之前做了对应的修改,重新运行,发现并没什么卵用,问题依旧!

咋整?为了解决这个问题,厚着脸皮在群里在公众号留言在github的issue留言,后来有个抖音公司里的音视频大佬回了我,也是下面这个公众号的作者,大家有兴趣可以关注一下:

他叫我把ffmpeg的打印打开试试,于是我就去网上查,因为ffmpeg默认的打印是printf输出的,printf的输出在android里面是看不到的,需要将打印重新定位到logcat才行,修改如下:

#include <android/log.h>#define LOG_TAG "MeidaOperationNative"
#define JLOG_I(...) ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
#define JLOG_E(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))static void my_logoutput(void *ptr, int level, const char *fmt, va_list vl)
{va_list vl2;char *line = malloc(128);static int print_prefix = 1;va_copy(vl2, vl);av_log_format_line(ptr, level, fmt, vl2, line, 128, &print_prefix);va_end(vl2);line[127] = '\0';JLOG_E("%s", line);free(line);
}av_log_set_level(AV_LOG_INFO);
av_log_set_callback(my_logoutput);

这样处理之后,ffmpeg中所有用 av_log(NULL, AV_LOG_TRACE, "")打印的信息都会输出到logcat中。

于是我在logcat中看到了如下的出错打印:

No output buffer available, try again later

总算有点蛛丝马迹了,感谢大佬的指点啊!这个是ffmpeg中打印出来的,于是我到ffmpeg的源码目录中grep一把,找到了地方,在下面这个文件中的第861行:

ffmpeg-4.4/libavcodec/mediacodecdec_common.c760 int ff_mediacodec_dec_receive(AVCodecContext *avctx, MediaCodecDecContext *s,
761                               AVFrame *frame, bool wait)
762 {
787     index = ff_AMediaCodec_dequeueOutputBuffer(codec, &info, output_dequeue_timeout_us);
788     if (index >= 0) {//代码省略
826     } else if (ff_AMediaCodec_infoOutputFormatChanged(codec, index)) {//代码省略
853     } else if (ff_AMediaCodec_infoOutputBuffersChanged(codec, index)) {
854         ff_AMediaCodec_cleanOutputBuffers(codec);
855     } else if (ff_AMediaCodec_infoTryAgainLater(codec, index)) {
856         if (s->draining) {
857             av_log(avctx, AV_LOG_ERROR, "Failed to dequeue output buffer within %" PRIi64 "ms "
858                                         "while draining remaining frames, output will probably lack frames\n",
859                                         output_dequeue_timeout_us / 1000);
860         } else {
861             av_log(avctx, AV_LOG_TRACE, "No output buffer available, try again later\n");
862         }
863     } else {
864         av_log(avctx, AV_LOG_ERROR, "Failed to dequeue output buffer (status=%zd)\n", index);
865         return AVERROR_EXTERNAL;
866     }

也就是说787行的函数调用ff_AMediaCodec_dequeueOutputBuffer返回是<0的,通过打断点进入到该函数里面:

ssize_t ff_AMediaCodec_dequeueOutputBuffer(FFAMediaCodec* codec, FFAMediaCodecBufferInfo *info, int64_t timeoutUs)
{int ret = 0;JNIEnv *env = NULL;JNI_GET_ENV_OR_RETURN(env, codec, AVERROR_EXTERNAL);ret = (*env)->CallIntMethod(env, codec->object, codec->jfields.dequeue_output_buffer_id, codec->buffer_info, timeoutUs);if (ff_jni_exception_check(env, 1, codec) < 0) {return AVERROR_EXTERNAL;}//代码省略
}

发现每次第7行CallIntMethod的调用都是返回-1,这是native对java层接口的访问,也就是说压根获取不到outputbufferid,跟踪发现其中的参数timeoutUs总为0,其他参数也都有值,貌似都正常,而CallIntMethod无法step into进去,所以也看不到它调用的java的接口实现。至此,貌似就断了,走不下去了。

其实它这里调用的正是我们在java层经常用的MediaCodec的dequeueOutputBuffer()接口:

libavcodec/mediacodec_wrapper.c 中有如下方法映射:{ "android/media/MediaCodec", "dequeueOutputBuffer", "(Landroid/media/MediaCodec$BufferInfo;J)I", FF_JNI_METHOD, offsetof(struct JNIAMediaCodecFields,dequeue_output_buffer_id), 1 },
实际java代码调用是类这样:
int outBuffId = mMediaDeCodec.dequeueOutputBuffer(mBuffInfo, 3000);

java层的这个接口的最后一个时间参数给0或者3000都没所谓啊,我还特意试了的,没有影响,都能正常获取buffer的id。

求助无门,后来想着是不是还有一些ffmpeg的打印没用输出到logcat啊,我把打印都保存到文件试试噶:

static void my_logoutput(void *ptr, int level, const char *fmt, va_list vl) {FILE *fp = fopen("/storage/emulated/0/Android/av_log.txt", "w+");if (fp) {vfprintf(fp, fmt, vl);fflush(fp);fclose(fp);}
}

不改不知道一改吓一跳,这样修改之后,居然硬解码成功了,之前的错误没有了,也就是说加了这个log保存文件的功能后,能正常获取到outputbufferid了。

按理说我这里的my_logoutput实现和获取buffer id应该没有关系才对啊,要说有关系,无非就是这里的打开文件写文件关闭文件多耗了点时间,对了就是时间,再回头想想那个timeoutUs参数,如果我把这个参数改为非0,比如改为8000会怎么样,于是我直接把ffmpeg拖出来修改:

ffmpeg4.4/libavcodec/mediacodecdec_common.c#define INPUT_DEQUEUE_TIMEOUT_US 8000#define OUTPUT_DEQUEUE_TIMEOUT_US 8000#define OUTPUT_DEQUEUE_BLOCK_TIMEOUT_US 1000000//此处省略几百行代码int64_t output_dequeue_timeout_us = OUTPUT_DEQUEUE_TIMEOUT_US;if (s->draining && s->eos) {return AVERROR_EOF;}if (s->draining) {/* If the codec is flushing or need to be flushed, block for a fair* amount of time to ensure we got a frame */output_dequeue_timeout_us = OUTPUT_DEQUEUE_BLOCK_TIMEOUT_US;} else if (s->output_buffer_count == 0 || !wait) {/* If the codec hasn't produced any frames, do not block so we* can push data to it as fast as possible, and get the first* frame */output_dequeue_timeout_us = 0;}//特里同学hackoutput_dequeue_timeout_us = OUTPUT_DEQUEUE_TIMEOUT_US;index = ff_AMediaCodec_dequeueOutputBuffer(codec, &info, output_dequeue_timeout_us);

25行处是我添加的代码,给它个8ms反应时间(如果是0应该是立即返回),再重新编译后拷贝so到androidstudio那边,把my_logoutput注释掉,重新运行,发现硬编解码正常,问题解决了。

现在想想有点觉得不可思议,我一个ffmpeg新手就这么把ffmpeg的源代码给改了,为了自己的目的把大名鼎鼎的ffmpeg给hack了,我始终不太相信这就是root cause,肯定是我哪里没搞对才让timeoutUs这个值成了0,才导致了获取outputbufferid始终为-1,(比如13行的draining应该为1才对,瞎猜的啊)有知道的大佬请务必指点指点。

当我们像无头苍蝇一样无助的时候,这也算一种解决方案吧,你说呢?!

74539d553778aa37ed0f8a90139583a1.png

技术交流,欢迎加我微信:ezglumes ,拉你入技术交流群。

1a3e7df7b1297ddeaafde7b065efb803.png

私信领取相关资料

推荐阅读:

音视频开发工作经验分享 || 视频版

OpenGL ES 学习资源分享

开通专辑 | 细数那些年写过的技术文章专辑

NDK 学习进阶免费视频来了

你想要的音视频开发资料库来了

推荐几个堪称教科书级别的 Android 音视频入门项目

觉得不错,点个在看呗~

09dd30a02a63cba761c7795b5356dad4.gif


http://chatgpt.dhexx.cn/article/gJRqFRgK.shtml

相关文章

APK加固(梆梆助手)

前言&#xff1a;朋友在使用梆梆时出现Apk加固后安装失败的现象&#xff0c;所以自己写篇小白文 1.进入梆梆官网&#xff08;注册&#xff09; 2.下载梆梆助手&#xff08;点击加固工具&#xff09; 本人电脑是Windows系统&#xff08;下载&#xff09; 3.安装后&#xff0c…

使用360进行apk加固并进行2次签名整体流程

因新版360加固助手需要付费才能进行自动签名&#xff0c;故只能自己手动来签名了~ 1.使用Android studio进行首次签名并打包apk 首先选择build下该选项 选择apk 如果没有key&#xff0c;则点击新建 需要输入key存储的位置&#xff0c;key store密码&#xff0c;key别名&#…

Android-APK加固-简单版

Android-APK加固-简单版 Proguard的使用与配置介绍开启proguard常用配置 加固大体思路 源码&#xff08;浅析&#xff09;思路 撸码解密工具类-AES&#xff08;解密时用&#xff09;工具类-Zip&#xff08;压缩、解压&#xff09;工具类-Utils&#xff08;反射操作&#xff09;…

Android Apk加固后手动签名

手动签名 &#xff1a; 不用任何第三方可视化工具签名 &#xff0c;使用命令做签名。手动签名原因&#xff1a;以前加固签名都是使用第三方工具操作&#xff0c;最近发现工具都开始收费了&#xff0c;免费的羊毛没得薅了&#xff0c;收费价格极高 5000/年/App, &#xff08;加固…

手写apk加固

手写apk加固 加壳解压原apk并加密重命名dex文件对壳文件操作打包压缩成apk文件签名 脱壳运行解压原apk, 解密原dex文件加载原dex文件 demo下载 apk加固的目的其实就是对app的核心代码做防护工作&#xff0c;避免被其他人反编译&#xff1b; 废话不多说了&#xff0c;直接开始! …

android apk 加固后重新签名

针对于加固平台在加固的过程中不能配置签名文件&#xff0c;加固后的apk需要进行重新签名才能安装&#xff0c;并发布到应用市场。 第一步&#xff0c;用AS对项目进行打包&#xff0c;生成签名的apk文件。 第二步&#xff0c;使用加固平台&#xff0c;对apk包进行加固&#xff…

Android Apk加固原理解析

前言 为什么要加固 对APP进行加固&#xff0c;可以有效防止移动应用被破解、盗版、二次打包、注入、反编译等&#xff0c;保障程序的安全性、稳定性。 常见的加固方案有很多&#xff0c;本文主要介绍如果通过对dex文件进行加密来达到apk加固的目的&#xff1b; APK加固整体…

apk加固后再签名

目录 前言v1签名v1v2签名 前言 apk更新之前需要做安全检测&#xff0c;检测之前一版会做加固处理&#xff0c;加固后还需要重新进行签名。本文介绍一下v1签名和v1v2签名两种方式。 有文章说需要把apk原来的签名文件&#xff0c;即META-INF文件夹删除&#xff0c;实测不删好像也…

Android apk 加固混淆的作用之解决apk报毒

现在市面上对apk的安全合规管控越来越严格了&#xff0c;也就要求了apk在上架之前一定要做合规检测和加固处理。对apk就是加固的好处&#xff0c;可以提高apk的安全性&#xff0c;提高apk被逆向分析破解的门槛&#xff0c;同时通过加固保护可以提高过安全合规的检测。由于APP加…

简书 android 加固,Android apk加固(加壳)整理

一、Dex加壳由来 最近在学习apk加密&#xff0c;在网上看了一篇《Android中的Apk的加固(加壳)原理解析和实现》&#xff0c;我发现原文把整个apk都写入到dex文件中&#xff0c;如果apk小还好&#xff0c;当原APK大于200M&#xff0c;客户端解壳很费劲&#xff0c;打开后应用就卡…

019 Android加固之APK加固的原理和实现

文章目录 前言加载Activity遇到的问题APK的启动过程替换ClassLoader流程获取ActivityThread类对象获取AppBindData类对象mBoundApplication获取LoadedApk类对象info获取info对象中的ClassLoader 设计傀儡dex文件手工加固APK代码实现APK加固实现步骤 总结 前言 动态加载dex之后…

【Android 安全】Android 应用 APK 加固总结 ( 加固原理 | 应用加固完整的实现方案 | 源码资源 )

文章目录 一、 APK 加固原理1、 Android 应用反编译2、 ProGuard 混淆3、 多 dex 加载原理4、 代理 Application 开发5、Java 工具开发6、Application 替换 二、 应用加固完整的实现方案1、 代理 Application( 1 ) ProxyApplication( 2 ) OpenSSL 解码 Kotlin 类( 3 ) 反射工具…

android资源加固,Android apk加固实现原理

apk加固是每一个app发布之前必须要做的事情;如果一个apk没有加固那么别人就很容易被别人反编译&#xff0c;看到这其中的原码&#xff0c;虽然现在有代码混淆、把业务写到native层&#xff0c;但是这都是治标不治本。反编译的技术在更新&#xff0c;那么保护Apk的技术就不能停止…

Android中Apk加固代码实现

前言&#xff1a;上一篇博客已经把Apk加固的思路详细的介绍过了&#xff0c;也开始创建了一个空的demo进行&#xff0c;然后在项目中添加一个代理module&#xff08;解密&#xff0c;和系统源码交互功能&#xff09;和tools工具加密Java library 的module &#xff0c;这里开始…

Android APK加固原理

一、前言 Android作为开源框架&#xff0c;开放之余&#xff0c;所要面临的就是安全问题&#xff0c;世间之事&#xff0c;有正就有邪&#xff0c;有攻就有守&#xff0c;作为开发者虽然不需要进入专业安全领域&#xff0c;但还是需要掌握基本的安全常识和原理。 二、加壳 加…

APK加固原理详解

一、前言 之前使用的360加固&#xff0c;挺好用的&#xff0c;从2021年底的时候限制每天每个账号仅上传2次apk&#xff08;免费的&#xff0c;不知道VIP的是不是这样&#xff09;。通过这个事情&#xff0c;感觉技术还是掌握在自己手里稳妥点&#xff0c;不用受制于人&#xf…

Android中的Apk的加固(加壳)原理解析和实现

本文转载自&#xff1a;Android中的Apk的加固(加壳)原理解析和实现 - roccheung - 博客园 一、前言 今天又到周末了&#xff0c;憋了好久又要出博客了&#xff0c;今天来介绍一下Android中的如何对Apk进行加固的原理。现阶段。我们知道Android中的反编译工作越来越让人操作熟…

浅谈安卓apk加固原理和实现

转载本文需注明出处&#xff1a;微信公众号EAWorld&#xff0c;违者必究。 引言&#xff1a; 在安卓开发中&#xff0c;打包发布是开发的最后一个环节&#xff0c;apk是整个项目的源码和资源的结合体&#xff1b;对于懂点反编译原理的人可以轻松编译出apk的源码资源&#xff0c…

安卓逆向笔记--apk加固

安卓逆向笔记–apk加固 资料来源: 浅谈安卓apk加固原理和实现 Android中的Apk的加固(加壳)原理解析和实现 前两个太老了所以具体代码借鉴下面的 Android Apk加壳技术实战详解 一、apk常见加固方法 (1)代码层级加密–代码混淆 代码混淆是一种常见的加密方式。本质是把工程中原…

imx6ull uboot移植

以下内容来自&#xff1a;正点原子Linux驱动文档 一、简介 uboot移植主要是根据原厂的uboot移植&#xff1a;芯片厂商通常会做一块自己的评估板并发布BSP&#xff0c;当我们需要定制自己的开发板时可以根据自己的需求&#xff08;硬件上的不同&#xff09;&#xff0c;对原厂…