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信息可以看这个文章
使用 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的知识可以点击
使用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.pngHTTP/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其实是有问题的,实在没有想到会是这种问题,知道问题点了其实修改也就简单了
网友评论