bool MoofParser::HasMetadata() { int64_t length = std::numeric_limits<int64_t>::max(); mSource->Length(&length); nsTArray<MediaByteRange> byteRanges; byteRanges.AppendElement(MediaByteRange(0, length)); nsRefPtr<mp4_demuxer::BlockingStream> stream = new BlockingStream(mSource); MediaByteRange ftyp; MediaByteRange moov; BoxContext context(stream, byteRanges); for (Box box(&context, mOffset); box.IsAvailable(); box = box.Next()) { if (box.IsType("ftyp")) { ftyp = box.Range(); continue; } if (box.IsType("moov")) { moov = box.Range(); break; } } if (!ftyp.Length() || !moov.Length()) { return false; } mInitRange = ftyp.Extents(moov); return true; }
already_AddRefed<MediaRawData> WAVTrackDemuxer::GetFileHeader(const MediaByteRange& aRange) { if (!aRange.Length()) { return nullptr; } RefPtr<MediaRawData> fileHeader = new MediaRawData(); fileHeader->mOffset = aRange.mStart; nsAutoPtr<MediaRawDataWriter> headerWriter(fileHeader->CreateWriter()); if (!headerWriter->SetSize(aRange.Length())) { return nullptr; } const uint32_t read = Read(headerWriter->Data(), fileHeader->mOffset, fileHeader->Size()); if (read != aRange.Length()) { return nullptr; } UpdateState(aRange); return fileHeader.forget(); }
nsRefPtr<MP4Demuxer::InitPromise> MP4Demuxer::Init() { AutoPinned<mp4_demuxer::ResourceStream> stream(mStream); // Check that we have enough data to read the metadata. MediaByteRange br = mp4_demuxer::MP4Metadata::MetadataRange(stream); if (br.IsNull()) { return InitPromise::CreateAndReject(DemuxerFailureReason::WAITING_FOR_DATA, __func__); } if (!mInitData->SetLength(br.Length(), fallible)) { // OOM return InitPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); } size_t size; mStream->ReadAt(br.mStart, mInitData->Elements(), br.Length(), &size); if (size != size_t(br.Length())) { return InitPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); } nsRefPtr<mp4_demuxer::BufferStream> bufferstream = new mp4_demuxer::BufferStream(mInitData); mMetadata = MakeUnique<mp4_demuxer::MP4Metadata>(bufferstream); if (!mMetadata->GetNumberTracks(mozilla::TrackInfo::kAudioTrack) && !mMetadata->GetNumberTracks(mozilla::TrackInfo::kVideoTrack)) { return InitPromise::CreateAndReject(DemuxerFailureReason::DEMUXER_ERROR, __func__); } return InitPromise::CreateAndResolve(NS_OK, __func__); }
void MP3TrackDemuxer::UpdateState(const MediaByteRange& aRange) { // Prevent overflow. if (mTotalFrameLen + aRange.Length() < mTotalFrameLen) { // These variables have a linear dependency and are only used to derive the // average frame length. mTotalFrameLen /= 2; mNumParsedFrames /= 2; } // Full frame parsed, move offset to its end. mOffset = aRange.mEnd; mTotalFrameLen += aRange.Length(); if (!mSamplesPerFrame) { mSamplesPerFrame = mParser.CurrentFrame().Header().SamplesPerFrame(); mSamplesPerSecond = mParser.CurrentFrame().Header().SampleRate(); mChannels = mParser.CurrentFrame().Header().Channels(); } ++mNumParsedFrames; ++mFrameIndex; MOZ_ASSERT(mFrameIndex > 0); // Prepare the parser for the next frame parsing session. mParser.EndFrameSession(); }
bool MoofParser::HasMetadata() { MediaByteRange ftyp; MediaByteRange moov; ScanForMetadata(ftyp, moov); return !!ftyp.Length() && !!moov.Length(); }
already_AddRefed<mozilla::MediaByteBuffer> MoofParser::Metadata() { MediaByteRange ftyp; MediaByteRange moov; ScanForMetadata(ftyp, moov); if (!ftyp.Length() || !moov.Length()) { return nullptr; } RefPtr<MediaByteBuffer> metadata = new MediaByteBuffer(); if (!metadata->SetLength(ftyp.Length() + moov.Length(), fallible)) { // OOM return nullptr; } RefPtr<mp4_demuxer::BlockingStream> stream = new BlockingStream(mSource); size_t read; bool rv = stream->ReadAt(ftyp.mStart, metadata->Elements(), ftyp.Length(), &read); if (!rv || read != ftyp.Length()) { return nullptr; } rv = stream->ReadAt(moov.mStart, metadata->Elements() + ftyp.Length(), moov.Length(), &read); if (!rv || read != moov.Length()) { return nullptr; } return metadata.forget(); }
already_AddRefed<MediaRawData> MP3TrackDemuxer::GetNextFrame(const MediaByteRange& aRange) { MP3LOG("GetNext() Begin({mStart=%" PRId64 " Length()=%" PRId64 "})", aRange.mStart, aRange.Length()); if (!aRange.Length()) { return nullptr; } RefPtr<MediaRawData> frame = new MediaRawData(); frame->mOffset = aRange.mStart; nsAutoPtr<MediaRawDataWriter> frameWriter(frame->CreateWriter()); if (!frameWriter->SetSize(aRange.Length())) { MP3LOG("GetNext() Exit failed to allocated media buffer"); return nullptr; } const uint32_t read = Read(frameWriter->Data(), frame->mOffset, frame->Size()); if (read != aRange.Length()) { MP3LOG("GetNext() Exit read=%u frame->Size()=%u", read, frame->Size()); return nullptr; } UpdateState(aRange); frame->mTime = Duration(mFrameIndex - 1).ToMicroseconds(); frame->mDuration = Duration(1).ToMicroseconds(); frame->mTimecode = frame->mTime; frame->mKeyframe = true; MOZ_ASSERT(frame->mTime >= 0); MOZ_ASSERT(frame->mDuration > 0); if (mNumParsedFrames == 1) { // First frame parsed, let's read VBR info if available. // TODO: read info that helps with seeking (bug 1163667). ByteReader reader(frame->Data(), frame->Size()); mParser.ParseVBRHeader(&reader); reader.DiscardRemaining(); mFirstFrameOffset = frame->mOffset; } MP3LOGV("GetNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64 " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d", mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond, mChannels); return frame.forget(); }
void WAVTrackDemuxer::UpdateState(const MediaByteRange& aRange) { // Full chunk parsed, move offset to its end. mOffset = aRange.mEnd; mTotalChunkLen += aRange.Length(); }
already_AddRefed<mozilla::MediaByteBuffer> MoofParser::Metadata() { MediaByteRange moov; ScanForMetadata(moov); CheckedInt<MediaByteBuffer::size_type> moovLength = moov.Length(); if (!moovLength.isValid() || !moovLength.value()) { // No moov, or cannot be used as array size. return nullptr; } RefPtr<MediaByteBuffer> metadata = new MediaByteBuffer(); if (!metadata->SetLength(moovLength.value(), fallible)) { LOG(Moof, "OOM"); return nullptr; } RefPtr<BlockingStream> stream = new BlockingStream(mSource); size_t read; bool rv = stream->ReadAt(moov.mStart, metadata->Elements(), moovLength.value(), &read); if (!rv || read != moovLength.value()) { return nullptr; } return metadata.forget(); }
BOOL MediaSourceImpl::NeedRestart(const MediaByteRange& request) { OP_ASSERT(!request.IsEmpty()); // Note: this function assumes that request is not in cache // If not loading we certainly need to start. if (m_state == NONE || m_state == IDLE) return TRUE; // Only restart resumable resources. if (!IsResumableURL(m_use_url)) return FALSE; // Get the currently loading range. MediaByteRange loading; m_use_url->GetAttribute(URL::KHTTPRangeStart, &loading.start); m_use_url->GetAttribute(URL::KHTTPRangeEnd, &loading.end); OP_ASSERT(!loading.IsEmpty()); // When streaming, adjust the loading range to not include what // has already been evicted from the cache. Note: This must not be // done for a request that was just started, as the cache can then // contain data from the previous request which is not relevant. if (m_state >= LOADING && IsStreaming()) { BOOL available = FALSE; OpFileLength length = 0; GetPartialCoverage(loading.start, available, length); if (!available && (!loading.IsFinite() || length < loading.Length())) loading.start += length; } // Restart if request is before currently loading range. if (request.start < loading.start) return TRUE; // Restart if request is after currently loading range. if (loading.IsFinite() && request.start > loading.end) return TRUE; // request is now a subset of loading, check how much we have left // to load until request.start. BOOL available = FALSE; OpFileLength length = 0; GetPartialCoverage(loading.start, available, length); if (!available) length = 0; if (request.start > loading.start + length) { // FIXME: calculate download rate and time taken to reach offset (CORE-27952) OpFileLength remaining = request.start - (loading.start + length); if (remaining > MEDIA_SOURCE_MAX_WAIT) return TRUE; } return FALSE; }
already_AddRefed<MediaRawData> WAVTrackDemuxer::GetNextChunk(const MediaByteRange& aRange) { if (!aRange.Length()) { return nullptr; } RefPtr<MediaRawData> datachunk = new MediaRawData(); datachunk->mOffset = aRange.mStart; nsAutoPtr<MediaRawDataWriter> chunkWriter(datachunk->CreateWriter()); if (!chunkWriter->SetSize(aRange.Length())) { return nullptr; } const uint32_t read = Read(chunkWriter->Data(), datachunk->mOffset, datachunk->Size()); if (read != aRange.Length()) { return nullptr; } UpdateState(aRange); ++mNumParsedChunks; ++mChunkIndex; datachunk->mTime = Duration(mChunkIndex - 1); if (static_cast<uint32_t>(mChunkIndex) * DATA_CHUNK_SIZE < mDataLength) { datachunk->mDuration = Duration(1); } else { uint32_t mBytesRemaining = mDataLength - mChunkIndex * DATA_CHUNK_SIZE; datachunk->mDuration = DurationFromBytes(mBytesRemaining); } datachunk->mTimecode = datachunk->mTime; datachunk->mKeyframe = true; MOZ_ASSERT(!datachunk->mTime.IsNegative()); MOZ_ASSERT(!datachunk->mDuration.IsNegative()); return datachunk.forget(); }
bool MP3TrackDemuxer::SkipNextFrame(const MediaByteRange& aRange) { if (!mNumParsedFrames || !aRange.Length()) { // We can't skip the first frame, since it could contain VBR headers. nsRefPtr<MediaRawData> frame(GetNextFrame(aRange)); return frame; } UpdateState(aRange); return true; }
already_AddRefed<mozilla::MediaByteBuffer> MoofParser::Metadata() { MediaByteRange ftyp; MediaByteRange moov; ScanForMetadata(ftyp, moov); CheckedInt<MediaByteBuffer::size_type> ftypLength = ftyp.Length(); CheckedInt<MediaByteBuffer::size_type> moovLength = moov.Length(); if (!ftypLength.isValid() || !moovLength.isValid() || !ftypLength.value() || !moovLength.value()) { // No ftyp or moov, or they cannot be used as array size. return nullptr; } CheckedInt<MediaByteBuffer::size_type> totalLength = ftypLength + moovLength; if (!totalLength.isValid()) { // Addition overflow, or sum cannot be used as array size. return nullptr; } RefPtr<MediaByteBuffer> metadata = new MediaByteBuffer(); if (!metadata->SetLength(totalLength.value(), fallible)) { // OOM return nullptr; } RefPtr<mp4_demuxer::BlockingStream> stream = new BlockingStream(mSource); size_t read; bool rv = stream->ReadAt(ftyp.mStart, metadata->Elements(), ftypLength.value(), &read); if (!rv || read != ftypLength.value()) { return nullptr; } rv = stream->ReadAt(moov.mStart, metadata->Elements() + ftypLength.value(), moovLength.value(), &read); if (!rv || read != moovLength.value()) { return nullptr; } return metadata.forget(); }
already_AddRefed<MediaRawData> MP3TrackDemuxer::GetNextFrame(const MediaByteRange& aRange) { if (!aRange.Length()) { return nullptr; } nsRefPtr<MediaRawData> frame = new MediaRawData(); frame->mOffset = aRange.mStart; nsAutoPtr<MediaRawDataWriter> frameWriter(frame->CreateWriter()); if (!frameWriter->SetSize(aRange.Length())) { return nullptr; } const uint32_t read = Read(frameWriter->mData, frame->mOffset, frame->mSize); if (read != aRange.Length()) { return nullptr; } UpdateState(aRange); frame->mTime = Duration(mFrameIndex - 1).ToMicroseconds(); frame->mDuration = Duration(1).ToMicroseconds(); MOZ_ASSERT(frame->mTime >= 0); MOZ_ASSERT(frame->mDuration > 0); if (mNumParsedFrames == 1) { // First frame parsed, let's read VBR info if available. // TODO: read info that helps with seeking (bug 1163667). mParser.ParseVBRHeader(frame->mData, frame->mData + frame->mSize); mFirstFrameOffset = frame->mOffset; } return frame.forget(); }
MediaByteRange MP3TrackDemuxer::FindFirstFrame() { static const int MIN_SUCCESSIVE_FRAMES = 4; MediaByteRange candidateFrame = FindNextFrame(); int numSuccFrames = candidateFrame.Length() > 0; MediaByteRange currentFrame = candidateFrame; MP3LOGV("FindFirst() first candidate frame: mOffset=%" PRIu64 " Length()=%" PRIu64, candidateFrame.mStart, candidateFrame.Length()); while (candidateFrame.Length() && numSuccFrames < MIN_SUCCESSIVE_FRAMES) { mParser.EndFrameSession(); mOffset = currentFrame.mEnd; const MediaByteRange prevFrame = currentFrame; // FindNextFrame() here will only return frames consistent with our candidate frame. currentFrame = FindNextFrame(); numSuccFrames += currentFrame.Length() > 0; // Multiple successive false positives, which wouldn't be caught by the consistency // checks alone, can be detected by wrong alignment (non-zero gap between frames). const int64_t frameSeparation = currentFrame.mStart - prevFrame.mEnd; if (!currentFrame.Length() || frameSeparation != 0) { MP3LOGV("FindFirst() not enough successive frames detected, " "rejecting candidate frame: successiveFrames=%d, last Length()=%" PRIu64 ", last frameSeparation=%" PRId64, numSuccFrames, currentFrame.Length(), frameSeparation); mParser.ResetFrameData(); mOffset = candidateFrame.mStart + 1; candidateFrame = FindNextFrame(); numSuccFrames = candidateFrame.Length() > 0; currentFrame = candidateFrame; MP3LOGV("FindFirst() new candidate frame: mOffset=%" PRIu64 " Length()=%" PRIu64, candidateFrame.mStart, candidateFrame.Length()); } } if (numSuccFrames >= MIN_SUCCESSIVE_FRAMES) { MP3LOG("FindFirst() accepting candidate frame: " "successiveFrames=%d", numSuccFrames); } else { MP3LOG("FindFirst() no suitable first frame found"); } return candidateFrame; }
bool MP3TrackDemuxer::SkipNextFrame(const MediaByteRange& aRange) { if (!mNumParsedFrames || !aRange.Length()) { // We can't skip the first frame, since it could contain VBR headers. nsRefPtr<MediaRawData> frame(GetNextFrame(aRange)); return frame; } UpdateState(aRange); MP3DEMUXER_LOGV("SkipNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64 " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64 " mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d", mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond, mChannels); return true; }
void MediaSourceImpl::CalcRequest(MediaByteRange& request) { if (IsStreaming()) { // When streaming we only consider the pending range (if any). // When there are no clients, request the entire resource. // To support preload together with streaming, care must be // taken to not restart a preload request [0,Inf] as soon as // data has been evicted and the cached range is e.g. // [500,1000499] if there is no pending request in that range. if (!m_clients.Empty()) { MediaByteRange preload; // unused ReduceRanges(m_clients, request, preload); if (!request.IsEmpty()) { request.SetLength(FILE_LENGTH_NONE); IntersectWithUnavailable(request, m_use_url); if (!request.IsEmpty()) request.SetLength(FILE_LENGTH_NONE); } } else { request.start = 0; } // At this point we should have an unbounded range, but it may // be clamped to the resource length below. OP_ASSERT(!request.IsFinite()); } else { // When not streaming (using multiple range disk cache), both // pending and preload requests are taken into account. // // Example 1: Everything should be preloaded, but since there is a // pending read, start buffering from that offset first. Also, // don't refetch the end of the resource. // // resource: <----------------------> // buffered: <--> <---> // pending: <----> // preload: <----------------------> // request: <--------> // // Example 2: The requested preload is already buffered, so the // request is the empty range. // // resource: <----------------------> // buffered: <--------> // pending: empty range // preload: <-----> // request: empty range MediaByteRange pending, preload; ReduceRanges(m_clients, pending, preload); CombineRanges(pending, preload, request); IntersectWithUnavailable(request, m_use_url); } if (!request.IsEmpty()) { // Extra restrictions if resource length is known (this won't // be needed after CORE-30311 is fixed). OpFileLength resource_length = GetTotalBytes(); if (resource_length > 0) { OP_ASSERT(request.start <= resource_length); MediaByteRange resource(0, resource_length - 1); // Clamp request to resource. request.IntersectWith(resource); // Increase size if it is unreasonably small at the end... if (!request.IsEmpty() && request.Length() < MEDIA_SOURCE_MIN_SIZE && resource.Length() >= MEDIA_SOURCE_MIN_SIZE && request.end == resource.end) { // ... but only if nothing in that range is available. MediaByteRange cand_request(resource_length - MEDIA_SOURCE_MIN_SIZE, resource_length - 1); OP_ASSERT(cand_request.Length() == MEDIA_SOURCE_MIN_SIZE); IntersectWithUnavailable(cand_request, m_use_url); if (cand_request.Length() == MEDIA_SOURCE_MIN_SIZE) request = cand_request; } } } OP_NEW_DBG("CalcRequest", "MediaSource"); OP_DBG(("request: ") << request); }