由于目前网络不好,暂时先记录想到的,因为HLS是最近开始学习研究的,害怕最近项目忙忘记,所以先记录下(以下位于LibStageFright):
上面三个算是底层主要是的核心东西.我先大致讲一下Android手机播放HLS的流程.
连接:https://android.googlesource.com/platform/frameworks/av/+/jb-release/media/libmediaplayerservice/MediaPlayerService.cpp
主要是根据url地址创建不同的播放器对象,具体代码如下:
static sp<MediaPlayerBase> createPlayer(player_type playerType, void* cookie,
notify_callback_f notifyFunc)
{
sp<MediaPlayerBase> p;
switch (playerType) { // 根据类型创建不同的播放器对象.
case SONIVOX_PLAYER:
ALOGV(" create MidiFile");
p = new MidiFile();
break;
case STAGEFRIGHT_PLAYER:
ALOGV(" create StagefrightPlayer");
p = new StagefrightPlayer;
break;
case NU_PLAYER:
ALOGV(" create NuPlayer");
p = new NuPlayerDriver; //专门用来播放HLS协议的播放器对象
break;
case TEST_PLAYER:
ALOGV("Create Test Player stub");
p = new TestPlayerStub();
break;
case AAH_RX_PLAYER:
ALOGV(" create A@H RX Player");
p = createAAH_RXPlayer();
break;
case AAH_TX_PLAYER:
ALOGV(" create A@H TX Player");
p = createAAH_TXPlayer();
break;
default:
ALOGE("Unknown player type: %d", playerType);
return NULL;
}
if (p != NULL) {
if (p->initCheck() == NO_ERROR) {
p->setNotifyCallback(cookie, notifyFunc);
} else {
p.clear();
}
}
if (p == NULL) {
ALOGE("Failed to create player object");
}
return p;
}
player_type getPlayerType(const char* url)
{
if (TestPlayerStub::canBeUsed(url)) {
return TEST_PLAYER;
}
if (!strncasecmp("http://", url, 7)
|| !strncasecmp("https://", url, 8)) { //判断是否为https,或者http的
size_t len = strlen(url);
if (len >= 5 && !strcasecmp(".m3u8", &url[len - 5])) { // 并且是有一个有效的m3u8结尾的url
return NU_PLAYER;
}
if (strstr(url,"m3u8")) {
return NU_PLAYER;
}
}
if (!strncasecmp("rtsp://", url, 7)) {
return NU_PLAYER;
}
if (!strncasecmp("aahRX://", url, 8)) {
return AAH_RX_PLAYER;
}
// use MidiFile for MIDI extensions
int lenURL = strlen(url);
for (int i = 0; i < NELEM(FILE_EXTS); ++i) {
int len = strlen(FILE_EXTS[i].extension);
int start = lenURL - len;
if (start > 0) {
if (!strncasecmp(url + start, FILE_EXTS[i].extension, len)) {
return FILE_EXTS[i].playertype;
}
}
}
return getDefaultPlayerType(); // 如果之前都没有,那么返回默认的.
}
for (size_t i = 0; i < kNumSources; ++i) { // 创建了2个获取器,去获取TS资源.
mPacketSources.add(indexToType(i), new AnotherPacketSource(NULL /* meta */));
mPacketSources2.add(indexToType(i), new AnotherPacketSource(NULL /* meta */));
}
sp<AnotherPacketSource> LiveSession::getMetadataSource(
sp<AnotherPacketSource> sources[kNumSources], uint32_t streamMask, bool newUri) {
// todo: One case where the following strategy can fail is when audio and video
// are in separate playlists, both are transport streams, and the metadata
// is actually contained in the audio stream.
ALOGV("[timed_id3] getMetadataSourceForUri streamMask %x newUri %s",
streamMask, newUri ? "true" : "false");
if ((sources[kVideoIndex] != NULL) // video fetcher; or ...
|| (!(streamMask & STREAMTYPE_VIDEO) && sources[kAudioIndex] != NULL)) {
// ... audio fetcher for audio only variant
return getPacketSourceForStreamIndex(kMetaDataIndex, newUri);
}
return NULL;
}
看名字就知道是来获取PlayList索引文件里面的数据,比如ts解析每个字段什么的.
我们看看它具体做了些什么:
PlaylistFetcher::PlaylistFetcher( // 很多参数
const sp<AMessage> ¬ify,
const sp<LiveSession> &session,
const char *uri,
int32_t id,
int32_t subtitleGeneration)
: mNotify(notify),
mSession(session),
mURI(uri),
mFetcherID(id),
mStreamTypeMask(0),
mStartTimeUs(-1ll),
mSegmentStartTimeUs(-1ll),
mDiscontinuitySeq(-1ll),
mStartTimeUsRelative(false),
mLastPlaylistFetchTimeUs(-1ll),
mPlaylistTimeUs(-1ll),
mSeqNumber(-1),
mNumRetries(0),
mStartup(true),
mIDRFound(false),
mSeekMode(LiveSession::kSeekModeExactPosition),
mTimeChangeSignaled(false),
mNextPTSTimeUs(-1ll),
mMonitorQueueGeneration(0),
mSubtitleGeneration(subtitleGeneration),
mLastDiscontinuitySeq(-1ll),
mRefreshState(INITIAL_MINIMUM_RELOAD_DELAY),
mFirstPTSValid(false),
mFirstTimeUs(-1ll),
mVideoBuffer(new AnotherPacketSource(NULL)),
mThresholdRatio(-1.0f),
mDownloadState(new DownloadState()),
mHasMetadata(false) {
memset(mPlaylistHash, 0, sizeof(mPlaylistHash));
mHTTPDownloader = mSession->getHTTPDownloader(); // HttpLiveSession对象去获取DownLoader.
}
// 看到LiveSession中去直接创建了一个HTTPDownloader对象.
sp<HTTPDownloader> LiveSession::getHTTPDownloader() {
return new HTTPDownloader(mHTTPService, mExtraHeaders);
}
/*我们看看HTTPDownloader,这个对象主要有三个方法:fetchBlock(),fetchFile(),fetchPlaylist()*/
HTTPDownloader::HTTPDownloader( //构造函数
const sp<IMediaHTTPService> &httpService,
const KeyedVector<String8, String8> &headers) :
mHTTPDataSource(new MediaHTTP(httpService->makeHTTPConnection())), // MediaHttp的连接对象.
mExtraHeaders(headers),
mDisconnecting(false) {
}
//fetchFile方法主要是获取Ts文件
ssize_t HTTPDownloader::fetchFile(
const char *url, sp<ABuffer> *out, String8 *actualUrl) {
ssize_t err = fetchBlock(url, out, 0, -1, 0, actualUrl, true /* reconnect */); // 可以看到主要是调用了fetchBlock方法.
// close off the connection after use
mHTTPDataSource->disconnect();
return err;
}
//那我们接下来去看看fetchBlock,这个方法有点复杂,大致就是获取流媒体内存区域的块
/* * Illustration of parameters: * * 0 `range_offset` * +------------+-------------------------------------------------------+--+--+ * | | | next block to fetch | | | * | | `source` handle => `out` buffer | | | | * | `url` file |<--------- buffer size --------->|<--- `block_size` -->| | | * | |<----------- `range_length` / buffer capacity ----------->| | * |<------------------------------ file_size ------------------------------->| * * Special parameter values: * - range_length == -1 means entire file * - block_size == 0 means entire range * */
ssize_t HTTPDownloader::fetchBlock(
const char *url, sp<ABuffer> *out,
int64_t range_offset, int64_t range_length,
uint32_t block_size, /* download block size */
String8 *actualUrl,
bool reconnect /* force connect HTTP when resuing source */) {
if (isDisconnecting()) {
return ERROR_NOT_CONNECTED;
}
off64_t size;
if (reconnect) {
if (!strncasecmp(url, "file://", 7)) {
mDataSource = new FileSource(url + 7);
} else if (strncasecmp(url, "http://", 7)
&& strncasecmp(url, "https://", 8)) {
return ERROR_UNSUPPORTED;
} else {
KeyedVector<String8, String8> headers = mExtraHeaders;
if (range_offset > 0 || range_length >= 0) {
headers.add(
String8("Range"),
String8(
AStringPrintf(
"bytes=%lld-%s",
range_offset,
range_length < 0
? "" : AStringPrintf("%lld",
range_offset + range_length - 1).c_str()).c_str()));
}
status_t err = mHTTPDataSource->connect(url, &headers);
if (isDisconnecting()) {
return ERROR_NOT_CONNECTED;
}
if (err != OK) {
return err;
}
mDataSource = mHTTPDataSource;
}
}
status_t getSizeErr = mDataSource->getSize(&size);
if (isDisconnecting()) {
return ERROR_NOT_CONNECTED;
}
if (getSizeErr != OK) {
size = 65536;
}
sp<ABuffer> buffer = *out != NULL ? *out : new ABuffer(size);
if (*out == NULL) {
buffer->setRange(0, 0);
}
ssize_t bytesRead = 0;
// adjust range_length if only reading partial block
if (block_size > 0 && (range_length == -1 || (int64_t)(buffer->size() + block_size) < range_length)) {
range_length = buffer->size() + block_size;
}
for (;;) {
// Only resize when we don't know the size.
size_t bufferRemaining = buffer->capacity() - buffer->size();
if (bufferRemaining == 0 && getSizeErr != OK) {
size_t bufferIncrement = buffer->size() / 2;
if (bufferIncrement < 32768) {
bufferIncrement = 32768;
}
bufferRemaining = bufferIncrement;
ALOGV("increasing download buffer to %zu bytes",
buffer->size() + bufferRemaining);
sp<ABuffer> copy = new ABuffer(buffer->size() + bufferRemaining);
memcpy(copy->data(), buffer->data(), buffer->size());
copy->setRange(0, buffer->size());
buffer = copy;
}
size_t maxBytesToRead = bufferRemaining;
if (range_length >= 0) {
int64_t bytesLeftInRange = range_length - buffer->size();
if (bytesLeftInRange < (int64_t)maxBytesToRead) {
maxBytesToRead = bytesLeftInRange;
if (bytesLeftInRange == 0) {
break;
}
}
}
// The DataSource is responsible for informing us of error (n < 0) or eof (n == 0)
// to help us break out of the loop.
ssize_t n = mDataSource->readAt(
buffer->size(), buffer->data() + buffer->size(),
maxBytesToRead);
if (isDisconnecting()) {
return ERROR_NOT_CONNECTED;
}
if (n < 0) {
return n;
}
if (n == 0) {
break;
}
buffer->setRange(0, buffer->size() + (size_t)n);
bytesRead += n;
}
*out = buffer;
if (actualUrl != NULL) {
*actualUrl = mDataSource->getUri();
if (actualUrl->isEmpty()) {
*actualUrl = url;
}
}
return bytesRead;
}
//接下来就是:fetchPlaylist方法.
sp<M3UParser> HTTPDownloader::fetchPlaylist(
const char *url, uint8_t *curPlaylistHash, bool *unchanged) {
ALOGV("fetchPlaylist '%s'", url);
*unchanged = false;
sp<ABuffer> buffer;
String8 actualUrl;
ssize_t err = fetchFile(url, &buffer, &actualUrl);//首先去获取文件
// close off the connection after use
mHTTPDataSource->disconnect();
if (err <= 0) {
return NULL;
}
// MD5 functionality is not available on the simulator, treat all
// playlists as changed.
#if defined(__ANDROID__)
uint8_t hash[16];
MD5_CTX m;
MD5_Init(&m);
MD5_Update(&m, buffer->data(), buffer->size());
MD5_Final(hash, &m);
if (curPlaylistHash != NULL && !memcmp(hash, curPlaylistHash, 16)) {
// playlist unchanged
*unchanged = true;
return NULL;
}
if (curPlaylistHash != NULL) {
memcpy(curPlaylistHash, hash, sizeof(hash));
}
#endif
sp<M3UParser> playlist =
new M3UParser(actualUrl.string(), buffer->data(), buffer->size());// 可以看到去解析了M3u8文件.
if (playlist->initCheck() != OK) { // 检查是否解析成功的.
ALOGE("failed to parse .m3u8 playlist");
return NULL;
}
return playlist; // 得到一个M3UParser对象.
}
//接下来看看M3UParser吧,这个一看名字就知道是在解析M3u8文件,然后解析m3u8根据Hls的版本解析.
status_t M3UParser::parse(const void *_data, size_t size) {
int32_t lineNo = 0;
sp<AMessage> itemMeta;
const char *data = (const char *)_data;
size_t offset = 0;
uint64_t segmentRangeOffset = 0;
while (offset < size) {
size_t offsetLF = offset;
while (offsetLF < size && data[offsetLF] != '\n') {
++offsetLF;
}
AString line;
if (offsetLF > offset && data[offsetLF - 1] == '\r') {
line.setTo(&data[offset], offsetLF - offset - 1);
} else {
line.setTo(&data[offset], offsetLF - offset);
}
// ALOGI("#%s#", line.c_str());
if (line.empty()) {
offset = offsetLF + 1;
continue;
}
if (lineNo == 0 && line == "#EXTM3U") { //是否为M3u8协议头
mIsExtM3U = true;
}
if (mIsExtM3U) {
status_t err = OK;
if (line.startsWith("#EXT-X-TARGETDURATION")) {
if (mIsVariantPlaylist) {
return ERROR_MALFORMED;
}
err = parseMetaData(line, &mMeta, "target-duration");
} else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE")) { //协议序列
if (mIsVariantPlaylist) {
return ERROR_MALFORMED;
}
err = parseMetaData(line, &mMeta, "media-sequence");
} else if (line.startsWith("#EXT-X-KEY")) { // url地址key
if (mIsVariantPlaylist) {
return ERROR_MALFORMED;
}
err = parseCipherInfo(line, &itemMeta, mBaseURI);
} else if (line.startsWith("#EXT-X-ENDLIST")) { // 结束tag
mIsComplete = true;
} else if (line.startsWith("#EXT-X-PLAYLIST-TYPE:EVENT")) {
mIsEvent = true;
} else if (line.startsWith("#EXTINF")) { // 每一个item的开头
if (mIsVariantPlaylist) {
return ERROR_MALFORMED;
}
err = parseMetaDataDuration(line, &itemMeta, "durationUs");
} else if (line.startsWith("#EXT-X-DISCONTINUITY")) {
if (mIsVariantPlaylist) {
return ERROR_MALFORMED;
}
if (itemMeta == NULL) {
itemMeta = new AMessage;
}
itemMeta->setInt32("discontinuity", true);
++mDiscontinuityCount;
} else if (line.startsWith("#EXT-X-STREAM-INF")) {
if (mMeta != NULL) {
return ERROR_MALFORMED;
}
mIsVariantPlaylist = true;
err = parseStreamInf(line, &itemMeta);
} else if (line.startsWith("#EXT-X-BYTERANGE")) {
if (mIsVariantPlaylist) {
return ERROR_MALFORMED;
}
uint64_t length, offset;
err = parseByteRange(line, segmentRangeOffset, &length, &offset);
if (err == OK) {
if (itemMeta == NULL) {
itemMeta = new AMessage;
}
itemMeta->setInt64("range-offset", offset);
itemMeta->setInt64("range-length", length);
segmentRangeOffset = offset + length;
}
} else if (line.startsWith("#EXT-X-MEDIA")) {
err = parseMedia(line);
} else if (line.startsWith("#EXT-X-DISCONTINUITY-SEQUENCE")) {
if (mIsVariantPlaylist) {
return ERROR_MALFORMED;
}
size_t seq;
err = parseDiscontinuitySequence(line, &seq);
if (err == OK) {
mDiscontinuitySeq = seq;
}
}
if (err != OK) {
return err;
}
}
if (!line.startsWith("#")) {
if (!mIsVariantPlaylist) {
int64_t durationUs;
if (itemMeta == NULL
|| !itemMeta->findInt64("durationUs", &durationUs)) {
return ERROR_MALFORMED;
}
itemMeta->setInt32("discontinuity-sequence",
mDiscontinuitySeq + mDiscontinuityCount);
}
mItems.push();
Item *item = &mItems.editItemAt(mItems.size() - 1);
CHECK(MakeURL(mBaseURI.c_str(), line.c_str(), &item->mURI));
item->mMeta = itemMeta;
itemMeta.clear();
}
offset = offsetLF + 1;
++lineNo;
}
// error checking of all fields that's required to appear once
// (currently only checking "target-duration"), and
// initialization of playlist properties (eg. mTargetDurationUs)
if (!mIsVariantPlaylist) {
int32_t targetDurationSecs;
if (mMeta == NULL || !mMeta->findInt32(
"target-duration", &targetDurationSecs)) {
ALOGE("Media playlist missing #EXT-X-TARGETDURATION");
return ERROR_MALFORMED;
}
mTargetDurationUs = targetDurationSecs * 1000000ll;
mFirstSeqNumber = 0;
if (mMeta != NULL) {
mMeta->findInt32("media-sequence", &mFirstSeqNumber);
}
mLastSeqNumber = mFirstSeqNumber + mItems.size() - 1;
}
return OK;
}
/*好了以上大致流程看完了,留下一个问题,我们在播放ts的时候什么时候去下载下一个ts呢.?*/
void PlaylistFetcher::DownloadState::saveState( // 存储一些状态
AString &uri,
sp<AMessage> &itemMeta,
sp<ABuffer> &buffer,
sp<ABuffer> &tsBuffer,
int32_t &firstSeqNumberInPlaylist,
int32_t &lastSeqNumberInPlaylist) {
mHasSavedState = true;
mUri = uri; // 当前URi地址
mItemMeta = itemMeta; // 当前播放的数据
mBuffer = buffer; // 当前的Buffer
mTsBuffer = tsBuffer; // 当前ts的Buffer
mFirstSeqNumberInPlaylist = firstSeqNumberInPlaylist; // 主要是看到有2个序列
mLastSeqNumberInPlaylist = lastSeqNumberInPlaylist;
}
void PlaylistFetcher::onMonitorQueue() {
// in the middle of an unfinished download, delay
// playlist refresh as it'll change seq numbers
if (!mDownloadState->hasSavedState()) {
refreshPlaylist();
}
int64_t targetDurationUs = kMinBufferedDurationUs;
if (mPlaylist != NULL) {
targetDurationUs = mPlaylist->getTargetDuration();
}
int64_t bufferedDurationUs = 0ll;
status_t finalResult = OK;
if (mStreamTypeMask == LiveSession::STREAMTYPE_SUBTITLES) {
sp<AnotherPacketSource> packetSource =
mPacketSources.valueFor(LiveSession::STREAMTYPE_SUBTITLES);
bufferedDurationUs =
packetSource->getBufferedDurationUs(&finalResult);
} else {
// Use min stream duration, but ignore streams that never have any packet
// enqueued to prevent us from waiting on a non-existent stream;
// when we cannot make out from the manifest what streams are included in
// a playlist we might assume extra streams.
bufferedDurationUs = -1ll;
for (size_t i = 0; i < mPacketSources.size(); ++i) {
if ((mStreamTypeMask & mPacketSources.keyAt(i)) == 0
|| mPacketSources[i]->getLatestEnqueuedMeta() == NULL) {
continue;
}
int64_t bufferedStreamDurationUs =
mPacketSources.valueAt(i)->getBufferedDurationUs(&finalResult);
FSLOGV(mPacketSources.keyAt(i), "buffered %lld", (long long)bufferedStreamDurationUs);
if (bufferedDurationUs == -1ll
|| bufferedStreamDurationUs < bufferedDurationUs) {
bufferedDurationUs = bufferedStreamDurationUs;
}
}
if (bufferedDurationUs == -1ll) {
bufferedDurationUs = 0ll;
}
}
if (finalResult == OK && bufferedDurationUs < kMinBufferedDurationUs) {
FLOGV("monitoring, buffered=%lld < %lld",
(long long)bufferedDurationUs, (long long)kMinBufferedDurationUs);
// delay the next download slightly; hopefully this gives other concurrent fetchers
// a better chance to run.
// onDownloadNext(); //这儿文档也说的很明白了.其实就是handler机制,进行通信,所以主要what就是kWhatDownloadNext,我们去搜一下kWhatDownloadNext吧.
sp<AMessage> msg = new AMessage(kWhatDownloadNext, this);
msg->setInt32("generation", mMonitorQueueGeneration);
msg->post(1000l);
} else {
// We'd like to maintain buffering above durationToBufferUs, so try
// again when buffer just about to go below durationToBufferUs
// (or after targetDurationUs / 2, whichever is smaller).
int64_t delayUs = bufferedDurationUs - kMinBufferedDurationUs + 1000000ll;
if (delayUs > targetDurationUs / 2) {
delayUs = targetDurationUs / 2;
}
FLOGV("pausing for %lld, buffered=%lld > %lld",
(long long)delayUs,
(long long)bufferedDurationUs,
(long long)kMinBufferedDurationUs);
postMonitorQueue(delayUs);
}
}
//搜到了.kWhatDownloadNext
void PlaylistFetcher::onMessageReceived(const sp<AMessage> &msg) {
switch (msg->what()) {
case kWhatStart:
{
status_t err = onStart(msg);
sp<AMessage> notify = mNotify->dup();
notify->setInt32("what", kWhatStarted);
notify->setInt32("err", err);
notify->post();
break;
}
case kWhatPause:
{
onPause();
sp<AMessage> notify = mNotify->dup();
notify->setInt32("what", kWhatPaused);
notify->setInt32("seekMode",
mDownloadState->hasSavedState()
? LiveSession::kSeekModeNextSample
: LiveSession::kSeekModeNextSegment);
notify->post();
break;
}
case kWhatStop:
{
onStop(msg);
sp<AMessage> notify = mNotify->dup();
notify->setInt32("what", kWhatStopped);
notify->post();
break;
}
case kWhatFetchPlaylist:
{
bool unchanged;
sp<M3UParser> playlist = mHTTPDownloader->fetchPlaylist(
mURI.c_str(), NULL /* curPlaylistHash */, &unchanged);
sp<AMessage> notify = mNotify->dup();
notify->setInt32("what", kWhatPlaylistFetched);
notify->setObject("playlist", playlist);
notify->post();
break;
}
case kWhatMonitorQueue:
case kWhatDownloadNext: // 看到了吧,下载下一个,其中有一个错误的延时1s重新下载的机制.,最有才会放弃
{
int32_t generation;
CHECK(msg->findInt32("generation", &generation));
if (generation != mMonitorQueueGeneration) {
// Stale event
break;
}
if (msg->what() == kWhatMonitorQueue) { // retry机制
onMonitorQueue();
} else { // 否则下载下一个.
onDownloadNext();
}
break;
}
case kWhatResumeUntil:
{
onResumeUntil(msg);
break;
}
default:
TRESPASS();
}
}