uint32_t ResourceQueue::EvictBefore(uint64_t aOffset, ErrorResult& aRv) { SBR_DEBUG("EvictBefore(%llu)", aOffset); uint32_t evicted = 0; while (ResourceItem* item = ResourceAt(0)) { SBR_DEBUG("item=%p length=%d offset=%llu", item, item->mData->Length(), mOffset); if (item->mData->Length() + mOffset >= aOffset) { if (aOffset <= mOffset) { break; } uint32_t offset = aOffset - mOffset; mOffset += offset; evicted += offset; RefPtr<MediaByteBuffer> data = new MediaByteBuffer; if (!data->AppendElements(item->mData->Elements() + offset, item->mData->Length() - offset, fallible)) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return 0; } item->mData = data; break; } mOffset += item->mData->Length(); evicted += item->mData->Length(); delete PopFront(); } return evicted; }
TEST(ContainerParser, ADTSBlankMedia) { nsAutoPtr<ContainerParser> parser; parser = ContainerParser::CreateForMIMEType( MediaContainerType(MEDIAMIMETYPE("audio/aac"))); ASSERT_NE(parser, nullptr); // Audio data should have no gaps. EXPECT_EQ(parser->GetRoundingError(), 0); // Test the header only. RefPtr<MediaByteBuffer> header = make_adts_header(); EXPECT_TRUE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header))); // Test with the correct length of (invalid) frame data. size_t header_length = header->Length(); size_t data_length = 24; size_t frame_length = header_length + data_length; header->AppendElements(data_length); EXPECT_TRUE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header))) << "Rejected a valid header."; EXPECT_TRUE(NS_SUCCEEDED(parser->IsMediaSegmentPresent(header))) << "Rejected a full (but zeroed) media segment."; int64_t start = 0; int64_t end = 0; // We don't report timestamps from ADTS. EXPECT_TRUE( NS_FAILED(parser->ParseStartAndEndTimestamps(header, start, end))); EXPECT_EQ(start, 0); EXPECT_EQ(end, 0); // Verify the parser calculated header and packet data boundaries. EXPECT_TRUE(parser->HasInitData()); EXPECT_TRUE(parser->HasCompleteInitData()); MediaByteBuffer* init = parser->InitData(); ASSERT_NE(init, nullptr); EXPECT_EQ(init->Length(), header_length) << "Found incorrect init segment length."; EXPECT_EQ(parser->InitSegmentRange(), MediaByteRange(0, int64_t(header_length))); // In ADTS the Media Header is the same as the Media Segment. MediaByteRange expected_media = MediaByteRange(int64_t(header_length), int64_t(frame_length)); EXPECT_EQ(parser->MediaHeaderRange(), expected_media); EXPECT_EQ(parser->MediaSegmentRange(), expected_media); }
already_AddRefed<MediaByteBuffer> SourceBuffer::PrepareAppend(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv) { typedef TrackBuffersManager::EvictDataResult Result; if (!IsAttached() || mUpdating) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return nullptr; } // If the HTMLMediaElement.error attribute is not null, then throw an // InvalidStateError exception and abort these steps. if (!mMediaSource->GetDecoder() || mMediaSource->GetDecoder()->OwnerHasError()) { MSE_DEBUG("HTMLMediaElement.error is not null"); aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return nullptr; } if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) { mMediaSource->SetReadyState(MediaSourceReadyState::Open); } // Eviction uses a byte threshold. If the buffer is greater than the // number of bytes then data is evicted. // TODO: Drive evictions off memory pressure notifications. // TODO: Consider a global eviction threshold rather than per TrackBuffer. // Give a chance to the TrackBuffersManager to evict some data if needed. Result evicted = mTrackBuffersManager->EvictData(TimeUnit::FromSeconds(mMediaSource->GetDecoder()->GetCurrentTime()), aLength); // See if we have enough free space to append our new data. if (evicted == Result::BUFFER_FULL) { aRv.Throw(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR); return nullptr; } RefPtr<MediaByteBuffer> data = new MediaByteBuffer(); if (!data->AppendElements(aData, aLength, fallible)) { aRv.Throw(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR); return nullptr; } return data.forget(); }
TEST(BitWriter, SPS) { uint8_t sps_pps[] = {0x01, 0x4d, 0x40, 0x0c, 0xff, 0xe1, 0x00, 0x1b, 0x67, 0x4d, 0x40, 0x0c, 0xe8, 0x80, 0x80, 0x9d, 0x80, 0xb5, 0x01, 0x01, 0x01, 0x40, 0x00, 0x00, 0x03, 0x00, 0x40, 0x00, 0x00, 0x0f, 0x03, 0xc5, 0x0a, 0x44, 0x80, 0x01, 0x00, 0x04, 0x68, 0xeb, 0xef, 0x20}; RefPtr<MediaByteBuffer> extraData = new MediaByteBuffer(); extraData->AppendElements(sps_pps, sizeof(sps_pps)); SPSData spsdata1; bool success = H264::DecodeSPSFromExtraData(extraData, spsdata1); EXPECT_EQ(success, true); RefPtr<MediaByteBuffer> extraData2 = H264::CreateExtraData(0x42, 0xc0, 0x1e, {1280, 720}); SPSData spsdata2; success = H264::DecodeSPSFromExtraData(extraData2, spsdata2); EXPECT_EQ(success, true); }
/* static */ bool H264::DecodeSPSFromExtraData(const mozilla::MediaByteBuffer* aExtraData, SPSData& aDest) { if (!AnnexB::HasSPS(aExtraData)) { return false; } ByteReader reader(aExtraData); if (!reader.Read(5)) { return false; } if (!(reader.ReadU8() & 0x1f)) { // No SPS. reader.DiscardRemaining(); return false; } uint16_t length = reader.ReadU16(); if ((reader.PeekU8() & 0x1f) != 7) { // Not a SPS NAL type. reader.DiscardRemaining(); return false; } const uint8_t* ptr = reader.Read(length); if (!ptr) { return false; } reader.DiscardRemaining(); RefPtr<mozilla::MediaByteBuffer> rawNAL = new mozilla::MediaByteBuffer; rawNAL->AppendElements(ptr, length); RefPtr<mozilla::MediaByteBuffer> sps = DecodeNALUnit(rawNAL); if (!sps) { return false; } return DecodeSPS(sps, aDest); }
TEST(stagefright_MP4Metadata, EmptyCTTS) { RefPtr<MediaByteBuffer> buffer = new MediaByteBuffer(media_libstagefright_gtest_video_init_mp4_len); buffer->AppendElements(media_libstagefright_gtest_video_init_mp4, media_libstagefright_gtest_video_init_mp4_len); RefPtr<BufferStream> stream = new BufferStream(buffer); EXPECT_TRUE(MP4Metadata::HasCompleteMetadata(stream)); RefPtr<MediaByteBuffer> metadataBuffer = MP4Metadata::Metadata(stream); EXPECT_TRUE(metadataBuffer); MP4Metadata metadata(stream); EXPECT_EQ(1u, metadata.GetNumberTracks(TrackInfo::kVideoTrack)); mozilla::UniquePtr<mozilla::TrackInfo> track = metadata.GetTrackInfo(TrackInfo::kVideoTrack, 0); EXPECT_TRUE(track != nullptr); // We can seek anywhere in any MPEG4. EXPECT_TRUE(metadata.CanSeek()); EXPECT_FALSE(metadata.Crypto().valid); }
already_AddRefed<MediaByteBuffer> SourceBuffer::PrepareAppend(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv) { typedef SourceBufferContentManager::EvictDataResult Result; if (!IsAttached() || mUpdating) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return nullptr; } // If the HTMLMediaElement.error attribute is not null, then throw an // InvalidStateError exception and abort these steps. if (!mMediaSource->GetDecoder() || mMediaSource->GetDecoder()->IsEndedOrShutdown()) { aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return nullptr; } if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) { mMediaSource->SetReadyState(MediaSourceReadyState::Open); } // Eviction uses a byte threshold. If the buffer is greater than the // number of bytes then data is evicted. The time range for this // eviction is reported back to the media source. It will then // evict data before that range across all SourceBuffers it knows // about. // TODO: Make the eviction threshold smaller for audio-only streams. // TODO: Drive evictions off memory pressure notifications. // TODO: Consider a global eviction threshold rather than per TrackBuffer. TimeUnit newBufferStartTime; // Attempt to evict the amount of data we are about to add by lowering the // threshold. uint32_t toEvict = (mEvictionThreshold > aLength) ? mEvictionThreshold - aLength : aLength; Result evicted = mContentManager->EvictData(TimeUnit::FromSeconds(mMediaSource->GetDecoder()->GetCurrentTime()), toEvict, &newBufferStartTime); if (evicted == Result::DATA_EVICTED) { MSE_DEBUG("AppendData Evict; current buffered start=%f", GetBufferedStart()); // We notify that we've evicted from the time range 0 through to // the current start point. mMediaSource->NotifyEvicted(0.0, newBufferStartTime.ToSeconds()); } // See if we have enough free space to append our new data. // As we can only evict once we have playable data, we must give a chance // to the DASH player to provide a complete media segment. if (aLength > mEvictionThreshold || evicted == Result::BUFFER_FULL) { aRv.Throw(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR); return nullptr; } RefPtr<MediaByteBuffer> data = new MediaByteBuffer(); if (!data->AppendElements(aData, aLength, fallible)) { aRv.Throw(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR); return nullptr; } return data.forget(); }
TEST(ContainerParser, ADTSHeader) { nsAutoPtr<ContainerParser> parser; parser = ContainerParser::CreateForMIMEType( MediaContainerType(MEDIAMIMETYPE("audio/aac"))); ASSERT_NE(parser, nullptr); // Audio data should have no gaps. EXPECT_EQ(parser->GetRoundingError(), 0); // Test a valid header. RefPtr<MediaByteBuffer> header = make_adts_header(); EXPECT_TRUE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header))); // Test variations. uint8_t save = header->ElementAt(1); for (uint8_t i = 1; i < 3; ++i) { // Set non-zero layer. header->ReplaceElementAt(1, (header->ElementAt(1) & 0xf9) | (i << 1)); EXPECT_FALSE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header))) << "Accepted non-zero layer in header."; } header->ReplaceElementAt(1, save); save = header->ElementAt(2); header->ReplaceElementAt(2, (header->ElementAt(2) & 0x3b) | (15 << 2)); EXPECT_FALSE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header))) << "Accepted explicit frequency in header."; header->ReplaceElementAt(2, save); // Test a short header. header->SetLength(6); EXPECT_FALSE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header))) << "Accepted too-short header."; EXPECT_FALSE(NS_SUCCEEDED(parser->IsMediaSegmentPresent(header))) << "Found media segment when there was just a partial header."; // Test a header with short data. header = make_adts_header(); header->AppendElements(1); EXPECT_TRUE(NS_SUCCEEDED(parser->IsInitSegmentPresent(header))) << "Rejected a valid header."; EXPECT_TRUE(NS_SUCCEEDED(parser->IsMediaSegmentPresent(header))) << "Rejected a one-byte media segment."; // Test parse results. header = make_adts_header(); EXPECT_FALSE(NS_SUCCEEDED(parser->IsMediaSegmentPresent(header))) << "Found media segment when there was just a header."; int64_t start = 0; int64_t end = 0; EXPECT_TRUE( NS_FAILED(parser->ParseStartAndEndTimestamps(header, start, end))); EXPECT_TRUE(parser->HasInitData()); EXPECT_TRUE(parser->HasCompleteInitData()); MediaByteBuffer* init = parser->InitData(); ASSERT_NE(init, nullptr); EXPECT_EQ(init->Length(), header->Length()); EXPECT_EQ(parser->InitSegmentRange(), MediaByteRange(0, int64_t(header->Length()))); // Media segment range should be empty here. EXPECT_EQ(parser->MediaHeaderRange(), MediaByteRange()); EXPECT_EQ(parser->MediaSegmentRange(), MediaByteRange()); }