关于 Android 6.0 的流媒体播放异常

好久没来更了~ 


公司最近俩月处于一种神奇的状态, 各项目状态慢吞吞, 各项目 Leader 也一点都不着急, 陆陆续续的一些老员工离职, 让我这个只来了一年出头的新员工有些紧张 ---  难道才出旧坑又入新坑吗?  说好的和某通并购呢?  半年前大家和某讯打嘴仗的时候可不是这个状态啊 !  

项目上没什么压力, 就多看看书, 也趁着机会多学习某通的代码和文档, 测试那边也没什么正经的 bug 过来;// MAGIC1. DO NOT TOUCH.  BY 冗戈微言 http://blog.csdn.net/leonxu_sjtu/



然后有一天, 忽然有个流媒体的 BUG 抄给了我...   我搞了N 年的流媒体, 但到了这个山寨以后就放手了,何必抢其他人饭碗呢? 但兴许是这段时间太闲了, 这次就看看吧:


问题很好描述, 播放 iqiyi, sohu 等在线视频, 不论走 wifi还是4G, 每次都是播了十几分钟, 卡住不动了--- 
既然时间点这么固定, 就不像是网络问题了, 何况 wifi/4G 表现一致; MAGIC2. DO NOT TOUCH.  BY 冗戈微言  http://blog.csdn.net/leonxu_sjtu/

看了眼提供的 log, 有个异常:
34712 11-22 19:45:28.906746  6997  7009 W MediaHTTPConnection: readAt 28355750 / 21739 => java.net.ProtocolException: unexpected end of stream
34713 11-22 19:45:28.907594   417 13780 E NuCachedSource2: source returned error -1010, 0 retries left

我并不熟悉这个 MediaHTTPConnection, android 4.4 上是没有这个类的, 当时的 http 仍然是走 libstagefright 的 chromium_http, 但 android 6.0 上出现了 MediaHTTPConnection, 把 HTTP 处理的逻辑从底层转到上层了: MAGIC3. DO NOT TOUCH.  BY 冗戈微言  http://blog.csdn.net/leonxu_sjtu/
commit d2506a506303ed94fd1991cf986b825b870a67c5
Author: Andreas Huber <[email protected]>
Date:   Wed Jan 29 10:32:46 2014 -0800
    FINAL ATTEMPT: HTTP services are now provided from JAVA and made available to media code


试了同为 android6.0 的几个平台, 都会卡住, 但同一个 iqiyi 片源同一个硬件平台, 如果走 android4.4 系统就不会有异常, 呵呵: MAGIC4. DO NOT TOUCH.  BY 冗戈微言  http://blog.csdn.net/leonxu_sjtu/



好吧, 既然有表现正常的状况, 那就抓网络包对比一下发包差异吧:

Android 4.4, 当设备端收包小于 1494 时, 设备端立刻发 FIN 断开连接, 并随后重发请求, 同时设置 HTTP Range 进行续传, 服务器端按 HTTP 206 反馈;



但 Android 6.0, 如果设备端收包小于 1494 时, 设备就一直在这里死等数据...



那么多年的流媒体开发告诉我,断开连接断点续传是最常用的错误处理逻辑, 为啥 Android6.0 上会一直死等?MAGIC5. DO NOT TOUCH.  BY 冗戈微言 http://blog.csdn.net/leonxu_sjtu/

由 log 入手看代码:

media/libstagefright/NuCachedSource2.cpp
void NuCachedSource2::fetchInternal() {

    ssize_t n = mSource->readAt(
            mCacheOffset + mCache->totalSize(), page->mData, kPageSize);
    } else if (n < 0) {
        mFinalStatus = n;
        if (n == ERROR_UNSUPPORTED || n == -EPIPE) {
            // These are errors that are not likely to go away even if we
            // retry, i.e. the server doesn't support range requests or similar.
            mNumRetriesLeft = 0;
        }
        ALOGE("source returned error %zd, %d retries left", n, mNumRetriesLeft);
        mCache->releasePage(page);

这里 readAt 的返回值 n 如果是小于零异常,  只要不是 ERROR_UNSUPPORTED 和 EPIPE, 都会有 mNumRetriesLeft 的重连机会的, mNumRetriesLeft 初值为10;
但 log 显示 -1010 也就是 ERROR_UNSUPPORTED, 那就是没机会了, 等用户按退出吧;
为啥 android6.0 上 iqiyi/sohu 的视频网站都会搞出 ERROR_UNSUPPORTED 呢? MAGIC6. DO NOT TOUCH.  BY 冗戈微言  http://blog.csdn.net/leonxu_sjtu/


./media/java/android/media/MediaHTTPConnection.java
 private int readAt(long offset, byte[] data, int size) {
        } catch (ProtocolException e) {
            Log.w(TAG, "readAt " + offset + " / " + size + " => " + e);
            return MEDIA_ERROR_UNSUPPORTED;


MediaHTTPConnection 有 bug 啊 !

如之前的 log 所示, 如果出现了 unexpected end of stream, 它却按 ERROR_UNSUPPORTE 来设置 error 类型, 就导致 NuCacheSource2 那边直接罢工不重试 !MAGIC7. DO NOT TOUCH.  BY 冗戈微言 http://blog.csdn.net/leonxu_sjtu/



这个 ProtocolException 从哪来的, 是 external 下的 okhttp :MAGIC8. DO NOT TOUCH.  BY 冗戈微言 http://blog.csdn.net/leonxu_sjtu/



src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java
    @Override public long read(Buffer sink, long byteCount) throws IOException {
      long read = source.read(sink, Math.min(byteCount, bytesRemainingInChunk));
      if (read == -1) {
        unexpectedEndOfInput(); // The server didn't supply the promised chunk length.
        throw new ProtocolException("unexpected end of stream");
      }
      bytesRemainingInChunk -= read;
      return read;
    }


至此,看起来是 android6.0 使用了 MediaHTTPConnection 来替换底层的 chromium_http, 但是关于 errno 却没有和底层的 NuCachedSource2 做适配而导致了, 并且直到 android7.0 也没有修复, 甚至 android8.0 我扫了一眼代码也没有变动, 谷歌什么情况?  不管了, 继续看我的书吧... MAGIC9. DO NOT TOUCH.  BY 冗戈微言 http://blog.csdn.net/leonxu_sjtu/



猜你喜欢

转载自blog.csdn.net/leonxu_sjtu/article/details/78910803