关于 Cookies 的一些有的没的

今天是我们 App 发版的日子,产品小姐姐突然跟我说,百度应用市场登录不上去了。我打开她发的链接 http://app.baidu.com ,在 Chrome 上输入账号密码登录,完成后却还是未登录的模样。

没事,反正百度那也没几个下载量,下次再说!

咦,不对,上次好像就说了同样的话…

好吧,那该仔细思索一下了。偶尔挂还好说,谁没几个 Bug,可是这都一个多月了啊?

我看着浏览器陷入了沉思,然后我灵光一现把链接换成了 https,然后就正常了!

三三吐槽了一下百度这 Bug,他告诉了我一个有趣的现象:在 FireFox 上其实是正常的,这个 Bug 是因为 Chrome 没有遵守标准。

这样那问题就有趣起来了,看来并不是一个简单的疏忽。因此我决定把整个问题复盘一下,看看这背后究竟是怎么回事。

现象描述

在 Chrome 上,打开 http://app.baidu.com 进行登录,完成后 http 页面并没有登录状态,和未登录一样。而此时直接访问 https://app.baidu.com 却会发现已经登录了。

在 FireFox 上,打开 http://app.baidu.com 进行登录,之后无论是 http 还是 https 页面皆是正常。

而且重启大法,重装大法都不好使。

定位问题

本文属于科普向的,因此会有一些基础概念的介绍,会用引用表示,熟悉者可跳过。

既然区别在 Http 和 Https 上,那首先简要说明一下它们是什么:

Http

Http 是超文本传输协议,可以简单理解成访问网页用的协议。类似的协议还有 FTP、SSL 等。

Https

Https 简单说就是加密的 Http。

Http 是明文的,不够安全,还可能被篡改。在早期很多网上冲浪的用户会发现某些本没有广告的网站会出现广告,或者网站下 Apk 结果下下来是另一个 Apk。这些都是因为传输协议用的是 Http,可以被运营商或者路由器篡改,这种现象就叫运营商劫持。而 Https 可以避免这些问题。

还需要简单了解的一下是 Cookies:

Cookies

HTTP 协议是无状态的,意味着在原始情况下,你连续两次访问同一个网站,网站是不知道这两次访问是同一个人的。这会带来什么样的问题呢?会导致用户没法在不同的页面做连续的事情,比如我在淘宝的商品页选好商品下单,跳到付款页面下单,纯从 HTTP 协议的角度来说,网站在付款页面是不知道你商品页面选了啥的,也不知道你是谁。

当然,这种情况并没有发生,聪明的你肯定也想到了,我们可以在每次访问新的页面时把需要的数据传给它,就可以把两个网页连接起来了。有关这部分的具体知识,很推荐一篇文章 有关 Session 的那些事儿。再具体一些的内容因为不是本文重点就不展开了。

简而言之,就是当你登录后,网站会为了辨认你是谁,将你的身份信息用一串字母表示,返回给浏览器,浏览器在之后每次访问这个网站时都会把这串字母附带上,这样网站就能辨认出你了。

Cookies 就是一个浏览器用来存”网站让浏览器帮忙存的数据”的地方。

结合上面的知识,在 Chrome 上,我们通过 http 进行登录,之后 https 能正常显示说明登录状态(也就是 Cookies)是正常的,而在 http 页面没有登录,说明很可能是 Cookies 出了问题。

在 Chrome 的控制台,我们可以很轻松看到网站具体存了哪些 Cookies。

我们先查看 http://app.baidu.com 的 Cookies 信息,如下图。之前提到过 Cookies 里的内容是网站用来鉴别用户身份的,因此如果泄露别人就可能可以直接登录你账号了,所以需要打码。

之所以说”可能”,是因为网站检测到这次请求不符合常用电脑、常用地点时,可以增加额外的二次验证(都是隐私啊=。=)

image-20210423152518438

再查看 http://app.baidu.com 的 Cookies 信息。

image-20210423155631947

经过对比我们可以发现,https 的 Cookies 里比 http 多了 BAIDUID_BFESS_yjs_duidBDUSS_BFESS这三个 Key。

在这里我们需要更详细地了解一下 Cookies 的机制:

Cookies 的传输

对于一次网络请求,可以分成两部分看待:请求(Request)与响应(Response)。

请求就是用户把自己想要浏览的内容和需要的数据发给服务器,比如登录可以是请求 http://example/login?username=admin&password=123456 。访问网站的登录页(example/login),并携带用户输入的要登录的账号(admin)密码(123456)。当然实际的登录不会这样简单,这有一些安全问题。

响应就是服务器接受到了你的请求,把它决定返回的内容发给你。

再具体到请求/响应的格式,它们都由三部分构成:起始行/状态行、请求头/响应头(Headers)、正文(Body)。

起始行/状态行可以先不管。Headers 是用来传递一些用户不需要看见的内容,比如约定的格式,用的什么浏览器,和一些约定的数据。Body 则是用户实际可见的内容,比如用户上传一张图片,Body 就是图片本身,用户浏览一个网页,Body 就是肉眼可见的那些内容。

那么创建 Cookies,就是网站在响应的 Headers 里添加一个 Set-Cookie 字段,这样浏览器之后每次对该网站请求都会携带这些信息给网站。

比如这个响应就可以是:

HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry

[页面内容]

而我们之后再访问该网站,请求就可以是:

GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry

而 Cookies 除了基本的要传递的内容,还能更精准地控制内容的生效访问。比如失效时间、可以在哪些网站共享等等。

Cookies 的参数

比如一个复杂一点的 Cookie 是这样的:Set-Cookie
STOKEN=61d02***96; expires=Sat, 24-Apr-2021 04:00:27 GMT; path=/; domain=passport.baidu.com; secure; httponly

可以简单分为两部分,一部分是具体的内容,一部分是附带选项。

比如它要求浏览器保存一个参数名为STOKEN,内容是 61d02***96的 Cookies。然后它的附带选项是:1、expires 意思是过期时间是 2021 年 4 月 21 日;2、secure 是开启仅 https 发送;3、httponly 是开启仅服务器可获取。

附带选项有这么几种:Expires、Max-Age、Secure、HttpOnly、Path、SameSite。

详细的不一一展开,具体可参考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies

重点介绍 Secure:标记为 Secure 的 Cookie 只应通过被 HTTPS 协议加密过的请求发送给服务端。

再观察上面百度的 Cookies,发现 BDUSS_BFESS 含有 Secure 属性,因此可以猜测:这个包含用户身份信息的 Cookie 在 http 的情况下没有携带,在 https 的情况下正常携带,因此在 Chrome 下出现 http://app.baidu.com 无法登录,而 https://app.baidu.com 正常的情况。

那么,为什么 FireFox 会正常呢?

我们先观察一下 FireFox 登录后的 Cookies 信息:(http 和 https 两种情况下内容一致)

image-20210423190327326

可以发现:

1、FireFox 下 Cookies 条目少了很多;(原因我们暂不知道)

2、包含用户身份信息的 Cookie 条目为 BDUSS;(为何和 Chrome 不为同一个原因未知)

3、BDUSSSecure 属性没有被设置。

因此,为啥会产生 FireFox 正常,Chrome 不正常的现象的技术原理我们知道了。现在的问题变成了为什么会发生包含身份信息的 Cookie 在 Chrome 下是 Secure 的,在 FireFox 下不是 Secure 的。

根据前文的三三大佬的说法,按照 HTTP 标准,Https 的 Cookies 如果没有携带 Secure 字段,那么是可以和 Http 共享的。而 Chrome 觉得这样不安全,把它们分开了。

基于这种设想,我猜测是不是 Chrome 是把服务器传来的 Cookies 在客户端存储时强行加上了 Secure 字段,从而实现了这个功能。

猜想1:Chrome 把服务器传递的 Cookies 自行加了 Secure 字段

要验证这个猜想,需要查看服务器返回的原始的 Set-Cookie 字段。依旧通过 F12 开发者工具查看:

Chrome 的:

image-20210423194326889

FireFox 的:

image-20210423194532149

我们可以发现,Chrome 的原始 BDUSS_BFESS 的确有 Secure,因此猜想1基本可以认定是错的。

但是,我们同时也发现了,Chrome 的响应里也出现了 BDUSS,值跟 BDUSS_BFESS 是一样的,而它是没有 Secure,这点与 FireFox 一致。

因此我们衍生出来猜想2:百度对于所有的浏览器都是以 BDUSS为存储用户身份的字段。但是在 Chrome 下这样做失败了,因此用额外的逻辑即 BDUSS_BFESS 进行补救,但是此方式没有 http 场景。

猜想2:百度存储的 BDUSS 字段的 Cookies 在 Chrome 下因为某些原因失败了导致 http 状态无法登录

我在测试过程中,惊奇地发现自己的笔记本并没有复现这个情况。

image-20210424120926609

在 http 状态下,BDUSS 被正确地保存了下来,也因此没有出现 http 页面没法登录的现象。

我第一想法是浏览器拓展导致的,但是两台电脑的 Chrome 配置都是同步的。再检查浏览器版本,问题版是 90,正常版是 85。对 85 进行更新,然后惊喜地发现果然不行了。

再仔细观察 Chrome 的内容,以及切换到 Cookies 页面查看,发现有一部分”异常”数据:

image-20210424151124830

感叹号提示内容:

This Set-Cookie didn’t specifty a “SameSite” atribute, was defaulted to “SameSite=Lax”, and was blocked because it came from a cross-site response which was not the response to a top-level navigation. This response is considered cross-site because the URL has a different scheme than the current site.

好吧,这里提到了 SameSite,之前介绍 Cookies 参数时只介绍了 Secure,看来还是需要介绍一下 SameSite,不过在此之前我们需要简要说明一下 CSRF

CSRF

全名 Cross-site request forgery,即”跨站请求伪造”。

首先,我们知道每次访问网站,本质上就是携带一些参数去请求一个链接。那我们假设给某条微博点赞的链接是 https://weibo.com/aj/v6/like/add?id=123456,那当用户按下点赞按钮时,会访问如上所述的链接,给 ID 为 123456 的微博点赞,根据我们之前的知识,这次请求自然会携带 Cookies,这样服务器就知道是你点赞了。

那什么是 CSRF 呢?顾名思义,跨站请求伪造,就是用户不知道的情况下(对应:伪造)在 A 站访问了 B 站的内容(对应:跨站请求)。再具体一点,比如我这篇文章里,我骗你点上面那个链接,当你恰好登录了微博的时候就自动给这条微博点赞了。

当然,你肯定会觉得这样有点低端,还需要诱骗用户点击。当然也有直接访问的办法了,比如我上传一张图片,而图片的链接我用了上述链接,这样当你访问这个网站时,会自动加载图片,自然也就访问上述链接了。

这就是一个简单的 CSRF 例子。现实中想实现类似的攻击会比这复杂一些。在多年以前经常会有微博用户发现在自己不知情的情况下转发点赞了一堆广告微博,虽然背后的原因我不清楚,但可能也是类似 CSRF 的手段。

SameSite

SameSite 是一个较新的字段,加入它的原因就是为了解决上面的 CSRF 问题。它的作用是:服务器可以设定 Cookie 在跨站请求时会不会发送。

它有三种值:None、Strict、Lax。

None:浏览器会在相同站点请求、跨站请求时都依旧发送 Cookies。

Strict:浏览器只会在相同站点请求时发送 Cookies。

Lax:浏览器会在相同站点请求时发送 Cookies,但是在一部分跨站请求会发送 Cookies。这部分特殊的请求方式为链接、GET 表单、预渲染。

三种值的用户意义是:

None 会像以前一样,依旧有 CSRF 风险;

Strict 能完全避免 CSRF,但是会显著降低用户体验,比如我在知乎看到了个带货回答点开了淘宝链接,本来我已经登录过了淘宝,但是因为禁止跨站传递 Cookies,我还得重新登录;

Lax 是 Strict 的温和版,它允许了一些情况可以跨站传递 Cookies,避免了上述知乎打开淘宝需要重登录的问题。

因此,我们再去理解上面 Chrome 提示的内容,它说这个网站返回的这条 Cookie 没有指定 SameSite,因此 Chrome 把它默认设置为了 SameSite=Lax。并且被阻止了,因为它是一个跨站响应。而为什么认定是跨站响应呢,因为这两个 URL 有不同的 scheme

Scheme

我们给链接分组(区分是不是同一个网站之类的)的规则是:

<scheme>://<host><path>

Scheme 的值可以是:http、https、file、ftp、app、*。

我们知道,Chrome 会把它魔改的地方都加个开关放到 flags 页面里。因此我们打开 chrome://flags,搜索 SameSite,内容如下:

image-20210424172407325

阅读它的说明,你就会明白这件事是个什么情况了。

「SameSite by default cookies」:默认开启 SameSite=Lax,这个修改是在 Chrome 85 上进行的:Feature: Cookies default to SameSite=Lax。实际上在 80 也改过一次但是又改回来了,具体原因就不展开了。

「Cookies without SameSite must be secure」:你的 Cookies 在没有设置 SameSite 时,如果没有 Secure 属性就会被拒绝。

「Schemeful Same-Site」:在 SameSite 的是否相同站点计算时,不同的 schemes 也被认定为不同站点。也就是 http://app.baidu.comhttps://app.baidu.com 不被认为是相同的站点。

因此,我们把「Schemeful Same-Site」设为 Disabled,就会发现,之前不正常的 http 可以正常登录了。

总结

这个现象的原因就是,Chrome 在新版强行给没有设置 SameSite的 Cookie 设置了,并且对同站的判断没有遵循标准,而是认定 http 和 https 不同站。以至于 http://app.baidu.com 没法登录。

=。=因为我不是 Web 前端,所以对相应技术敏感性有些欠缺,不然可以更快定位。但如果我们是百度的 Web 前端开发,那该怎么解决这个问题呢?有下面几种方案:

1、全站执行 http 自动跳转 https。百度首页 http://baidu.com 就是这样做的,而 http://app.baidu.com 还没跟进。

2、http 下不跳转到 https 执行登录。这样 http 和 https 就是两套 Cookies 了,不会出现之前的 http 没法登录,但是直接切 https 发现已经登录了的情况。但问题在于 http 是明文的,用户的账号密码有一定风险。

3、对包含鉴权信息的那条 Cookie,添加 SameSite=None。这样的问题是依旧面临 CSRF 的风险。

4、让用户换 FireFox。

Android 工程模块化设计注意事项

在最新的一个项目里,因为遇到了多客户端的需求,是一个类似滴滴的项目,需要分货主端和车主端,互相有些差异也有一些共用的地方。那么怎么做是最便于维护与拓展的呢,我当时想了这样几个方案:
– 单 Project 单 Module,通过 gradle 进行差异化打包,而日常开发中对于能复用的页面添加 type 参数来区别。优点是简单方便。缺点是维护起来比较混乱。
– 单 Project 分主 Module 与次 Module,比如以车主端为主 Module,主要代码与资源都在该 Module 里面,而货主端引用该 Module,并进行定制化开发。优点是同样比较简单方便,也较为清晰。缺点是货主端会引入很多用不上的资源。
– 单 Project 基础 Module 两个客户端 Module,把基础控件和能共用的部分都封装到基础 Module 里,两个客户端 Module 各自引用它们。优点是清晰易维护。缺点是使用多 Module 本身存在的一些问题。
– 俩 Project,写完一个客户端后复制粘贴代码。优点是成本低难度最小,缺点是改起来恶心。

就观感而言应该是单 Project,分基础 Module 和 客户端 Module 最佳。但是多个 Module 协同开发之间还是存在一些问题的,因此写下此篇文章。由于文章还没竣工,不敢妄称指南,先取名为注意事项。

跨 Module 引用 kotlin 的代码异常,提示「Unresolved reference: xxx」

问题详情:在基础 Module 里写入的一些 Kotlin 方法,在客户端 Module 引用时编译不能通过,提示「Unresolved reference: xxx」,基础 Module 设置的 Application 在运行时会提示「Unable to instantiate application xxx.BaseApplication: java.lang.ClassNotFoundException」。
问题排查:创建了 Java 文件发现一切正常,因此判断是 kotlin 的问题。猜测是因为 kotlin 的作用域与 Java 不一样,但是设置了对应作用域修饰符后依旧无用。搜索结果里有很多干扰内容,最后 ‘Unresolved reference’ errors for android library module referenced in app module 解决了问题,因为作为 Library 的基础 Module 没有正确配置 kotlin。检查是否有以下内容:

apply plugin: 'kotlin-android'

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlinVersion:<version>"
}

我是遗漏了apply plugin: 'kotlin-android',但是假如客户端 Module 不调用对应方法的话是能正常运行的,而且也是通过 AS 的 new module创建的,所以这一块应该是属于 AS 的 bug 吧。
很多这样类似的问题有人喜欢称之为坑与踩坑,我不太喜欢这种说法,我觉得主要还是因为我蠢。

//后续内容待更新

惠普暗夜精灵4拆机/加装内存/拆后盖指南

加装内存不是什么难事,也不是第一次了,但是刚换的这台暗影精灵4的后盖真TM难拆。用手机各种百度也没几个靠谱的,在友人的帮助下看了官方的拆机文档然后发现也没什么用,所以还是写下这篇文章供世人参考。

不过我写文章有个劣习就是不喜欢发图片,虽然我也知道这种拆机文章图多视频多才好理解。。。

官方文档:http://h10032.www1.hp.com/ctg/Manual/c06034959,拆机部分在35页。

拆后盖

第一步:卸掉8颗螺丝。
我很轻松地卸掉了7颗,但是有一颗拧得很紧(贴吧也有一个人和我一样的情况,不知道是不是普遍问题),我和同事都尝试失败后我便去电脑店找人用专业点的螺丝刀拧开了。注意电脑的螺丝拧不开请不要强行拧,不然把螺丝拧花了问题就比较麻烦了。通常拧不开只是螺丝刀质量不行。

第二步:打开卡扣。
不得不说现在的笔记本外壳真是进步了,缝隙极窄压根找不到下手的地方。建议有条件的去淘宝买些翘片,这样应该不会像我这么难受了。
官方文档标注的是有3处卡扣,弄开后摇动一下即可打开后盖,都是骗人的。

暗影精灵4后盖的风扇靠后部分的缝隙大一些,找你觉得合适的工具把这两部分撬开卡扣,然后一路划过来,那么前面两侧以及背面的两端都松开了。然后此时我本以为差不多了,官方文档与网上的大部分资料都说用力掰开或者往前面拉,甚至我还在思考这个后盖是不是凹形状的,因为背面接口的那部分实在是紧。但是事实就是那部分很紧,有翘片的人可能比较幸福,我是死活才把信用卡塞进去了一部分然后很用力才拆开了这部分。然后风扇到接口处应该也有两个卡扣,这也是最后的两个卡扣了,弄开来就可以很轻松地掀开后盖了。

加装内存条

内存的位置是被一个黑色的防尘塑料遮住了,你需要把塑料撕开来才能弄,按贴吧人的说法是不会失去保修的。然后装内存条也很简单,我们把有磁针的一段称为前面,步骤是先前面与机箱的插口对接好,然后再把后面按下去。而不是像以前我们玩小霸王那样的把内存条与另一根内存条以平行的方式插进去。(这样理论上也行但是毕竟麻烦)

其他好像没啥可以说的了。

Android 编译 FFmpeg 产生「error undefined reference to ‘av_register_all()」等错误的多种原因

本文主要内容:讲述遇到集成 NDK 时遇到的「error: undefined reference to」的解决方案

集成 NDK 有很多方式,比如 ndk-build 后把 so 库直接链接使用,或者通过 CMake 的方式链接,通常推荐后者,因为这样编写调用 native 方法方便,不需要像前者每次需要重新打包。
在实现 FFmpeg 播放视频功能时,我参考 Android Studio FFmpeg视频解码播放 该文章操作后,始终无法成功编译,按照网上的种种解决方案(比如用 C 的方式引入头文件)都无法解决,最后一步一步跟能成功运行的项目对照后终于解决。

具体报错情况为一堆 FFmpeg 相关的方法提示 「error: undefined reference to XXXX」。网上的解决方案基本都为用「extern “C”{}」包裹,原因是 FFmpeg 是一共纯 C 的项目,C++ 想正确调用 C 代码则需使用这样的方式,具体示例如下:

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
#include "libavutil/opt.h"
#include "libavutil/imgutils.h"
}

该方案能解决一部分人问题,于我却无用,随后我检查了各种引入方式,以及 NDK 的语法后,确定问题其实是出在 CMakelists.txt 文件上。
继续阅读“Android 编译 FFmpeg 产生「error undefined reference to ‘av_register_all()」等错误的多种原因”

Windows 交叉编译 Android 版 FFmpeg 心得

本文主要内容:介绍一些在 win10 系统下打包出 Android 需要的 FFmpeg so 库的注意事项。

最近需要写一个短视频 Android 项目,经与领导讨论后决定不用市面上的短视频 SDK,采用 FFmpeg 实现。如何集成我选定了一篇质量不错叙述详细的教程 Android 集成 FFmpeg (一) 基础知识及简单调用 ,本以为照着来可以很轻松集成好的,没想到居然花了两天时间。现把注意事项分享给大家,供后人查阅。

假如你也有交叉编译 FFmpeg 的需求,推荐你参考上面那篇文章,遇到问题了再看看我这是否有解决方案。教程作者(后面将用该词称呼上面文章的作者)与大部分交叉编译 FFmpeg 教程的作者使用的都是 Mac,能省很多事,这让我煞是羡慕。然而我并没 Linux 环境,因此我使用了 win10 的 Linux 子系统,选择了 Ubuntu。大致步骤为「设置里开启开发者选项」→「程序和功能里启用 适用于 Linux 的 Windows 子系统」→「应用市场里安装 Ubuntu」,这个步骤很简单,网上有很多指导,我就不详细描述了。然后在命令行输入「bash」即可进入 Ubuntu,在这里 C 盘映射到了「mnt/c」目录下(其他盘类推),因此我将所有相关资料都放到了 D 盘的「Share」文件夹下,方便使用。

sh 脚本报错各种 not found

我对 sh 脚本不熟悉,因此此处尝试过其他人的编译脚本才发现问题所在,教程作者的注释影响了脚本的正常运行,你需要将所有「\」后的空格与注释都删掉才可正常运行。
同时,还有一个可能导致这个错误,就是你使用了 Windows 的换行符,假如你用的是 vscode,直接右下角将「CRLF」改成「LF」即可,其他编辑器解决方案可自行搜索。

ERROR: libmp3lame >= 3.98.3 not found

请先确保你下载了教程作者编译好的 libmp3lame,并在 sh 脚本里修改了对应路径。如果你跟我一样不方便下载 CSDN 积分内容的话,我把它传到了我的百度网盘 链接(密码:5jm6)的 lame 文件夹。
同时,你很有可能跟我一样下载且更改路径之后依然存在这个问题,当你搜索该问题时可能很多人会建议你安装 libmp3lame,Ubuntu 的话命令是「apt-get install libmp3lame-dev」,假如找不到请「apt-get update」一下。虽然我对该操作是否有效持怀疑态度(我们需要的是交叉编译出 Android 端的 FFmpeg,而不是在你的 Ubuntu 上使用 FFmpeg),但是似乎是因此解决了该问题。

make -j4 的时候各种提示 「fatal error: stdlib.h: No such file or directory」等头文件缺失

这是个很蛋疼的问题,在此时(2018年6月),我使用的都是最新版(ndk 17,FFmpeg 4.0),居然会有兼容问题而且似乎还是由来已久的,相关介绍:Unified Headers,我尝试使用它说的「-D__ANDROID_API__=$API」并没有成功(很可能是我姿势不对),最后把 NDK 版本换成了 15 解决(注意你应该下载 Linux 版的 NDK)。

如果你成功解决这些问题,你应该成功生成了动态库,如果你遇到了暂时没法解决的问题想绕过,那么同样可以直接下载我编译好的版本:百度网盘 链接(密码:5jm6)的 arm 文件夹。剩下的内容就不需要 Linux 相关了,只需要你使用 ndk 即可。(注意此时你应该使用 Windows 的 ndk)

ndk-build 时报错 「make: *** No rule to make target jni_FFmpegJni.c', needed byobj/local/armeabi-v7a/objs/ffmpeg/com_jni_FFmpegJni.o’. Stop.」

不用怀疑了,不是网上说的那些什么缓存问题,就是你的 Android.mk 写错了,仔细检查。注意教程作者贴的 Android.mk 里的 「libpostproc」我们是没有的,以及对应的版本也都更新了,其实你可以在 Android.mk 里写「libavcodec-*.so」来忽略版本问题,或者链接到目录底下的快捷方式。

然后是一个比较严重的问题,按照教程作者的第二篇文章修改 ffmpeg.c 等相关内容后,ndk-build 始终无法成功。此处我尝试过很多方法都没有解决,最后采用了其他人的实现方式,而且我觉得 CMake 的方案其实优于教程作者介绍的这种,该部分可以参考在Mac下编译 FFmpeg ,并在Android中使用

以及后续 FFmpeg 相关学习推荐参考雷神的FFMPEG视音频编解码零基础学习方法