美文网首页
Nuplyaer streaming 获取duration的流程

Nuplyaer streaming 获取duration的流程

作者: Nothing_655f | 来源:发表于2021-06-09 15:51 被阅读0次

    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其实是有问题的,实在没有想到会是这种问题,知道问题点了其实修改也就简单了

    相关文章

      网友评论

          本文标题:Nuplyaer streaming 获取duration的流程

          本文链接:https://www.haomeiwen.com/subject/qkcteltx.html