Nuplyaer streaming 获取duration的流程

Nuplyaer streaming 获取duration的流程

从上次往下查是最后到这里获取的

status_t NuPlayer::GenericSource::initFromDataSource() {
    // ....
    mFileMeta = fileMeta;
    if (mFileMeta != NULL) {
        int64_t duration;
        if (mFileMeta->findInt64(kKeyDuration, &duration)) {
            mDurationUs = duration;
            ALOGE("Horace find filemeta duration:%lld", mDurationUs);
        } else
            ALOGE("Horace not find filemeta duration");
    } else
        ALOGE("Horace mFileMeta is null");

    // ....
        int64_t durationUs;
        if (meta->findInt64(kKeyDuration, &durationUs)) {
            if (durationUs > mDurationUs) {
                mDurationUs = durationUs;
            } else
                ALOGE("Horace find durationUs:%lld", durationUs);
        } else
            ALOGE("Horace initFromDataSource not kKeyDuration");

        ALOGD("Horace initFromDataSource mDurationUs:%lld", mDurationUs);
        int32_t bitrate;
        if (totalBitrate >= 0 && meta->findInt32(kKeyBitRate, &bitrate)) {
            totalBitrate += bitrate;
        } else {
            totalBitrate = -1;
        }
}

通过 kKeyDuration 获取对应的值,每个container不一样

frameworks/av/media/extractors/amr/AMRExtractor.cpp:188:        mMeta.setInt64(kKeyDuration, duration);
frameworks/av/media/extractors/midi/MidiExtractor.cpp:178:        trackMetadata->setInt64(kKeyDuration, 1000ll * temp); // milli->micro
frameworks/av/media/extractors/mpeg2/MPEG2TSExtractor.cpp:317:            meta->setInt64(kKeyDuration, durationUs);
frameworks/av/media/extractors/mpeg2/MPEG2TSExtractor.cpp:448:                    if (!meta->findInt64(kKeyDuration, &origDurationUs)
frameworks/av/media/extractors/mpeg2/MPEG2TSExtractor.cpp:452:                        meta->setInt64(kKeyDuration, durationUs);
frameworks/av/media/extractors/mpeg2/MPEG2TSExtractor.cpp:469:                if (!meta->findInt64(kKeyDuration, &durationUs)) {
frameworks/av/media/extractors/flac/FLACExtractor.cpp:621:            mTrackMetadata->setInt64(kKeyDuration,
frameworks/av/media/extractors/mp3/MP3Extractor.cpp:466:        mMeta.setInt64(kKeyDuration, durationUs);

以MPEG2TSExtractor 为例,其是在如下设置的

void MPEG2TSExtractor::init() {
    // ...
    off64_t size;
    err = mDataSource->getSize(&size);
    ALOGD(" %s getSize %lld ret %d", __FUNCTION__, size, err);

        // Estimate duration --- stabilize until you get <500ms deviation.
        while (feedMore() == OK
                && ALooper::GetNowUs() - startTime <= 2000000ll) {
            if (mSeekSyncPoints->size() > prevSyncSize) {
                prevSyncSize = mSeekSyncPoints->size();
                int64_t diffUs = mSeekSyncPoints->keyAt(prevSyncSize - 1)
                        - mSeekSyncPoints->keyAt(0);
                off64_t diffOffset = mSeekSyncPoints->valueAt(prevSyncSize - 1)
                        - mSeekSyncPoints->valueAt(0);
                // 通过粗略的文件比特率计算
                int64_t currentDurationUs = size * diffUs / diffOffset;
                durations.push_back(currentDurationUs);
                if (durations.size() > 5) {
                    durations.erase(durations.begin());
                    int64_t min = *durations.begin();
                    int64_t max = *durations.begin();
                    for (auto duration : durations) {
                        if (min > duration) {
                            min = duration;
                        }
                        if (max < duration) {
                            max = duration;
                        }
                    }
                    if (max - min < 500 * 1000) {
                        durationUs = currentDurationUs; 
                        break;
                    }
                }
            }
        }
        status_t err;
        int64_t bufferedDurationUs;
        bufferedDurationUs = impl->getBufferedDurationUs(&err);
        if (err == ERROR_END_OF_STREAM) {
            durationUs = bufferedDurationUs;
            ALOGD("Horace %s ERROR_END_OF_STREAM %lld", __FUNCTION__, durationUs);
        }
        if (durationUs > 0) {
            const sp<MetaData> meta = impl->getFormat();
            meta->setInt64(kKeyDuration, durationUs);
            impl->setFormat(meta);
            ALOGD("Horace %s %lld", __FUNCTION__, durationUs);
        } else {
            estimateDurationsFromTimesUsAtEnd();    // 这个函数里面也有对时长进行估算
        }
    // ....
}

我这边的错误返回是在 mDataSource->getSize(&size);

而 mDataSource 这里使用的是 NuCachedSource2

status_t NuCachedSource2::getSize(off64_t *size) {
    status_t err = mSource->getSize(size);
    return err;
}

这个 mSource 又是 CallbackDataSource

status_t CallbackDataSource::getSize(off64_t *size) {
    status_t err = mIDataSource->getSize(size);
    if (err != OK) {
        return err;
    }
    if (*size < 0) {
        // IDataSource will set size to -1 to indicate unknown size, but
        // DataSource returns ERROR_UNSUPPORTED for that.
        return ERROR_UNSUPPORTED;
    }
    return OK;
}

直接看 MediaHTTP

status_t MediaHTTP::getSize(off64_t *size) {
    if (mInitCheck != OK) {
        return mInitCheck;
    }

    // Caching the returned size so that it stays valid even after a
    // disconnect. NuCachedSource2 relies on this.

    if (!mCachedSizeValid) {
        mCachedSize = mHTTPConnection->getSize();
        if (mCachedSize > 0)
            mCachedSizeValid = true;
        else
            mCachedSizeValid = false;
        ALOGD("Horace %s line:%d size:%lld", __FUNCTION__, __LINE__, mCachedSize);
    }

    *size = mCachedSize;

    //CallStack stack;
    //CallStack stack("Debug", 5);
    return *size < 0 ? *size : static_cast<status_t>(OK);
}

顺着 mHTTPConnection 可以看到 frameworks\base\media\java\android\media\MediaHTTPConnection.java

这里来

    public synchronized long getSize() {
        if (mConnection == null) {
            try {
                seekTo(0);
            } catch (IOException e) {
                return -1;
            }
        }
        return mTotalSize;
    }

MediaHTTPConnection中的connect 方法只是接受了外部调用的 url 及header,真正建立链接是在 getSize或者seekTo

seekTo 这个函数里面还是有一些逻辑的,不过多介绍了,我处理的这个问题主要是看如下这个地方是否能够获取到对应的Header 信息

image-20210527180343138.png

看到这里后有点怀疑是服务端没有送 ContentLength 或者ContentRange信息,我这边走的case是

mTotalSize = mConnection.getContentLength();

接下来使用抓包确认下服务端有没有送ContentLength,关于ContentLength等 Header信息可以看这个文章

https://byvoid.com/zhs/blog/http-keep-alive-header/

使用 tcpdump 抓包

tcpdump -i wlan0 -s 0 -w /sdcard/test1.pcap 

具体关于tcpdump更多的用法可以参考

常见的有这些:

-i, 要监听的网卡名称,-i rvi0监听虚拟网卡。不设置的时候默认监听所有网卡流量。

-A, 用ASCII码展示所截取的流量,一般用于网页或者app里http请求。-AA可以获取更多的信息。

-X,用ASCII码和hex来展示包的内容,和上面的-A比较像。-XX可以展示更多的信息(比如link layer的header)。

-n,不解析hostname,tcpdump会优先暂时主机的名字。-nn则不展示主机名和端口名(比如443端口会被展示成https)。

-s,截取的包字节长度,默认情况下tcpdump会展示96字节的长度,要获取完整的长度可以用-s0或者-s1600。

-c,只截取指定数目的包,然后退出。

-v,展示更多的有用信息,还可以用-vv -vvv增加信息的展示量。

src,指明ip包的发送方地址。

dst,指明ip包的接收方地址。

port,指明tcp包发送方或者接收方的端口号。

and,or,not,操作法

上面比较常用的,更多的参数可以参考这个详细文档

跟多关于 tcp的知识可以点击

https://zhuanlan.zhihu.com/p/23377655

https://blog.csdn.net/chinaltx/article/details/87469933

使用Wireshark分析pcap文件

wireshark的一些过滤条件

tcp.port == 80 //过滤来自80端口的TCP数据 
udp.port == 12345 //过滤来自12345端口的UDP数据 
ip.src == 192.168.0.1 //过滤源IP为192.168.0.1的数据 
ip.dst == 192.168.0.1 //过目的IP为192.168.0.1的数据 
tcp.port == 80 and ip.src == 192.168.0.1 //过滤来自80端口,源IP为192.168.0.1的TCP数
udp.port == 12345 or ip.dst == 192.168.0.1 //过滤来自12345端口的UDP数据,或者目的IP为192.168.0.1的数据

更多使用Wireshark 可以看到这篇文章

https://www.cnblogs.com/mq0036/p/11187138.html

由于我这边的链接一直在重定向,所以tcpdump 是dump了所有流过WLAN0 的数据,所以可以使用wireshark的搜索功能URL链接中的关键字

image-20210527172331290.png

看到其IP地址是 39.134.176.244,选择右键

image-20210527174305887.png

选择 追踪流中 TCP流 ,可以看到其完整的信息, 如下的信息可以看到是重定向到 39.136.4.34 这个地址

GET XXXXXXXXXXX
User-Agent: Dalvik/2.1.0 (Linux; U; Android 9; XXXXXX Build/PPR2.180905.006.A1)
Host: XXXXXXXX
Connection: Keep-Alive
Accept-Encoding: gzip

HTTP/1.1 302 Moved Temporarily
Server: CMHI LTC
Date: Wed, 26 May 2021 06:44:14 GMT
Content-Length: 0
Connection: close
Location: http://39.136.4.34:80/xxxxx

过滤下 39.136.4.34 后继续看其TCP流信息

image-20210527174717735.png
HTTP/1.1 302 Moved Temporarily
Location: http://39.136.4.18:80

其又重定向到了 39.136.4.18,继续往下看

User-Agent: Dalvik/2.1.0 (Linux; U; Android 9; XXX Build/PPR2.180905.006.A1)
Host: 39.136.4.18
Connection: Keep-Alive
Accept-Encoding: gzip

HTTP/1.1 200 OK
Date: Wed, 26 May 2021 06:44:14 GMT
Last-Modified: Fri, 05 Feb 2021 05:19:22 GMT
Server: XXXX
Accept-Ranges: bytes
Content-Length: 4545804092
Content-Range: bytes 0-4545804091/4545804092
Content-Type: video/mpeg
Connection: Keep-Alive

到了这里可以看到 服务端返回的信息,可以看到 其Header中 明显是有包含这个问题中需要的 Content-Length: 4545804092

这里排除了服务端的问题了,那么直接继续看为什么 ,

打印 Http Header信息

为了确认下系统的 HttpURLConnection能不能返回正常的信息,加了个打印看下

            Map em = mConnection.getHeaderFields();
            Log.d(TAG, "header Values:" + em.toString());
            String headerName = null;   

            for (int i = 1; (headerName = mConnection.getHeaderFieldKey(i)) != null; i++) 
            {
                 Log.d(TAG, " Header Nme : " + headerName);
                 Log.d(TAG, mConnection.getHeaderField(i));

            }

从打印来看这个是 HttpURLConnection 是能够正常获取Header信息的

MediaHTTPConnection: header Values:{null=[HTTP/1.1 200 OK], Accept-Ranges=[bytes], Connection=[Keep-Alive], Content-Length=[7909435984], Content-Range=[bytes 0-7909435983/7909435984], Content-Type=[video/mpeg], Date=[Thu, 27 May 2021 07:54:11 GMT], Last-Modified=[Thu, 06 May 2021 08:26:54 GMT], Server=[HMS Download Service], X-Android-Received-Millis=[1622102051852], X-Android-Response-Source=[NETWORK 200], X-Android-Selected-Protocol=[http/1.1], X-Android-Sent-Millis=[1622102051778]}
MediaHTTPConnection: Header Nme : Last-Modified
MediaHTTPConnection: Thu, 06 May 2021 08:26:54 GMT
MediaHTTPConnection: Header Nme : Server
MediaHTTPConnection: XXXXXXXX
MediaHTTPConnection: Header Nme : Accept-Ranges
MediaHTTPConnection: bytes
MediaHTTPConnection: Header Nme : Content-Length
MediaHTTPConnection: 7909435984
MediaHTTPConnection: Header Nme : Content-Range
MediaHTTPConnection: bytes 0-7909435983/7909435984
MediaHTTPConnection: Header Nme : Content-Type
MediaHTTPConnection: video/mpeg
MediaHTTPConnection: Header Nme : Connection
MediaHTTPConnection: Keep-Alive
MediaHTTPConnection: Header Nme : X-Android-Sent-Millis
MediaHTTPConnection: 1622102051778
MediaHTTPConnection: Header Nme : X-Android-Received-Millis
MediaHTTPConnection: 1622102051852
MediaHTTPConnection: Header Nme : X-Android-Selected-Protocol
MediaHTTPConnection: http/1.1
MediaHTTPConnection: Header Nme : X-Android-Response-Source
MediaHTTPConnection: NETWORK 200

那么就是说api mConnection.getContentLength(); 这里内部调用哪里出问题了

没办法继续看API的解释和内部实现,其实还是能发现问题的

    /**
     * Returns the value of the {@code content-length} header field.
     * <P>
     * <B>Note</B>: {@link #getContentLengthLong() getContentLengthLong()}
     * should be preferred over this method, since it returns a {@code long}
     * instead and is therefore more portable.</P>
     *
     * @return  the content length of the resource that this connection's URL
     *          references, {@code -1} if the content length is not known,
     *          or if the content length is greater than Integer.MAX_VALUE.
     */
    public int getContentLength() {
        long l = getContentLengthLong();
        if (l > Integer.MAX_VALUE)
            return -1;
        return (int) l;
    }

这个问题的原因其实就是我们获取到的服务端文件比 Integer.MAX_VALUE = 2147483647; // 0x7fffffff 还要大,而Android调用了这个getContentLength API其实是有问题的,实在没有想到会是这种问题,知道问题点了其实修改也就简单了

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容