forked from mirrors/gecko-dev
Merge mozilla-central to autoland.
This commit is contained in:
commit
3979c05ca1
166 changed files with 1169 additions and 3387 deletions
|
|
@ -90,9 +90,9 @@ git = "https://github.com/mozilla/midir.git"
|
|||
rev = "519e651241e867af3391db08f9ae6400bc023e18"
|
||||
replace-with = "vendored-sources"
|
||||
|
||||
[source."git+https://github.com/mozilla/mp4parse-rust?rev=cf8b0e04de9c60f38f7f057f9f29c74d19336d0c"]
|
||||
[source."git+https://github.com/mozilla/mp4parse-rust?rev=2b572e83608a3d0867b935e076f45d9fe248069d"]
|
||||
git = "https://github.com/mozilla/mp4parse-rust"
|
||||
rev = "cf8b0e04de9c60f38f7f057f9f29c74d19336d0c"
|
||||
rev = "2b572e83608a3d0867b935e076f45d9fe248069d"
|
||||
replace-with = "vendored-sources"
|
||||
|
||||
[source."git+https://github.com/mozilla/neqo?tag=v0.6.4"]
|
||||
|
|
|
|||
8
Cargo.lock
generated
8
Cargo.lock
generated
|
|
@ -3469,8 +3469,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "mp4parse"
|
||||
version = "0.17.0"
|
||||
source = "git+https://github.com/mozilla/mp4parse-rust?rev=cf8b0e04de9c60f38f7f057f9f29c74d19336d0c#cf8b0e04de9c60f38f7f057f9f29c74d19336d0c"
|
||||
version = "0.16.0"
|
||||
source = "git+https://github.com/mozilla/mp4parse-rust?rev=2b572e83608a3d0867b935e076f45d9fe248069d#2b572e83608a3d0867b935e076f45d9fe248069d"
|
||||
dependencies = [
|
||||
"bitreader",
|
||||
"byteorder",
|
||||
|
|
@ -3486,8 +3486,8 @@ version = "0.1.0"
|
|||
|
||||
[[package]]
|
||||
name = "mp4parse_capi"
|
||||
version = "0.17.0"
|
||||
source = "git+https://github.com/mozilla/mp4parse-rust?rev=cf8b0e04de9c60f38f7f057f9f29c74d19336d0c#cf8b0e04de9c60f38f7f057f9f29c74d19336d0c"
|
||||
version = "0.16.0"
|
||||
source = "git+https://github.com/mozilla/mp4parse-rust?rev=2b572e83608a3d0867b935e076f45d9fe248069d#2b572e83608a3d0867b935e076f45d9fe248069d"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"fallible_collections",
|
||||
|
|
|
|||
|
|
@ -3094,11 +3094,11 @@ bool HTMLMediaElement::Seeking() const {
|
|||
|
||||
double HTMLMediaElement::CurrentTime() const {
|
||||
if (mMediaStreamRenderer) {
|
||||
return ToMicrosecondResolution(mMediaStreamRenderer->CurrentTime());
|
||||
return mMediaStreamRenderer->CurrentTime();
|
||||
}
|
||||
|
||||
if (mDefaultPlaybackStartPosition == 0.0 && mDecoder) {
|
||||
return std::clamp(mDecoder->GetCurrentTime(), 0.0, mDecoder->GetDuration());
|
||||
return mDecoder->GetCurrentTime();
|
||||
}
|
||||
|
||||
return mDefaultPlaybackStartPosition;
|
||||
|
|
@ -3136,7 +3136,7 @@ already_AddRefed<Promise> HTMLMediaElement::SeekToNextFrame(ErrorResult& aRv) {
|
|||
|
||||
void HTMLMediaElement::SetCurrentTime(double aCurrentTime, ErrorResult& aRv) {
|
||||
LOG(LogLevel::Debug,
|
||||
("%p SetCurrentTime(%lf) called by JS", this, aCurrentTime));
|
||||
("%p SetCurrentTime(%f) called by JS", this, aCurrentTime));
|
||||
Seek(aCurrentTime, SeekTarget::Accurate, IgnoreErrors());
|
||||
}
|
||||
|
||||
|
|
@ -3200,8 +3200,6 @@ void HTMLMediaElement::Seek(double aTime, SeekTarget::Type aSeekType,
|
|||
mCurrentPlayRangeStart, rangeEndTime));
|
||||
// Multiple seek without playing, or seek while playing.
|
||||
if (mCurrentPlayRangeStart != rangeEndTime) {
|
||||
// Don't round the left of the interval: it comes from script and needs
|
||||
// to be exact.
|
||||
mPlayed->Add(mCurrentPlayRangeStart, rangeEndTime);
|
||||
}
|
||||
// Reset the current played range start time. We'll re-set it once
|
||||
|
|
@ -3223,13 +3221,13 @@ void HTMLMediaElement::Seek(double aTime, SeekTarget::Type aSeekType,
|
|||
}
|
||||
|
||||
// Clamp the seek target to inside the seekable ranges.
|
||||
media::TimeRanges seekableRanges = mDecoder->GetSeekableTimeRanges();
|
||||
if (seekableRanges.IsInvalid()) {
|
||||
media::TimeIntervals seekableIntervals = mDecoder->GetSeekable();
|
||||
if (seekableIntervals.IsInvalid()) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
RefPtr<TimeRanges> seekable =
|
||||
new TimeRanges(ToSupports(OwnerDoc()), seekableRanges);
|
||||
new TimeRanges(ToSupports(OwnerDoc()), seekableIntervals);
|
||||
uint32_t length = seekable->Length();
|
||||
if (length == 0) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
|
|
@ -3302,8 +3300,7 @@ double HTMLMediaElement::Duration() const {
|
|||
already_AddRefed<TimeRanges> HTMLMediaElement::Seekable() const {
|
||||
media::TimeIntervals seekable =
|
||||
mDecoder ? mDecoder->GetSeekable() : media::TimeIntervals();
|
||||
RefPtr<TimeRanges> ranges = new TimeRanges(
|
||||
ToSupports(OwnerDoc()), seekable.ToMicrosecondResolution());
|
||||
RefPtr<TimeRanges> ranges = new TimeRanges(ToSupports(OwnerDoc()), seekable);
|
||||
return ranges.forget();
|
||||
}
|
||||
|
||||
|
|
@ -3323,8 +3320,6 @@ already_AddRefed<TimeRanges> HTMLMediaElement::Played() {
|
|||
if (mCurrentPlayRangeStart != -1.0) {
|
||||
double now = CurrentTime();
|
||||
if (mCurrentPlayRangeStart != now) {
|
||||
// Don't round the left of the interval: it comes from script and needs
|
||||
// to be exact.
|
||||
ranges->Add(mCurrentPlayRangeStart, now);
|
||||
}
|
||||
}
|
||||
|
|
@ -6632,8 +6627,7 @@ nsresult HTMLMediaElement::CopyInnerTo(Element* aDest) {
|
|||
already_AddRefed<TimeRanges> HTMLMediaElement::Buffered() const {
|
||||
media::TimeIntervals buffered =
|
||||
mDecoder ? mDecoder->GetBuffered() : media::TimeIntervals();
|
||||
RefPtr<TimeRanges> ranges = new TimeRanges(
|
||||
ToSupports(OwnerDoc()), buffered.ToMicrosecondResolution());
|
||||
RefPtr<TimeRanges> ranges = new TimeRanges(ToSupports(OwnerDoc()), buffered);
|
||||
return ranges.forget();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,23 +35,9 @@ TimeRanges::TimeRanges(nsISupports* aParent,
|
|||
}
|
||||
}
|
||||
|
||||
TimeRanges::TimeRanges(nsISupports* aParent,
|
||||
const media::TimeRanges& aTimeRanges)
|
||||
: TimeRanges(aParent) {
|
||||
if (aTimeRanges.IsInvalid()) {
|
||||
return;
|
||||
}
|
||||
for (const media::TimeRange& interval : aTimeRanges) {
|
||||
Add(interval.mStart, interval.mEnd);
|
||||
}
|
||||
}
|
||||
|
||||
TimeRanges::TimeRanges(const media::TimeIntervals& aTimeIntervals)
|
||||
: TimeRanges(nullptr, aTimeIntervals) {}
|
||||
|
||||
TimeRanges::TimeRanges(const media::TimeRanges& aTimeRanges)
|
||||
: TimeRanges(nullptr, aTimeRanges) {}
|
||||
|
||||
media::TimeIntervals TimeRanges::ToTimeIntervals() const {
|
||||
media::TimeIntervals t;
|
||||
for (uint32_t i = 0; i < Length(); i++) {
|
||||
|
|
|
|||
|
|
@ -32,9 +32,7 @@ class TimeRanges final : public nsISupports, public nsWrapperCache {
|
|||
TimeRanges();
|
||||
explicit TimeRanges(nsISupports* aParent);
|
||||
explicit TimeRanges(const media::TimeIntervals& aTimeIntervals);
|
||||
explicit TimeRanges(const media::TimeRanges& aTimeRanges);
|
||||
TimeRanges(nsISupports* aParent, const media::TimeIntervals& aTimeIntervals);
|
||||
TimeRanges(nsISupports* aParent, const media::TimeRanges& aTimeRanges);
|
||||
|
||||
media::TimeIntervals ToTimeIntervals() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -343,13 +343,11 @@ bool ADTSTrackDemuxer::Init() {
|
|||
mInfo->mRate, mInfo->mChannels, mInfo->mBitDepth,
|
||||
mInfo->mDuration.ToMicroseconds());
|
||||
|
||||
// AAC encoder delay can be 2112 (typical value when using Apple AAC encoder),
|
||||
// or 1024 (typical value when encoding using fdk_aac, often via ffmpeg).
|
||||
// AAC encoder delay is by default 2112 audio frames.
|
||||
// See
|
||||
// https://developer.apple.com/library/content/documentation/QuickTime/QTFF/QTFFAppenG/QTFFAppenG.html
|
||||
// In an attempt to not trim valid audio data, and because ADTS doesn't
|
||||
// provide a way to know this pre-roll value, this offets by 1024 frames.
|
||||
mPreRoll = TimeUnit(1024, mSamplesPerSecond);
|
||||
// So we always seek 2112 frames prior the seeking point.
|
||||
mPreRoll = TimeUnit::FromMicroseconds(2112 * 1000000ULL / mSamplesPerSecond);
|
||||
return mChannels;
|
||||
}
|
||||
|
||||
|
|
@ -498,13 +496,13 @@ int64_t ADTSTrackDemuxer::StreamLength() const { return mSource.GetLength(); }
|
|||
|
||||
TimeUnit ADTSTrackDemuxer::Duration() const {
|
||||
if (!mNumParsedFrames) {
|
||||
return TimeUnit::Invalid();
|
||||
return TimeUnit::FromMicroseconds(-1);
|
||||
}
|
||||
|
||||
const int64_t streamLen = StreamLength();
|
||||
if (streamLen < 0) {
|
||||
// Unknown length, we can't estimate duration.
|
||||
return TimeUnit::Invalid();
|
||||
return TimeUnit::FromMicroseconds(-1);
|
||||
}
|
||||
const int64_t firstFrameOffset = mParser->FirstFrame().Offset();
|
||||
int64_t numFrames = (streamLen - firstFrameOffset) / AverageFrameLength();
|
||||
|
|
@ -513,10 +511,10 @@ TimeUnit ADTSTrackDemuxer::Duration() const {
|
|||
|
||||
TimeUnit ADTSTrackDemuxer::Duration(int64_t aNumFrames) const {
|
||||
if (!mSamplesPerSecond) {
|
||||
return TimeUnit::Invalid();
|
||||
return TimeUnit::FromMicroseconds(-1);
|
||||
}
|
||||
|
||||
return TimeUnit(aNumFrames * mSamplesPerFrame, mSamplesPerSecond);
|
||||
return FramesToTimeUnit(aNumFrames * mSamplesPerFrame, mSamplesPerSecond);
|
||||
}
|
||||
|
||||
const adts::Frame& ADTSTrackDemuxer::FindNextFrame(
|
||||
|
|
@ -648,36 +646,13 @@ already_AddRefed<MediaRawData> ADTSTrackDemuxer::GetNextFrame(
|
|||
|
||||
UpdateState(aFrame);
|
||||
|
||||
TimeUnit rawpts = Duration(mFrameIndex - 1) - mPreRoll;
|
||||
TimeUnit rawDuration = Duration(1);
|
||||
TimeUnit rawend = rawpts + rawDuration;
|
||||
|
||||
frame->mTime = std::max(TimeUnit::Zero(), rawpts);
|
||||
frame->mTime = Duration(mFrameIndex - 1);
|
||||
frame->mDuration = Duration(1);
|
||||
frame->mTimecode = frame->mTime;
|
||||
frame->mKeyframe = true;
|
||||
|
||||
// Handle decoder delay. A packet must be trimmed if its pts, adjusted for
|
||||
// decoder delay, is negative. A packet can be trimmed entirely.
|
||||
if (rawpts.IsNegative()) {
|
||||
frame->mDuration = std::max(TimeUnit::Zero(), rawend - frame->mTime);
|
||||
}
|
||||
|
||||
// ADTS frames can have a presentation duration of zero, e.g. when a frame is
|
||||
// part of preroll.
|
||||
MOZ_ASSERT(frame->mDuration.IsPositiveOrZero());
|
||||
|
||||
ADTSLOG("ADTS packet demuxed: pts [%lf, %lf] (duration: %lf)",
|
||||
frame->mTime.ToSeconds(), frame->GetEndTime().ToSeconds(),
|
||||
frame->mDuration.ToSeconds());
|
||||
|
||||
// Indicate original packet information to trim after decoding.
|
||||
if (frame->mDuration != rawDuration) {
|
||||
frame->mOriginalPresentationWindow =
|
||||
Some(media::TimeInterval{rawpts, rawend});
|
||||
ADTSLOG("Total packet time excluding trimming: [%lf, %lf]",
|
||||
rawpts.ToSeconds(), rawend.ToSeconds());
|
||||
}
|
||||
MOZ_ASSERT(!frame->mTime.IsNegative());
|
||||
MOZ_ASSERT(frame->mDuration.IsPositive());
|
||||
|
||||
ADTSLOGV("GetNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
|
||||
" mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ class AudioCompactor {
|
|||
NS_ASSERTION(framesCopied <= aFrames, "functor copied too many frames");
|
||||
buffer.SetLength(size_t(framesCopied) * aChannels);
|
||||
|
||||
auto duration = media::TimeUnit(framesCopied, aSampleRate);
|
||||
auto duration = FramesToTimeUnit(framesCopied, aSampleRate);
|
||||
if (!duration.IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@
|
|||
|
||||
namespace mozilla {
|
||||
|
||||
using TimeUnit = media::TimeUnit;
|
||||
|
||||
extern LazyLogModule gMediaDecoderLog;
|
||||
#define LOG(x, ...) \
|
||||
DDMOZ_LOG(gMediaDecoderLog, LogLevel::Debug, x, ##__VA_ARGS__)
|
||||
|
|
@ -314,8 +312,7 @@ void ChannelMediaDecoder::NotifyDownloadEnded(nsresult aStatus) {
|
|||
"ChannelMediaDecoder::UpdatePlaybackRate",
|
||||
[stats = mPlaybackStatistics,
|
||||
res = RefPtr<BaseMediaResource>(mResource), duration = mDuration]() {
|
||||
auto rate = ComputePlaybackRate(stats, res,
|
||||
duration.match(DurationToTimeUnit()));
|
||||
auto rate = ComputePlaybackRate(stats, res, duration);
|
||||
UpdatePlaybackRate(rate, res);
|
||||
});
|
||||
nsresult rv = GetStateMachine()->OwnerThread()->Dispatch(r.forget());
|
||||
|
|
@ -373,8 +370,7 @@ void ChannelMediaDecoder::DurationChanged() {
|
|||
"ChannelMediaDecoder::UpdatePlaybackRate",
|
||||
[stats = mPlaybackStatistics, res = RefPtr<BaseMediaResource>(mResource),
|
||||
duration = mDuration]() {
|
||||
auto rate = ComputePlaybackRate(stats, res,
|
||||
duration.match(DurationToTimeUnit()));
|
||||
auto rate = ComputePlaybackRate(stats, res, duration);
|
||||
UpdatePlaybackRate(rate, res);
|
||||
});
|
||||
nsresult rv = GetStateMachine()->OwnerThread()->Dispatch(r.forget());
|
||||
|
|
@ -393,8 +389,7 @@ void ChannelMediaDecoder::DownloadProgressed() {
|
|||
[playbackStats = mPlaybackStatistics,
|
||||
res = RefPtr<BaseMediaResource>(mResource), duration = mDuration,
|
||||
pos = mPlaybackPosition]() {
|
||||
auto rate = ComputePlaybackRate(
|
||||
playbackStats, res, duration.match(DurationToTimeUnit()));
|
||||
auto rate = ComputePlaybackRate(playbackStats, res, duration);
|
||||
UpdatePlaybackRate(rate, res);
|
||||
MediaStatistics stats = GetStatistics(rate, res, pos);
|
||||
return StatsPromise::CreateAndResolve(stats, __func__);
|
||||
|
|
@ -418,13 +413,13 @@ void ChannelMediaDecoder::DownloadProgressed() {
|
|||
/* static */ ChannelMediaDecoder::PlaybackRateInfo
|
||||
ChannelMediaDecoder::ComputePlaybackRate(const MediaChannelStatistics& aStats,
|
||||
BaseMediaResource* aResource,
|
||||
const TimeUnit& aDuration) {
|
||||
double aDuration) {
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
int64_t length = aResource->GetLength();
|
||||
if (aDuration.IsInfinite() && aDuration.IsPositive() > 0 && length >= 0 &&
|
||||
length / aDuration.ToSeconds() < UINT32_MAX) {
|
||||
return {uint32_t(length / aDuration.ToSeconds()), true};
|
||||
if (std::isfinite(aDuration) && aDuration > 0 && length >= 0 &&
|
||||
length / aDuration < UINT32_MAX) {
|
||||
return {uint32_t(length / aDuration), true};
|
||||
}
|
||||
|
||||
bool reliable = false;
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ class ChannelMediaDecoder
|
|||
// The actual playback rate computation.
|
||||
static PlaybackRateInfo ComputePlaybackRate(
|
||||
const MediaChannelStatistics& aStats, BaseMediaResource* aResource,
|
||||
const media::TimeUnit& aDuration);
|
||||
double aDuration);
|
||||
|
||||
// Something has changed that could affect the computed playback rate,
|
||||
// so recompute it.
|
||||
|
|
|
|||
|
|
@ -40,13 +40,17 @@ class Interval {
|
|||
|
||||
template <typename StartArg, typename EndArg>
|
||||
Interval(StartArg&& aStart, EndArg&& aEnd)
|
||||
: mStart(aStart), mEnd(aEnd), mFuzz() {
|
||||
: mStart(std::forward<StartArg>(aStart)),
|
||||
mEnd(std::forward<EndArg>(aEnd)),
|
||||
mFuzz() {
|
||||
MOZ_DIAGNOSTIC_ASSERT(mStart <= mEnd, "Invalid Interval");
|
||||
}
|
||||
|
||||
template <typename StartArg, typename EndArg, typename FuzzArg>
|
||||
Interval(StartArg&& aStart, EndArg&& aEnd, FuzzArg&& aFuzz)
|
||||
: mStart(aStart), mEnd(aEnd), mFuzz(aFuzz) {
|
||||
: mStart(std::forward<StartArg>(aStart)),
|
||||
mEnd(std::forward<EndArg>(aEnd)),
|
||||
mFuzz(std::forward<FuzzArg>(aFuzz)) {
|
||||
MOZ_DIAGNOSTIC_ASSERT(mStart <= mEnd, "Invalid Interval");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
namespace mozilla {
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
using layers::ImageContainer;
|
||||
using layers::PlanarYCbCrData;
|
||||
using layers::PlanarYCbCrImage;
|
||||
using media::TimeUnit;
|
||||
|
|
@ -41,10 +42,8 @@ const char* VideoData::sTypeName = "video";
|
|||
AudioData::AudioData(int64_t aOffset, const media::TimeUnit& aTime,
|
||||
AlignedAudioBuffer&& aData, uint32_t aChannels,
|
||||
uint32_t aRate, uint32_t aChannelMap)
|
||||
// Passing TimeUnit::Zero() here because we can't pass the result of an
|
||||
// arithmetic operation to the CheckedInt ctor. We set the duration in the
|
||||
// ctor body below.
|
||||
: MediaData(sType, aOffset, aTime, TimeUnit::Zero()),
|
||||
: MediaData(sType, aOffset, aTime,
|
||||
FramesToTimeUnit(aData.Length() / aChannels, aRate)),
|
||||
mChannels(aChannels),
|
||||
mChannelMap(aChannelMap),
|
||||
mRate(aRate),
|
||||
|
|
@ -55,7 +54,6 @@ AudioData::AudioData(int64_t aOffset, const media::TimeUnit& aTime,
|
|||
"Can't create an AudioData with 0 channels.");
|
||||
MOZ_RELEASE_ASSERT(aRate != 0,
|
||||
"Can't create an AudioData with a sample-rate of 0.");
|
||||
mDuration = TimeUnit(mFrames, aRate);
|
||||
}
|
||||
|
||||
Span<AudioDataValue> AudioData::Data() const {
|
||||
|
|
@ -88,34 +86,37 @@ bool AudioData::SetTrimWindow(const media::TimeInterval& aTrim) {
|
|||
// MoveableData got called. Can no longer work on it.
|
||||
return false;
|
||||
}
|
||||
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
||||
const size_t originalFrames = mAudioData.Length() / mChannels;
|
||||
#endif
|
||||
if (aTrim.mStart < mOriginalTime || aTrim.mEnd > GetEndTime()) {
|
||||
const TimeUnit originalDuration = FramesToTimeUnit(originalFrames, mRate);
|
||||
if (aTrim.mStart < mOriginalTime ||
|
||||
aTrim.mEnd > mOriginalTime + originalDuration) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto trimBefore = aTrim.mStart - mOriginalTime;
|
||||
auto trimAfter = aTrim.mEnd - mOriginalTime;
|
||||
if (!trimBefore.IsValid() || !trimAfter.IsValid()) {
|
||||
auto trimBefore = TimeUnitToFrames(aTrim.mStart - mOriginalTime, mRate);
|
||||
auto trimAfter = aTrim.mEnd == GetEndTime()
|
||||
? originalFrames
|
||||
: TimeUnitToFrames(aTrim.mEnd - mOriginalTime, mRate);
|
||||
if (!trimBefore.isValid() || !trimAfter.isValid()) {
|
||||
// Overflow.
|
||||
return false;
|
||||
}
|
||||
if (!mTrimWindow && trimBefore.IsZero() && trimAfter == mDuration) {
|
||||
MOZ_DIAGNOSTIC_ASSERT(trimAfter.value() >= trimBefore.value(),
|
||||
"Something went wrong with trimming value");
|
||||
if (!mTrimWindow && trimBefore == 0 && trimAfter == originalFrames) {
|
||||
// Nothing to change, abort early to prevent rounding errors.
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t frameOffset = trimBefore.ToTicksAtRate(mRate);
|
||||
mTrimWindow = Some(aTrim);
|
||||
mDataOffset = frameOffset * mChannels;
|
||||
mDataOffset = trimBefore.value() * mChannels;
|
||||
MOZ_DIAGNOSTIC_ASSERT(mDataOffset <= mAudioData.Length(),
|
||||
"Data offset outside original buffer");
|
||||
mFrames = (trimAfter - trimBefore).ToTicksAtRate(mRate);
|
||||
mFrames = (trimAfter - trimBefore).value();
|
||||
MOZ_DIAGNOSTIC_ASSERT(mFrames <= originalFrames,
|
||||
"More frames than found in container");
|
||||
mTime = mOriginalTime + trimBefore;
|
||||
mDuration = TimeUnit(mFrames, mRate);
|
||||
mTime = mOriginalTime + FramesToTimeUnit(trimBefore.value(), mRate);
|
||||
mDuration = FramesToTimeUnit(mFrames, mRate);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -277,13 +278,13 @@ PlanarYCbCrData ConstructPlanarYCbCrData(const VideoInfo& aInfo,
|
|||
|
||||
PlanarYCbCrData data;
|
||||
data.mYChannel = Y.mData;
|
||||
data.mYStride = AssertedCast<int32_t>(Y.mStride);
|
||||
data.mYSkip = AssertedCast<int32_t>(Y.mSkip);
|
||||
data.mYStride = Y.mStride;
|
||||
data.mYSkip = Y.mSkip;
|
||||
data.mCbChannel = Cb.mData;
|
||||
data.mCrChannel = Cr.mData;
|
||||
data.mCbCrStride = AssertedCast<int32_t>(Cb.mStride);
|
||||
data.mCbSkip = AssertedCast<int32_t>(Cb.mSkip);
|
||||
data.mCrSkip = AssertedCast<int32_t>(Cr.mSkip);
|
||||
data.mCbCrStride = Cb.mStride;
|
||||
data.mCbSkip = Cb.mSkip;
|
||||
data.mCrSkip = Cr.mSkip;
|
||||
data.mPictureRect = aPicture;
|
||||
data.mStereoMode = aInfo.mStereoMode;
|
||||
data.mYUVColorSpace = aBuffer.mYUVColorSpace;
|
||||
|
|
@ -310,8 +311,9 @@ bool VideoData::SetVideoDataToImage(PlanarYCbCrImage* aVideoImage,
|
|||
|
||||
if (aCopyData) {
|
||||
return aVideoImage->CopyData(data);
|
||||
} else {
|
||||
return aVideoImage->AdoptData(data);
|
||||
}
|
||||
return aVideoImage->AdoptData(data);
|
||||
}
|
||||
|
||||
/* static */
|
||||
|
|
@ -483,8 +485,7 @@ already_AddRefed<VideoData> VideoData::CreateAndCopyData(
|
|||
// The naming convention in the gfx stack is byte-order.
|
||||
ConvertI420AlphaToARGB(aBuffer.mPlanes[0].mData, aBuffer.mPlanes[1].mData,
|
||||
aBuffer.mPlanes[2].mData, aAlphaPlane.mData,
|
||||
AssertedCast<int>(aBuffer.mPlanes[0].mStride),
|
||||
AssertedCast<int>(aBuffer.mPlanes[1].mStride),
|
||||
aBuffer.mPlanes[0].mStride, aBuffer.mPlanes[1].mStride,
|
||||
buffer.data, buffer.stride, buffer.size.width,
|
||||
buffer.size.height);
|
||||
|
||||
|
|
|
|||
|
|
@ -241,9 +241,9 @@ class AlignedBuffer {
|
|||
return true;
|
||||
}
|
||||
Type* mData;
|
||||
size_t mLength{}; // number of elements
|
||||
size_t mLength; // number of elements
|
||||
UniquePtr<uint8_t[]> mBuffer;
|
||||
size_t mCapacity{}; // in bytes
|
||||
size_t mCapacity; // in bytes
|
||||
};
|
||||
|
||||
typedef AlignedBuffer<uint8_t> AlignedByteBuffer;
|
||||
|
|
|
|||
|
|
@ -242,12 +242,12 @@ void MediaDecoder::SetOutputTracksPrincipal(
|
|||
|
||||
double MediaDecoder::GetDuration() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return ToMicrosecondResolution(mDuration.match(DurationToDouble()));
|
||||
return mDuration;
|
||||
}
|
||||
|
||||
bool MediaDecoder::IsInfinite() const {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return std::isinf(mDuration.match(DurationToDouble()));
|
||||
return std::isinf(mDuration);
|
||||
}
|
||||
|
||||
#define INIT_MIRROR(name, val) \
|
||||
|
|
@ -258,7 +258,7 @@ bool MediaDecoder::IsInfinite() const {
|
|||
MediaDecoder::MediaDecoder(MediaDecoderInit& aInit)
|
||||
: mWatchManager(this, aInit.mOwner->AbstractMainThread()),
|
||||
mLogicalPosition(0.0),
|
||||
mDuration(TimeUnit::Invalid()),
|
||||
mDuration(std::numeric_limits<double>::quiet_NaN()),
|
||||
mOwner(aInit.mOwner),
|
||||
mAbstractMainThread(aInit.mOwner->AbstractMainThread()),
|
||||
mFrameStats(new FrameStatistics()),
|
||||
|
|
@ -994,27 +994,25 @@ void MediaDecoder::UpdateTelemetryHelperBasedOnPlayState(
|
|||
}
|
||||
|
||||
MediaDecoder::PositionUpdate MediaDecoder::GetPositionUpdateReason(
|
||||
double aPrevPos, const TimeUnit& aCurPos) const {
|
||||
double aPrevPos, double aCurPos) const {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
// If current position is earlier than previous position and we didn't do
|
||||
// seek, that means we looped back to the start position.
|
||||
const bool notSeeking = !mSeekRequest.Exists();
|
||||
if (mLooping && notSeeking && aCurPos.ToSeconds() < aPrevPos) {
|
||||
if (mLooping && notSeeking && aCurPos < aPrevPos) {
|
||||
return PositionUpdate::eSeamlessLoopingSeeking;
|
||||
}
|
||||
return aPrevPos != aCurPos.ToSeconds() && notSeeking
|
||||
? PositionUpdate::ePeriodicUpdate
|
||||
: PositionUpdate::eOther;
|
||||
return aPrevPos != aCurPos && notSeeking ? PositionUpdate::ePeriodicUpdate
|
||||
: PositionUpdate::eOther;
|
||||
}
|
||||
|
||||
void MediaDecoder::UpdateLogicalPositionInternal() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
|
||||
|
||||
TimeUnit currentPosition = CurrentPosition();
|
||||
double currentPosition = CurrentPosition().ToSeconds();
|
||||
if (mPlayState == PLAY_STATE_ENDED) {
|
||||
currentPosition =
|
||||
std::max(currentPosition, mDuration.match(DurationToTimeUnit()));
|
||||
currentPosition = std::max(currentPosition, mDuration);
|
||||
}
|
||||
|
||||
const PositionUpdate reason =
|
||||
|
|
@ -1049,13 +1047,12 @@ void MediaDecoder::UpdateLogicalPositionInternal() {
|
|||
Invalidate();
|
||||
}
|
||||
|
||||
void MediaDecoder::SetLogicalPosition(const TimeUnit& aNewPosition) {
|
||||
void MediaDecoder::SetLogicalPosition(double aNewPosition) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (TimeUnit::FromSeconds(mLogicalPosition) == aNewPosition ||
|
||||
mLogicalPosition == aNewPosition.ToSeconds()) {
|
||||
if (mLogicalPosition == aNewPosition) {
|
||||
return;
|
||||
}
|
||||
mLogicalPosition = aNewPosition.ToSeconds();
|
||||
mLogicalPosition = aNewPosition;
|
||||
DDLOG(DDLogCategory::Property, "currentTime", mLogicalPosition);
|
||||
}
|
||||
|
||||
|
|
@ -1063,44 +1060,31 @@ void MediaDecoder::DurationChanged() {
|
|||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
|
||||
|
||||
Variant<TimeUnit, double> oldDuration = mDuration;
|
||||
double oldDuration = mDuration;
|
||||
|
||||
// Use the explicit duration if we have one.
|
||||
// Otherwise use the duration mirrored from MDSM.
|
||||
if (mExplicitDuration.isSome()) {
|
||||
mDuration.emplace<double>(mExplicitDuration.ref());
|
||||
mDuration = mExplicitDuration.ref();
|
||||
} else if (mStateMachineDuration.Ref().isSome()) {
|
||||
MOZ_ASSERT(mStateMachineDuration.Ref().ref().IsValid());
|
||||
mDuration.emplace<TimeUnit>(mStateMachineDuration.Ref().ref());
|
||||
mDuration = mStateMachineDuration.Ref().ref().ToSeconds();
|
||||
}
|
||||
|
||||
LOG("New duration: %s",
|
||||
mDuration.match(DurationToTimeUnit()).ToString().get());
|
||||
if (oldDuration.is<TimeUnit>() && oldDuration.as<TimeUnit>().IsValid()) {
|
||||
LOG("Old Duration %s",
|
||||
oldDuration.match(DurationToTimeUnit()).ToString().get());
|
||||
if (mDuration == oldDuration || std::isnan(mDuration)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((oldDuration.is<double>() || oldDuration.as<TimeUnit>().IsValid())) {
|
||||
if (mDuration.match(DurationToDouble()) ==
|
||||
oldDuration.match(DurationToDouble())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LOG("Duration changed to %s",
|
||||
mDuration.match(DurationToTimeUnit()).ToString().get());
|
||||
LOG("Duration changed to %f", mDuration);
|
||||
|
||||
// See https://www.w3.org/Bugs/Public/show_bug.cgi?id=28822 for a discussion
|
||||
// of whether we should fire durationchange on explicit infinity.
|
||||
if (mFiredMetadataLoaded &&
|
||||
(!std::isinf(mDuration.match(DurationToDouble())) ||
|
||||
mExplicitDuration.isSome())) {
|
||||
(!std::isinf(mDuration) || mExplicitDuration.isSome())) {
|
||||
GetOwner()->DispatchAsyncEvent(u"durationchange"_ns);
|
||||
}
|
||||
|
||||
if (CurrentPosition().ToSeconds() > mDuration.match(DurationToDouble())) {
|
||||
Seek(mDuration.match(DurationToDouble()), SeekTarget::Accurate);
|
||||
if (CurrentPosition() > TimeUnit::FromSeconds(mDuration)) {
|
||||
Seek(mDuration, SeekTarget::Accurate);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1239,77 +1223,29 @@ bool MediaDecoder::IsMediaSeekable() {
|
|||
return mMediaSeekable;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Returns zero, either as a TimeUnit or as a double.
|
||||
template <typename T>
|
||||
constexpr T Zero() {
|
||||
if constexpr (std::is_same<T, double>::value) {
|
||||
return 0.0;
|
||||
} else if constexpr (std::is_same<T, TimeUnit>::value) {
|
||||
return TimeUnit::Zero();
|
||||
}
|
||||
MOZ_RELEASE_ASSERT(false);
|
||||
};
|
||||
|
||||
// Returns Infinity either as a TimeUnit or as a double.
|
||||
template <typename T>
|
||||
constexpr T Infinity() {
|
||||
if constexpr (std::is_same<T, double>::value) {
|
||||
return std::numeric_limits<double>::infinity();
|
||||
} else if constexpr (std::is_same<T, TimeUnit>::value) {
|
||||
return TimeUnit::FromInfinity();
|
||||
}
|
||||
MOZ_RELEASE_ASSERT(false);
|
||||
};
|
||||
|
||||
}; // namespace
|
||||
|
||||
// This method can be made to return either TimeIntervals, that is a set of
|
||||
// interval that are delimited with TimeUnit, or TimeRanges, that is a set of
|
||||
// intervals that are delimited by seconds, as doubles.
|
||||
// seekable often depends on the duration of a media, in the very common case
|
||||
// where the seekable range is [0, duration]. When playing a MediaSource, the
|
||||
// duration of a media element can be set as an arbitrary number, that are
|
||||
// 64-bits floating point values.
|
||||
// This allows returning an interval that is [0, duration], with duration being
|
||||
// a double that cannot be represented as a TimeUnit, either because it has too
|
||||
// many significant digits, or because it's outside of the int64_t range that
|
||||
// TimeUnit internally uses.
|
||||
template <typename IntervalType>
|
||||
IntervalType MediaDecoder::GetSeekableImpl() {
|
||||
media::TimeIntervals MediaDecoder::GetSeekable() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (std::isnan(GetDuration())) {
|
||||
// We do not have a duration yet, we can't determine the seekable range.
|
||||
return IntervalType();
|
||||
return TimeIntervals();
|
||||
}
|
||||
|
||||
// We can seek in buffered range if the media is seekable. Also, we can seek
|
||||
// in unbuffered ranges if the transport level is seekable (local file or the
|
||||
// server supports range requests, etc.) or in cue-less WebMs
|
||||
if (mMediaSeekableOnlyInBufferedRanges) {
|
||||
return IntervalType(GetBuffered());
|
||||
return GetBuffered();
|
||||
}
|
||||
if (!IsMediaSeekable()) {
|
||||
return IntervalType();
|
||||
return media::TimeIntervals();
|
||||
}
|
||||
if (!IsTransportSeekable()) {
|
||||
return IntervalType(GetBuffered());
|
||||
return GetBuffered();
|
||||
}
|
||||
// Return [0, duration].
|
||||
return IntervalType(typename IntervalType::ElemType(
|
||||
Zero<typename IntervalType::InnerType>(),
|
||||
IsInfinite() ? Infinity<typename IntervalType::InnerType>()
|
||||
: mDuration.match(
|
||||
DurationToType<typename IntervalType::InnerType>())));
|
||||
}
|
||||
|
||||
media::TimeIntervals MediaDecoder::GetSeekable() {
|
||||
return GetSeekableImpl<media::TimeIntervals>();
|
||||
}
|
||||
|
||||
media::TimeRanges MediaDecoder::GetSeekableTimeRanges() {
|
||||
return GetSeekableImpl<media::TimeRanges>();
|
||||
return media::TimeIntervals(media::TimeInterval(
|
||||
TimeUnit::Zero(), IsInfinite() ? TimeUnit::FromInfinity()
|
||||
: TimeUnit::FromSeconds(GetDuration())));
|
||||
}
|
||||
|
||||
void MediaDecoder::SetFragmentEndTime(double aTime) {
|
||||
|
|
|
|||
|
|
@ -51,43 +51,6 @@ class MediaDecoderStateMachineBase;
|
|||
struct MediaPlaybackEvent;
|
||||
struct SharedDummyTrack;
|
||||
|
||||
template <typename T>
|
||||
struct DurationToType {
|
||||
double operator()(double aDouble);
|
||||
double operator()(const media::TimeUnit& aTimeUnit);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct DurationToType<double> {
|
||||
double operator()(double aDouble) { return aDouble; }
|
||||
double operator()(const media::TimeUnit& aTimeUnit) {
|
||||
if (aTimeUnit.IsValid()) {
|
||||
if (aTimeUnit.IsPosInf()) {
|
||||
return std::numeric_limits<double>::infinity();
|
||||
}
|
||||
if (aTimeUnit.IsNegInf()) {
|
||||
return -std::numeric_limits<double>::infinity();
|
||||
}
|
||||
return aTimeUnit.ToSeconds();
|
||||
}
|
||||
return std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
};
|
||||
|
||||
using DurationToDouble = DurationToType<double>;
|
||||
|
||||
template <>
|
||||
struct DurationToType<media::TimeUnit> {
|
||||
media::TimeUnit operator()(double aDouble) {
|
||||
return media::TimeUnit::FromSeconds(aDouble);
|
||||
}
|
||||
media::TimeUnit operator()(const media::TimeUnit& aTimeUnit) {
|
||||
return aTimeUnit;
|
||||
}
|
||||
};
|
||||
|
||||
using DurationToTimeUnit = DurationToType<media::TimeUnit>;
|
||||
|
||||
struct MOZ_STACK_CLASS MediaDecoderInit {
|
||||
MediaDecoderOwner* const mOwner;
|
||||
TelemetryProbesReporterOwner* const mReporterOwner;
|
||||
|
|
@ -260,14 +223,8 @@ class MediaDecoder : public DecoderDoctorLifeLogger<MediaDecoder> {
|
|||
// supports range requests, we are playing a file, etc.).
|
||||
virtual bool IsTransportSeekable() = 0;
|
||||
|
||||
// Return the time ranges that can be seeked into, in TimeUnits.
|
||||
// Return the time ranges that can be seeked into.
|
||||
virtual media::TimeIntervals GetSeekable();
|
||||
// Return the time ranges that can be seeked into, in seconds, double
|
||||
// precision.
|
||||
virtual media::TimeRanges GetSeekableTimeRanges();
|
||||
|
||||
template <typename T>
|
||||
T GetSeekableImpl();
|
||||
|
||||
// Set the end time of the media resource. When playback reaches
|
||||
// this point the media pauses. aTime is in seconds.
|
||||
|
|
@ -498,7 +455,7 @@ class MediaDecoder : public DecoderDoctorLifeLogger<MediaDecoder> {
|
|||
UniquePtr<MetadataTags> aTags,
|
||||
MediaDecoderEventVisibility aEventVisibility);
|
||||
|
||||
void SetLogicalPosition(const media::TimeUnit& aNewPosition);
|
||||
void SetLogicalPosition(double aNewPosition);
|
||||
|
||||
/******
|
||||
* The following members should be accessed with the decoder lock held.
|
||||
|
|
@ -520,10 +477,7 @@ class MediaDecoder : public DecoderDoctorLifeLogger<MediaDecoder> {
|
|||
already_AddRefed<layers::KnowsCompositor> GetCompositor();
|
||||
|
||||
// Official duration of the media resource as observed by script.
|
||||
// This can be a TimeUnit representing the exact duration found by demuxing,
|
||||
// as a TimeUnit. This can also be a duration set explicitly by script, as a
|
||||
// double.
|
||||
Variant<media::TimeUnit, double> mDuration;
|
||||
double mDuration;
|
||||
|
||||
/******
|
||||
* The following member variables can be accessed from any thread.
|
||||
|
|
@ -805,8 +759,7 @@ class MediaDecoder : public DecoderDoctorLifeLogger<MediaDecoder> {
|
|||
eSeamlessLoopingSeeking,
|
||||
eOther,
|
||||
};
|
||||
PositionUpdate GetPositionUpdateReason(double aPrevPos,
|
||||
const media::TimeUnit& aCurPos) const;
|
||||
PositionUpdate GetPositionUpdateReason(double aPrevPos, double aCurPos) const;
|
||||
|
||||
// Notify owner when the audible state changed
|
||||
void NotifyAudibleStateChanged();
|
||||
|
|
|
|||
|
|
@ -3194,11 +3194,8 @@ void MediaDecoderStateMachine::LoopingDecodingState::HandleError(
|
|||
void MediaDecoderStateMachine::SeekingState::SeekCompleted() {
|
||||
const auto newCurrentTime = CalculateNewCurrentTime();
|
||||
|
||||
if ((newCurrentTime == mMaster->Duration() ||
|
||||
newCurrentTime.EqualsAtLowestResolution(
|
||||
mMaster->Duration().ToBase(USECS_PER_S))) &&
|
||||
!mMaster->mIsLiveStream) {
|
||||
SLOG("Seek completed, seeked to end: %s", newCurrentTime.ToString().get());
|
||||
if (newCurrentTime == mMaster->Duration() && !mMaster->mIsLiveStream) {
|
||||
// Seeked to end of media. Explicitly finish the queues so DECODING
|
||||
// will transition to COMPLETED immediately. Note we don't do
|
||||
// this when playing a live stream, since the end of media will advance
|
||||
// once we download more data!
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ class MediaDecoderStateMachine
|
|||
using TrackSet = MediaFormatReader::TrackSet;
|
||||
|
||||
public:
|
||||
using FrameID = mozilla::layers::ImageContainer::FrameID;
|
||||
typedef mozilla::layers::ImageContainer::FrameID FrameID;
|
||||
MediaDecoderStateMachine(MediaDecoder* aDecoder, MediaFormatReader* aReader);
|
||||
|
||||
nsresult Init(MediaDecoder* aDecoder) override;
|
||||
|
|
|
|||
|
|
@ -70,17 +70,6 @@ struct AacCodecSpecificData {
|
|||
*mDecoderConfigDescriptorBinaryBlob ==
|
||||
*rhs.mDecoderConfigDescriptorBinaryBlob;
|
||||
}
|
||||
// An explanation for the necessity of handling the encoder delay and the
|
||||
// padding is available here:
|
||||
// https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFAppenG/QTFFAppenG.html
|
||||
|
||||
// The number of frames that should be skipped from the beginning of the
|
||||
// decoded stream.
|
||||
uint32_t mEncoderDelayFrames{0};
|
||||
|
||||
// The total number of frames of the media, that is, excluding the encoder
|
||||
// delay and the padding of the last packet, that must be discarded.
|
||||
uint64_t mMediaFrameCount{0};
|
||||
|
||||
// The bytes of the ES_Descriptor field parsed out of esds box. We store
|
||||
// this as a blob as some decoders want this.
|
||||
|
|
@ -275,7 +264,6 @@ class TrackInfo {
|
|||
nsCString mMimeType;
|
||||
media::TimeUnit mDuration;
|
||||
media::TimeUnit mMediaTime;
|
||||
uint32_t mTimeScale = 0;
|
||||
CryptoTrack mCrypto;
|
||||
|
||||
CopyableTArray<MetadataTag> mTags;
|
||||
|
|
|
|||
|
|
@ -1,440 +0,0 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "TimeUnits.h"
|
||||
#include "Intervals.h"
|
||||
#include "mozilla/CheckedInt.h"
|
||||
#include "mozilla/FloatingPoint.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/IntegerPrintfMacros.h"
|
||||
#include "nsPrintfCString.h"
|
||||
#include "nsStringFwd.h"
|
||||
|
||||
namespace mozilla::media {
|
||||
class TimeIntervals;
|
||||
} // namespace mozilla::media
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace media {
|
||||
|
||||
TimeUnit TimeUnit::FromSeconds(double aValue, int64_t aBase) {
|
||||
MOZ_ASSERT(!std::isnan(aValue));
|
||||
MOZ_ASSERT(aBase > 0);
|
||||
|
||||
if (std::isinf(aValue)) {
|
||||
return aValue > 0 ? FromInfinity() : FromNegativeInfinity();
|
||||
}
|
||||
// Warn that a particular value won't be able to be roundtrip at the same
|
||||
// base -- we can keep this for some time until we're confident this is
|
||||
// stable.
|
||||
double inBase = aValue * static_cast<double>(aBase);
|
||||
if (inBase > static_cast<double>(std::numeric_limits<int64_t>::max())) {
|
||||
NS_WARNING(nsPrintfCString("Warning: base %" PRId64
|
||||
" is too high to represent %lfs accurately: "
|
||||
"overflows int64_t",
|
||||
aBase, aValue)
|
||||
.get());
|
||||
MOZ_DIAGNOSTIC_ASSERT(false);
|
||||
}
|
||||
if (inBase > std::pow(2, std::numeric_limits<double>::digits) - 1) {
|
||||
NS_WARNING(
|
||||
nsPrintfCString("Warning base %" PRId64
|
||||
" is too high to represent %lfs accurately: greater "
|
||||
"than the max integer representable in a double.",
|
||||
aBase, aValue)
|
||||
.get());
|
||||
MOZ_DIAGNOSTIC_ASSERT(false);
|
||||
}
|
||||
return TimeUnit(static_cast<int64_t>(inBase), aBase);
|
||||
}
|
||||
|
||||
TimeUnit TimeUnit::FromInfinity() { return TimeUnit(INT64_MAX); }
|
||||
|
||||
TimeUnit TimeUnit::FromNegativeInfinity() { return TimeUnit(INT64_MIN); }
|
||||
|
||||
TimeUnit TimeUnit::FromTimeDuration(const TimeDuration& aDuration) {
|
||||
// This could be made to choose the base
|
||||
return TimeUnit(AssertedCast<int64_t>(aDuration.ToMicroseconds()),
|
||||
USECS_PER_S);
|
||||
}
|
||||
|
||||
TimeUnit TimeUnit::Invalid() {
|
||||
TimeUnit ret;
|
||||
ret.mTicks = CheckedInt64(INT64_MAX);
|
||||
// Force an overflow to render the CheckedInt invalid.
|
||||
ret.mTicks += 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int64_t TimeUnit::ToMilliseconds() const { return ToCommonUnit(MSECS_PER_S); }
|
||||
|
||||
int64_t TimeUnit::ToMicroseconds() const { return ToCommonUnit(USECS_PER_S); }
|
||||
|
||||
int64_t TimeUnit::ToNanoseconds() const { return ToCommonUnit(NSECS_PER_S); }
|
||||
|
||||
int64_t TimeUnit::ToTicksAtRate(int64_t aRate) const {
|
||||
// Common case
|
||||
if (aRate == mBase) {
|
||||
return mTicks.value();
|
||||
}
|
||||
// Approximation
|
||||
return mTicks.value() * aRate / mBase;
|
||||
}
|
||||
|
||||
double TimeUnit::ToSeconds() const {
|
||||
if (IsPosInf()) {
|
||||
return PositiveInfinity<double>();
|
||||
}
|
||||
if (IsNegInf()) {
|
||||
return NegativeInfinity<double>();
|
||||
}
|
||||
return static_cast<double>(mTicks.value()) / static_cast<double>(mBase);
|
||||
}
|
||||
|
||||
nsCString TimeUnit::ToString() const {
|
||||
nsCString dump;
|
||||
if (mTicks.isValid()) {
|
||||
dump += nsPrintfCString("{%" PRId64 ",%" PRId64 "}", mTicks.value(), mBase);
|
||||
} else {
|
||||
dump += nsLiteralCString("{invalid}"_ns);
|
||||
}
|
||||
return dump;
|
||||
}
|
||||
|
||||
TimeDuration TimeUnit::ToTimeDuration() const {
|
||||
return TimeDuration::FromSeconds(ToSeconds());
|
||||
}
|
||||
|
||||
bool TimeUnit::IsInfinite() const { return IsPosInf() || IsNegInf(); }
|
||||
|
||||
bool TimeUnit::IsPositive() const { return mTicks.value() > 0; }
|
||||
|
||||
bool TimeUnit::IsPositiveOrZero() const { return mTicks.value() >= 0; }
|
||||
|
||||
bool TimeUnit::IsZero() const { return mTicks.value() == 0; }
|
||||
|
||||
bool TimeUnit::IsNegative() const { return mTicks.value() < 0; }
|
||||
|
||||
// Returns true if the fractions are equal when converted to the smallest
|
||||
// base.
|
||||
bool TimeUnit::EqualsAtLowestResolution(const TimeUnit& aOther) const {
|
||||
MOZ_ASSERT(IsValid() && aOther.IsValid());
|
||||
if (aOther.mBase == mBase) {
|
||||
return mTicks == aOther.mTicks;
|
||||
}
|
||||
if (mBase > aOther.mBase) {
|
||||
TimeUnit thisInBase = ToBase(aOther.mBase);
|
||||
return thisInBase.mTicks == aOther.mTicks;
|
||||
}
|
||||
TimeUnit otherInBase = aOther.ToBase(mBase);
|
||||
return otherInBase.mTicks == mTicks;
|
||||
}
|
||||
|
||||
// Strict equality -- the fractions must be exactly equal
|
||||
bool TimeUnit::operator==(const TimeUnit& aOther) const {
|
||||
MOZ_ASSERT(IsValid() && aOther.IsValid());
|
||||
if (aOther.mBase == mBase) {
|
||||
return mTicks == aOther.mTicks;
|
||||
}
|
||||
// debatable mathematically
|
||||
if ((IsPosInf() && aOther.IsPosInf()) || (IsNegInf() && aOther.IsNegInf())) {
|
||||
return true;
|
||||
}
|
||||
if ((IsPosInf() && !aOther.IsPosInf()) ||
|
||||
(IsNegInf() && !aOther.IsNegInf())) {
|
||||
return false;
|
||||
}
|
||||
CheckedInt<int64_t> lhs = mTicks * aOther.mBase;
|
||||
CheckedInt<int64_t> rhs = aOther.mTicks * mBase;
|
||||
if (lhs.isValid() && rhs.isValid()) {
|
||||
return lhs == rhs;
|
||||
}
|
||||
// Reduce the fractions and try again
|
||||
const TimeUnit a = Reduced();
|
||||
const TimeUnit b = Reduced();
|
||||
lhs = a.mTicks * b.mBase;
|
||||
rhs = b.mTicks * a.mBase;
|
||||
|
||||
if (lhs.isValid() && rhs.isValid()) {
|
||||
return lhs.value() == rhs.value();
|
||||
}
|
||||
// last ditch, convert the reduced fractions to doubles
|
||||
double lhsFloating =
|
||||
static_cast<double>(a.mTicks.value()) * static_cast<double>(a.mBase);
|
||||
double rhsFloating =
|
||||
static_cast<double>(b.mTicks.value()) * static_cast<double>(b.mBase);
|
||||
|
||||
return lhsFloating == rhsFloating;
|
||||
}
|
||||
bool TimeUnit::operator!=(const TimeUnit& aOther) const {
|
||||
MOZ_ASSERT(IsValid() && aOther.IsValid());
|
||||
return !(aOther == *this);
|
||||
}
|
||||
bool TimeUnit::operator>=(const TimeUnit& aOther) const {
|
||||
MOZ_ASSERT(IsValid() && aOther.IsValid());
|
||||
if (aOther.mBase == mBase) {
|
||||
return mTicks.value() >= aOther.mTicks.value();
|
||||
}
|
||||
if ((!IsPosInf() && aOther.IsPosInf()) ||
|
||||
(IsNegInf() && !aOther.IsNegInf())) {
|
||||
return false;
|
||||
}
|
||||
if ((IsPosInf() && !aOther.IsPosInf()) ||
|
||||
(!IsNegInf() && aOther.IsNegInf())) {
|
||||
return true;
|
||||
}
|
||||
CheckedInt<int64_t> lhs = mTicks * aOther.mBase;
|
||||
CheckedInt<int64_t> rhs = aOther.mTicks * mBase;
|
||||
if (lhs.isValid() && rhs.isValid()) {
|
||||
return lhs.value() >= rhs.value();
|
||||
}
|
||||
// Reduce the fractions and try again
|
||||
const TimeUnit a = Reduced();
|
||||
const TimeUnit b = aOther.Reduced();
|
||||
lhs = a.mTicks * b.mBase;
|
||||
rhs = b.mTicks * a.mBase;
|
||||
|
||||
if (lhs.isValid() && rhs.isValid()) {
|
||||
return lhs.value() >= rhs.value();
|
||||
}
|
||||
// last ditch, convert the reduced fractions to doubles
|
||||
double lhsFloating =
|
||||
static_cast<double>(a.mTicks.value()) * static_cast<double>(a.mBase);
|
||||
double rhsFloating =
|
||||
static_cast<double>(b.mTicks.value()) * static_cast<double>(b.mBase);
|
||||
|
||||
return lhsFloating >= rhsFloating;
|
||||
}
|
||||
bool TimeUnit::operator>(const TimeUnit& aOther) const {
|
||||
return !(*this <= aOther);
|
||||
}
|
||||
bool TimeUnit::operator<=(const TimeUnit& aOther) const {
|
||||
MOZ_ASSERT(IsValid() && aOther.IsValid());
|
||||
if (aOther.mBase == mBase) {
|
||||
return mTicks.value() <= aOther.mTicks.value();
|
||||
}
|
||||
if ((!IsPosInf() && aOther.IsPosInf()) ||
|
||||
(IsNegInf() && !aOther.IsNegInf())) {
|
||||
return true;
|
||||
}
|
||||
if ((IsPosInf() && !aOther.IsPosInf()) ||
|
||||
(!IsNegInf() && aOther.IsNegInf())) {
|
||||
return false;
|
||||
}
|
||||
CheckedInt<int64_t> lhs = mTicks * aOther.mBase;
|
||||
CheckedInt<int64_t> rhs = aOther.mTicks * mBase;
|
||||
if (lhs.isValid() && rhs.isValid()) {
|
||||
return lhs.value() <= rhs.value();
|
||||
}
|
||||
// Reduce the fractions and try again
|
||||
const TimeUnit a = Reduced();
|
||||
const TimeUnit b = aOther.Reduced();
|
||||
lhs = a.mTicks * b.mBase;
|
||||
rhs = b.mTicks * a.mBase;
|
||||
if (lhs.isValid() && rhs.isValid()) {
|
||||
return lhs.value() <= rhs.value();
|
||||
}
|
||||
// last ditch, convert the reduced fractions to doubles
|
||||
double lhsFloating =
|
||||
static_cast<double>(a.mTicks.value()) * static_cast<double>(a.mBase);
|
||||
double rhsFloating =
|
||||
static_cast<double>(b.mTicks.value()) * static_cast<double>(b.mBase);
|
||||
|
||||
return lhsFloating <= rhsFloating;
|
||||
}
|
||||
bool TimeUnit::operator<(const TimeUnit& aOther) const {
|
||||
return !(*this >= aOther);
|
||||
}
|
||||
|
||||
TimeUnit TimeUnit::operator%(const TimeUnit& aOther) const {
|
||||
MOZ_ASSERT(IsValid() && aOther.IsValid());
|
||||
if (aOther.mBase == mBase) {
|
||||
return TimeUnit(mTicks % aOther.mTicks, mBase);
|
||||
}
|
||||
// This path can be made better if need be.
|
||||
double a = ToSeconds();
|
||||
double b = aOther.ToSeconds();
|
||||
return TimeUnit::FromSeconds(fmod(a, b), mBase);
|
||||
}
|
||||
|
||||
TimeUnit TimeUnit::operator+(const TimeUnit& aOther) const {
|
||||
if (IsInfinite() || aOther.IsInfinite()) {
|
||||
// When adding at least one infinite value, the result is either
|
||||
// +/-Inf, or NaN. So do the calculation in floating point for
|
||||
// simplicity.
|
||||
double result = ToSeconds() + aOther.ToSeconds();
|
||||
return std::isnan(result) ? TimeUnit::Invalid() : FromSeconds(result);
|
||||
}
|
||||
if (aOther.mBase == mBase) {
|
||||
return TimeUnit(mTicks + aOther.mTicks, mBase);
|
||||
}
|
||||
if (aOther.IsZero()) {
|
||||
return *this;
|
||||
}
|
||||
if (IsZero()) {
|
||||
return aOther;
|
||||
}
|
||||
|
||||
double error;
|
||||
TimeUnit inBase = aOther.ToBase(mBase, error);
|
||||
if (error == 0.0) {
|
||||
return *this + inBase;
|
||||
}
|
||||
|
||||
// Last ditch: not exact
|
||||
double a = ToSeconds();
|
||||
double b = aOther.ToSeconds();
|
||||
return TimeUnit::FromSeconds(a + b, mBase);
|
||||
}
|
||||
|
||||
TimeUnit TimeUnit::operator-(const TimeUnit& aOther) const {
|
||||
if (IsInfinite() || aOther.IsInfinite()) {
|
||||
// When subtracting at least one infinite value, the result is either
|
||||
// +/-Inf, or NaN. So do the calculation in floating point for
|
||||
// simplicity.
|
||||
double result = ToSeconds() - aOther.ToSeconds();
|
||||
return std::isnan(result) ? TimeUnit::Invalid() : FromSeconds(result);
|
||||
}
|
||||
if (aOther.mBase == mBase) {
|
||||
return TimeUnit(mTicks - aOther.mTicks, mBase);
|
||||
}
|
||||
if (aOther.IsZero()) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
if (IsZero()) {
|
||||
return TimeUnit(-aOther.mTicks, aOther.mBase);
|
||||
}
|
||||
|
||||
double error = 0.0;
|
||||
TimeUnit inBase = aOther.ToBase(mBase, error);
|
||||
if (error == 0) {
|
||||
return *this - inBase;
|
||||
}
|
||||
|
||||
// Last ditch: not exact
|
||||
double a = ToSeconds();
|
||||
double b = aOther.ToSeconds();
|
||||
return TimeUnit::FromSeconds(a - b, mBase);
|
||||
}
|
||||
TimeUnit& TimeUnit::operator+=(const TimeUnit& aOther) {
|
||||
if (aOther.mBase == mBase) {
|
||||
mTicks += aOther.mTicks;
|
||||
return *this;
|
||||
}
|
||||
*this = *this + aOther;
|
||||
return *this;
|
||||
}
|
||||
TimeUnit& TimeUnit::operator-=(const TimeUnit& aOther) {
|
||||
if (aOther.mBase == mBase) {
|
||||
mTicks -= aOther.mTicks;
|
||||
return *this;
|
||||
}
|
||||
*this = *this - aOther;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TimeUnit TimeUnit::MultDouble(double aVal) const {
|
||||
double multiplied = AssertedCast<double>(mTicks.value()) * aVal;
|
||||
// Check is the result of the multiplication can be represented exactly as
|
||||
// an integer, in a double.
|
||||
if (multiplied > std::pow(2, std::numeric_limits<double>::digits) - 1) {
|
||||
printf_stderr("TimeUnit tick count after multiplication %" PRId64
|
||||
" * %lf is too"
|
||||
" high for the result to be exact",
|
||||
mTicks.value(), aVal);
|
||||
MOZ_CRASH();
|
||||
}
|
||||
// static_cast is ok, the magnitude of the number has been checked just above.
|
||||
return TimeUnit(static_cast<int64_t>(multiplied), mBase);
|
||||
}
|
||||
TimeUnit TimeUnit::ToBase(int64_t aTargetBase) const {
|
||||
double dummy = 0.0;
|
||||
return ToBase(aTargetBase, dummy);
|
||||
}
|
||||
|
||||
TimeUnit TimeUnit::ToBase(const TimeUnit& aTimeUnit) const {
|
||||
double dummy = 0.0;
|
||||
return ToBase(aTimeUnit, dummy);
|
||||
}
|
||||
|
||||
// Allow returning the same value, in a base that matches another TimeUnit.
|
||||
TimeUnit TimeUnit::ToBase(const TimeUnit& aTimeUnit, double& aOutError) const {
|
||||
int64_t targetBase = aTimeUnit.mBase;
|
||||
return ToBase(targetBase, aOutError);
|
||||
}
|
||||
|
||||
TimeUnit TimeUnit::ToBase(int64_t aTargetBase, double& aOutError) const {
|
||||
aOutError = 0.0;
|
||||
CheckedInt<int64_t> ticks = mTicks * aTargetBase;
|
||||
if (ticks.isValid()) {
|
||||
imaxdiv_t rv = imaxdiv(ticks.value(), mBase);
|
||||
if (!rv.rem) {
|
||||
return TimeUnit(rv.quot, aTargetBase);
|
||||
}
|
||||
}
|
||||
double approx = static_cast<double>(mTicks.value()) *
|
||||
static_cast<double>(aTargetBase) / static_cast<double>(mBase);
|
||||
double integer;
|
||||
aOutError = modf(approx, &integer);
|
||||
return TimeUnit(AssertedCast<int64_t>(approx), aTargetBase);
|
||||
}
|
||||
|
||||
bool TimeUnit::IsValid() const { return mTicks.isValid(); }
|
||||
|
||||
bool TimeUnit::IsPosInf() const {
|
||||
return mTicks.isValid() && mTicks.value() == INT64_MAX;
|
||||
}
|
||||
bool TimeUnit::IsNegInf() const {
|
||||
return mTicks.isValid() && mTicks.value() == INT64_MIN;
|
||||
}
|
||||
|
||||
int64_t TimeUnit::ToCommonUnit(int64_t aRatio) const {
|
||||
CheckedInt<int64_t> rv = mTicks;
|
||||
// Avoid the risk overflowing in common cases, e.g. converting a TimeUnit
|
||||
// with a base of 1e9 back to nanoseconds.
|
||||
if (mBase == aRatio) {
|
||||
return rv.value();
|
||||
}
|
||||
// Avoid overflowing in other common cases, e.g. converting a TimeUnit with
|
||||
// a base of 1e9 to microseconds: the denominator is divisible by the target
|
||||
// unit so we can reorder the computation and keep the number within int64_t
|
||||
// range.
|
||||
if (aRatio < mBase && (mBase % aRatio) == 0) {
|
||||
int64_t exactDivisor = mBase / aRatio;
|
||||
rv /= exactDivisor;
|
||||
return rv.value();
|
||||
}
|
||||
rv *= aRatio;
|
||||
rv /= mBase;
|
||||
if (rv.isValid()) {
|
||||
return rv.value();
|
||||
}
|
||||
// Last ditch, perform the computation in floating point.
|
||||
double ratioFloating = AssertedCast<double>(aRatio);
|
||||
double baseFloating = AssertedCast<double>(mBase);
|
||||
double ticksFloating = static_cast<double>(mTicks.value());
|
||||
double approx = ticksFloating * (ratioFloating / baseFloating);
|
||||
return AssertedCast<int64_t>(approx);
|
||||
}
|
||||
|
||||
// Reduce a TimeUnit to the smallest possible ticks and base. This is useful
|
||||
// to comparison with big time values that can otherwise overflow.
|
||||
TimeUnit TimeUnit::Reduced() const {
|
||||
int64_t gcd = GCD(mTicks.value(), mBase);
|
||||
return TimeUnit(mTicks.value() / gcd, mBase / gcd);
|
||||
}
|
||||
|
||||
CheckedInt64 mTicks{0};
|
||||
// Default base is microseconds.
|
||||
int64_t mBase{USECS_PER_S};
|
||||
}; // namespace media
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
@ -7,7 +7,6 @@
|
|||
#ifndef TIME_UNITS_H
|
||||
#define TIME_UNITS_H
|
||||
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#include "Intervals.h"
|
||||
|
|
@ -16,7 +15,6 @@
|
|||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/IntegerPrintfMacros.h"
|
||||
#include "nsPrintfCString.h"
|
||||
|
||||
namespace mozilla::media {
|
||||
class TimeIntervals;
|
||||
|
|
@ -30,17 +28,17 @@ struct nsTArray_RelocationStrategy<mozilla::media::TimeIntervals> {
|
|||
|
||||
namespace mozilla {
|
||||
|
||||
// Number of milliseconds per second. 1e3.
|
||||
static const int64_t MSECS_PER_S = 1000;
|
||||
|
||||
// Number of microseconds per second. 1e6.
|
||||
static const int64_t USECS_PER_S = 1000000;
|
||||
|
||||
// Number of nanoseconds per second. 1e9.
|
||||
static const int64_t NSECS_PER_S = 1000000000;
|
||||
// Number of microseconds per millisecond.
|
||||
static const int64_t USECS_PER_MS = 1000;
|
||||
|
||||
namespace media {
|
||||
|
||||
// Number of nanoseconds per second. 1e9.
|
||||
static const int64_t NSECS_PER_S = 1000000000;
|
||||
|
||||
#ifndef PROCESS_DECODE_LOG
|
||||
# define PROCESS_DECODE_LOG(sample) \
|
||||
MOZ_LOG(sPDMLog, mozilla::LogLevel::Verbose, \
|
||||
|
|
@ -51,119 +49,156 @@ namespace media {
|
|||
(sample)->mTimecode.ToMicroseconds()))
|
||||
#endif // PROCESS_DECODE_LOG
|
||||
|
||||
// TimeUnit is a class that represents a time value, that can be negative or
|
||||
// positive.
|
||||
//
|
||||
// Internally, it works by storing a numerator (the tick numbers), that uses
|
||||
// checked arithmetics, and a denominator (the base), that is a regular integer
|
||||
// on which arithmetics is never performed, and is only set at construction, or
|
||||
// replaced.
|
||||
//
|
||||
// Dividing the tick count by the base always yields a value in seconds,
|
||||
// but it's very useful to have a base that is dependant on the context: it can
|
||||
// be the sample-rate of an audio stream, the time base of an mp4, that's often
|
||||
// 90000 because it's divisible by 24, 25 and 30.
|
||||
//
|
||||
// Keeping time like this allows performing calculations on time values with
|
||||
// maximum precision, without having to have to care about rounding errors or
|
||||
// precision loss.
|
||||
//
|
||||
// If not specified, the base is 1e6, representing microseconds, for historical
|
||||
// reasons. Users can gradually move to more precise representations when
|
||||
// needed.
|
||||
//
|
||||
// INT64_MAX has the special meaning of being +∞, and INT64_MIN means -∞. Any
|
||||
// other value corresponds to a valid time.
|
||||
//
|
||||
// If an overflow or other problem occurs, the underlying CheckedInt<int64_t> is
|
||||
// invalid and a crash is triggered.
|
||||
// TimeUnit at present uses a CheckedInt64 as storage.
|
||||
// INT64_MAX has the special meaning of being +oo.
|
||||
class TimeUnit final {
|
||||
public:
|
||||
constexpr TimeUnit(CheckedInt64 aTicks, int64_t aBase)
|
||||
: mTicks(aTicks), mBase(aBase) {
|
||||
MOZ_RELEASE_ASSERT(mBase > 0);
|
||||
static TimeUnit FromSeconds(double aValue) {
|
||||
MOZ_ASSERT(!std::isnan(aValue));
|
||||
|
||||
if (std::isinf(aValue)) {
|
||||
return aValue > 0 ? FromInfinity() : FromNegativeInfinity();
|
||||
}
|
||||
// Due to internal double representation, this
|
||||
// operation is not commutative, do not attempt to simplify.
|
||||
double halfUsec = .0000005;
|
||||
double val =
|
||||
(aValue <= 0 ? aValue - halfUsec : aValue + halfUsec) * USECS_PER_S;
|
||||
if (val >= double(INT64_MAX)) {
|
||||
return FromMicroseconds(INT64_MAX);
|
||||
}
|
||||
if (val <= double(INT64_MIN)) {
|
||||
return FromMicroseconds(INT64_MIN);
|
||||
}
|
||||
return FromMicroseconds(int64_t(val));
|
||||
}
|
||||
|
||||
explicit constexpr TimeUnit(CheckedInt64 aTicks)
|
||||
: mTicks(aTicks), mBase(USECS_PER_S) {}
|
||||
|
||||
// Return the maximum number of ticks that a TimeUnit can contain.
|
||||
static constexpr int64_t MaxTicks() {
|
||||
return std::numeric_limits<int64_t>::max() - 1;
|
||||
}
|
||||
|
||||
// This is only precise up to a point, which is aValue * aBase <= 2^53 - 1
|
||||
static TimeUnit FromSeconds(double aValue, int64_t aBase = USECS_PER_S);
|
||||
static constexpr TimeUnit FromMicroseconds(int64_t aValue) {
|
||||
return TimeUnit(aValue, USECS_PER_S);
|
||||
return TimeUnit(aValue);
|
||||
}
|
||||
|
||||
static constexpr TimeUnit FromNanoseconds(int64_t aValue) {
|
||||
return TimeUnit(aValue / 1000);
|
||||
}
|
||||
|
||||
static constexpr TimeUnit FromInfinity() { return TimeUnit(INT64_MAX); }
|
||||
|
||||
static constexpr TimeUnit FromNegativeInfinity() {
|
||||
return TimeUnit(INT64_MIN);
|
||||
}
|
||||
|
||||
static TimeUnit FromTimeDuration(const TimeDuration& aDuration) {
|
||||
return FromSeconds(aDuration.ToSeconds());
|
||||
}
|
||||
|
||||
static constexpr TimeUnit Zero() { return TimeUnit(0); }
|
||||
|
||||
static TimeUnit Invalid() {
|
||||
TimeUnit ret;
|
||||
ret.mValue = CheckedInt64(INT64_MAX);
|
||||
// Force an overflow to render the CheckedInt invalid.
|
||||
ret.mValue += 1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int64_t ToMicroseconds() const { return mValue.value(); }
|
||||
|
||||
int64_t ToNanoseconds() const { return mValue.value() * 1000; }
|
||||
|
||||
double ToSeconds() const {
|
||||
if (IsPosInf()) {
|
||||
return PositiveInfinity<double>();
|
||||
}
|
||||
if (IsNegInf()) {
|
||||
return NegativeInfinity<double>();
|
||||
}
|
||||
return double(mValue.value()) / USECS_PER_S;
|
||||
}
|
||||
|
||||
TimeDuration ToTimeDuration() const {
|
||||
return TimeDuration::FromMicroseconds(mValue.value());
|
||||
}
|
||||
|
||||
bool IsInfinite() const { return IsPosInf() || IsNegInf(); }
|
||||
|
||||
bool IsPositive() const { return mValue.value() > 0; }
|
||||
|
||||
bool IsNegative() const { return mValue.value() < 0; }
|
||||
|
||||
bool operator==(const TimeUnit& aOther) const {
|
||||
MOZ_ASSERT(IsValid() && aOther.IsValid());
|
||||
return mValue.value() == aOther.mValue.value();
|
||||
}
|
||||
bool operator!=(const TimeUnit& aOther) const {
|
||||
MOZ_ASSERT(IsValid() && aOther.IsValid());
|
||||
return mValue.value() != aOther.mValue.value();
|
||||
}
|
||||
bool operator>=(const TimeUnit& aOther) const {
|
||||
MOZ_ASSERT(IsValid() && aOther.IsValid());
|
||||
return mValue.value() >= aOther.mValue.value();
|
||||
}
|
||||
bool operator>(const TimeUnit& aOther) const { return !(*this <= aOther); }
|
||||
bool operator<=(const TimeUnit& aOther) const {
|
||||
MOZ_ASSERT(IsValid() && aOther.IsValid());
|
||||
return mValue.value() <= aOther.mValue.value();
|
||||
}
|
||||
bool operator<(const TimeUnit& aOther) const { return !(*this >= aOther); }
|
||||
TimeUnit operator%(const TimeUnit& aOther) const {
|
||||
MOZ_ASSERT(IsValid() && aOther.IsValid());
|
||||
return TimeUnit(mValue % aOther.mValue);
|
||||
}
|
||||
|
||||
TimeUnit operator+(const TimeUnit& aOther) const {
|
||||
if (IsInfinite() || aOther.IsInfinite()) {
|
||||
// When adding at least one infinite value, the result is either
|
||||
// +/-Inf, or NaN. So do the calculation in floating point for
|
||||
// simplicity.
|
||||
double result = ToSeconds() + aOther.ToSeconds();
|
||||
return std::isnan(result) ? TimeUnit::Invalid() : FromSeconds(result);
|
||||
}
|
||||
return TimeUnit(mValue + aOther.mValue);
|
||||
}
|
||||
|
||||
TimeUnit operator-(const TimeUnit& aOther) const {
|
||||
if (IsInfinite() || aOther.IsInfinite()) {
|
||||
// When subtracting at least one infinite value, the result is either
|
||||
// +/-Inf, or NaN. So do the calculation in floating point for
|
||||
// simplicity.
|
||||
double result = ToSeconds() - aOther.ToSeconds();
|
||||
return std::isnan(result) ? TimeUnit::Invalid() : FromSeconds(result);
|
||||
}
|
||||
MOZ_ASSERT(!IsInfinite() && !aOther.IsInfinite());
|
||||
return TimeUnit(mValue - aOther.mValue);
|
||||
}
|
||||
TimeUnit& operator+=(const TimeUnit& aOther) {
|
||||
*this = *this + aOther;
|
||||
return *this;
|
||||
}
|
||||
TimeUnit& operator-=(const TimeUnit& aOther) {
|
||||
*this = *this - aOther;
|
||||
return *this;
|
||||
}
|
||||
static TimeUnit FromHns(int64_t aValue, int64_t aBase) {
|
||||
return TimeUnit::FromNanoseconds(aValue * 100).ToBase(aBase);
|
||||
}
|
||||
static constexpr TimeUnit FromNanoseconds(int64_t aValue) {
|
||||
return TimeUnit(aValue, NSECS_PER_S);
|
||||
}
|
||||
static TimeUnit FromInfinity();
|
||||
static TimeUnit FromNegativeInfinity();
|
||||
static TimeUnit FromTimeDuration(const TimeDuration& aDuration);
|
||||
static constexpr TimeUnit Zero(int64_t aBase = USECS_PER_S) {
|
||||
return TimeUnit(0, aBase);
|
||||
}
|
||||
static constexpr TimeUnit Zero(const TimeUnit& aOther) {
|
||||
return TimeUnit(0, aOther.mBase);
|
||||
}
|
||||
static TimeUnit Invalid();
|
||||
int64_t ToMilliseconds() const;
|
||||
int64_t ToMicroseconds() const;
|
||||
int64_t ToNanoseconds() const;
|
||||
int64_t ToTicksAtRate(int64_t aRate) const;
|
||||
double ToSeconds() const;
|
||||
nsCString ToString() const;
|
||||
TimeDuration ToTimeDuration() const;
|
||||
bool IsInfinite() const;
|
||||
bool IsPositive() const;
|
||||
bool IsPositiveOrZero() const;
|
||||
bool IsZero() const;
|
||||
bool IsNegative() const;
|
||||
|
||||
// Returns true if the fractions are equal when converted to the smallest
|
||||
// base.
|
||||
bool EqualsAtLowestResolution(const TimeUnit& aOther) const;
|
||||
// Strict equality -- the fractions must be exactly equal
|
||||
bool operator==(const TimeUnit& aOther) const;
|
||||
bool operator!=(const TimeUnit& aOther) const;
|
||||
bool operator>=(const TimeUnit& aOther) const;
|
||||
bool operator>(const TimeUnit& aOther) const;
|
||||
bool operator<=(const TimeUnit& aOther) const;
|
||||
bool operator<(const TimeUnit& aOther) const;
|
||||
TimeUnit operator%(const TimeUnit& aOther) const;
|
||||
TimeUnit operator+(const TimeUnit& aOther) const;
|
||||
TimeUnit operator-(const TimeUnit& aOther) const;
|
||||
TimeUnit& operator+=(const TimeUnit& aOther);
|
||||
TimeUnit& operator-=(const TimeUnit& aOther);
|
||||
template <typename T>
|
||||
TimeUnit operator*(T aVal) const {
|
||||
// See bug 853398 for the reason to block double multiplier.
|
||||
// If required, use MultDouble below and with caution.
|
||||
static_assert(std::is_integral_v<T>, "Must be an integral type");
|
||||
return TimeUnit(mTicks * aVal, mBase);
|
||||
return TimeUnit(mValue * aVal);
|
||||
}
|
||||
TimeUnit MultDouble(double aVal) const {
|
||||
return TimeUnit::FromSeconds(ToSeconds() * aVal);
|
||||
}
|
||||
TimeUnit MultDouble(double aVal) const;
|
||||
friend TimeUnit operator/(const TimeUnit& aUnit, int64_t aVal) {
|
||||
MOZ_DIAGNOSTIC_ASSERT(0 <= aVal && aVal <= UINT32_MAX);
|
||||
return TimeUnit(aUnit.mTicks / aVal, aUnit.mBase);
|
||||
return TimeUnit(aUnit.mValue / aVal);
|
||||
}
|
||||
friend TimeUnit operator%(const TimeUnit& aUnit, int64_t aVal) {
|
||||
MOZ_DIAGNOSTIC_ASSERT(0 <= aVal && aVal <= UINT32_MAX);
|
||||
return TimeUnit(aUnit.mTicks % aVal, aUnit.mBase);
|
||||
return TimeUnit(aUnit.mValue % aVal);
|
||||
}
|
||||
|
||||
TimeUnit ToBase(int64_t aTargetBase) const;
|
||||
TimeUnit ToBase(const TimeUnit& aTimeUnit) const;
|
||||
// Allow returning the same value, in a base that matches another TimeUnit.
|
||||
TimeUnit ToBase(const TimeUnit& aTimeUnit, double& aOutError) const;
|
||||
TimeUnit ToBase(int64_t aTargetBase, double& aOutError) const;
|
||||
bool IsValid() const;
|
||||
bool IsValid() const { return mValue.isValid(); }
|
||||
|
||||
constexpr TimeUnit() = default;
|
||||
|
||||
|
|
@ -171,34 +206,28 @@ class TimeUnit final {
|
|||
|
||||
TimeUnit& operator=(const TimeUnit&) = default;
|
||||
|
||||
bool IsPosInf() const;
|
||||
bool IsNegInf() const;
|
||||
bool IsPosInf() const {
|
||||
return mValue.isValid() && mValue.value() == INT64_MAX;
|
||||
}
|
||||
bool IsNegInf() const {
|
||||
return mValue.isValid() && mValue.value() == INT64_MIN;
|
||||
}
|
||||
|
||||
// Allow serializing a TimeUnit via IPC
|
||||
friend IPC::ParamTraits<mozilla::media::TimeUnit>;
|
||||
|
||||
#ifndef VISIBLE_TIMEUNIT_INTERNALS
|
||||
private:
|
||||
#endif
|
||||
int64_t ToCommonUnit(int64_t aRatio) const;
|
||||
// Reduce a TimeUnit to the smallest possible ticks and base. This is useful
|
||||
// to comparison with big time values that can otherwise overflow.
|
||||
TimeUnit Reduced() const;
|
||||
explicit constexpr TimeUnit(CheckedInt64 aMicroseconds)
|
||||
: mValue(aMicroseconds) {}
|
||||
|
||||
CheckedInt64 mTicks{0};
|
||||
// Default base is microseconds.
|
||||
int64_t mBase{USECS_PER_S};
|
||||
// Our internal representation is in microseconds.
|
||||
CheckedInt64 mValue{0};
|
||||
};
|
||||
|
||||
using NullableTimeUnit = Maybe<TimeUnit>;
|
||||
typedef Maybe<TimeUnit> NullableTimeUnit;
|
||||
|
||||
using TimeInterval = Interval<TimeUnit>;
|
||||
typedef Interval<TimeUnit> TimeInterval;
|
||||
|
||||
// A set of intervals, containing TimeUnit.
|
||||
class TimeIntervals : public IntervalSet<TimeUnit> {
|
||||
public:
|
||||
using BaseType = IntervalSet<TimeUnit>;
|
||||
using InnerType = TimeUnit;
|
||||
typedef IntervalSet<TimeUnit> BaseType;
|
||||
|
||||
// We can't use inherited constructors yet. So we have to duplicate all the
|
||||
// constructors found in IntervalSet base class.
|
||||
|
|
@ -221,63 +250,9 @@ class TimeIntervals : public IntervalSet<TimeUnit> {
|
|||
return Length() == 1 && Start(0).IsNegInf() && End(0).IsNegInf();
|
||||
}
|
||||
|
||||
// Returns the same interval, with a microsecond resolution. This is used to
|
||||
// compare TimeUnits internal to demuxers (that use a base from the container)
|
||||
// to floating point numbers in seconds from content.
|
||||
TimeIntervals ToMicrosecondResolution() const {
|
||||
TimeIntervals output;
|
||||
|
||||
for (const auto& interval : mIntervals) {
|
||||
TimeInterval reducedPrecision{interval.mStart.ToBase(USECS_PER_S),
|
||||
interval.mEnd.ToBase(USECS_PER_S),
|
||||
interval.mFuzz.ToBase(USECS_PER_S)};
|
||||
output += reducedPrecision;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
TimeIntervals() = default;
|
||||
};
|
||||
|
||||
using TimeRange = Interval<double>;
|
||||
|
||||
// A set of intervals, containing doubles that are seconds.
|
||||
class TimeRanges : public IntervalSet<double> {
|
||||
public:
|
||||
using BaseType = IntervalSet<double>;
|
||||
using InnerType = double;
|
||||
using nld = std::numeric_limits<double>;
|
||||
|
||||
// We can't use inherited constructors yet. So we have to duplicate all the
|
||||
// constructors found in IntervalSet base class.
|
||||
// all this could be later replaced with:
|
||||
// using IntervalSet<TimeUnit>::IntervalSet;
|
||||
|
||||
// MOZ_IMPLICIT as we want to enable initialization in the form:
|
||||
// TimeIntervals i = ... like we would do with IntervalSet<T> i = ...
|
||||
MOZ_IMPLICIT TimeRanges(const BaseType& aOther) : BaseType(aOther) {}
|
||||
MOZ_IMPLICIT TimeRanges(BaseType&& aOther) : BaseType(std::move(aOther)) {}
|
||||
explicit TimeRanges(const BaseType::ElemType& aOther) : BaseType(aOther) {}
|
||||
explicit TimeRanges(BaseType::ElemType&& aOther)
|
||||
: BaseType(std::move(aOther)) {}
|
||||
|
||||
static TimeRanges Invalid() {
|
||||
return TimeRanges(TimeRange(-nld::infinity(), nld::infinity()));
|
||||
}
|
||||
bool IsInvalid() const {
|
||||
return Length() == 1 && Start(0) == -nld::infinity() &&
|
||||
End(0) == nld::infinity();
|
||||
}
|
||||
// Convert from TimeUnit-based intervals to second-based TimeRanges.
|
||||
explicit TimeRanges(const TimeIntervals& aIntervals) {
|
||||
for (const auto& interval : aIntervals) {
|
||||
Add(TimeRange(interval.mStart.ToSeconds(), interval.mEnd.ToSeconds()));
|
||||
}
|
||||
}
|
||||
|
||||
TimeRanges() = default;
|
||||
};
|
||||
|
||||
} // namespace media
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
|
|||
|
|
@ -42,12 +42,6 @@ using gfx::CICP::TransferCharacteristics;
|
|||
using layers::PlanarYCbCrImage;
|
||||
using media::TimeUnit;
|
||||
|
||||
double ToMicrosecondResolution(double aSeconds) {
|
||||
double integer;
|
||||
modf(aSeconds * USECS_PER_S, &integer);
|
||||
return integer / USECS_PER_S;
|
||||
}
|
||||
|
||||
CheckedInt64 SaferMultDiv(int64_t aValue, uint64_t aMul, uint64_t aDiv) {
|
||||
if (aMul > INT64_MAX || aDiv > INT64_MAX) {
|
||||
return CheckedInt64(INT64_MAX) + 1; // Return an invalid checked int.
|
||||
|
|
@ -65,6 +59,16 @@ CheckedInt64 FramesToUsecs(int64_t aFrames, uint32_t aRate) {
|
|||
return SaferMultDiv(aFrames, USECS_PER_S, aRate);
|
||||
}
|
||||
|
||||
TimeUnit FramesToTimeUnit(int64_t aFrames, uint32_t aRate) {
|
||||
if (MOZ_UNLIKELY(!aRate)) {
|
||||
return TimeUnit::Invalid();
|
||||
}
|
||||
int64_t major = aFrames / aRate;
|
||||
int64_t remainder = aFrames % aRate;
|
||||
return TimeUnit::FromMicroseconds(major) * USECS_PER_S +
|
||||
(TimeUnit::FromMicroseconds(remainder) * USECS_PER_S) / aRate;
|
||||
}
|
||||
|
||||
// Converts from microseconds to number of audio frames, given the specified
|
||||
// audio rate.
|
||||
CheckedInt64 UsecsToFrames(int64_t aUsecs, uint32_t aRate) {
|
||||
|
|
|
|||
|
|
@ -109,7 +109,6 @@ class MediaResource;
|
|||
media::TimeIntervals GetEstimatedBufferedTimeRanges(
|
||||
mozilla::MediaResource* aStream, int64_t aDurationUsecs);
|
||||
|
||||
double ToMicrosecondResolution(double aSeconds);
|
||||
// Converts from number of audio frames (aFrames) to microseconds, given
|
||||
// the specified audio rate (aRate).
|
||||
CheckedInt64 FramesToUsecs(int64_t aFrames, uint32_t aRate);
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class EncodedFrame final {
|
|||
|
||||
// The end time of the frame in microseconds.
|
||||
media::TimeUnit GetEndTime() const {
|
||||
return mTime + media::TimeUnit(mDuration, mDurationBase);
|
||||
return mTime + FramesToTimeUnit(mDuration, mDurationBase);
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -430,7 +430,7 @@ nsresult OpusTrackEncoder::Encode(AudioSegment* aSegment) {
|
|||
|
||||
// timestamp should be the time of the first sample
|
||||
mEncodedDataQueue.Push(MakeAndAddRef<EncodedFrame>(
|
||||
media::TimeUnit(mNumOutputFrames + mLookahead, mOutputSampleRate),
|
||||
FramesToTimeUnit(mNumOutputFrames + mLookahead, mOutputSampleRate),
|
||||
static_cast<uint64_t>(framesInPCM) * kOpusSamplingRate /
|
||||
mOutputSampleRate,
|
||||
kOpusSamplingRate, EncodedFrame::OPUS_AUDIO_FRAME,
|
||||
|
|
@ -438,7 +438,7 @@ nsresult OpusTrackEncoder::Encode(AudioSegment* aSegment) {
|
|||
|
||||
mNumOutputFrames += NumOutputFramesPerPacket();
|
||||
LOG("[Opus] mOutputTimeStamp %.3f.",
|
||||
media::TimeUnit(mNumOutputFrames, mOutputSampleRate).ToSeconds());
|
||||
FramesToTimeUnit(mNumOutputFrames, mOutputSampleRate).ToSeconds());
|
||||
|
||||
if (isFinalPacket) {
|
||||
LOG("[Opus] Done encoding.");
|
||||
|
|
|
|||
|
|
@ -408,7 +408,7 @@ Result<RefPtr<EncodedFrame>, nsresult> VP8TrackEncoder::ExtractEncodedData() {
|
|||
}
|
||||
|
||||
// Convert the timestamp and duration to Usecs.
|
||||
media::TimeUnit timestamp = media::TimeUnit(pkt->data.frame.pts, mTrackRate);
|
||||
media::TimeUnit timestamp = FramesToTimeUnit(pkt->data.frame.pts, mTrackRate);
|
||||
if (!timestamp.IsValid()) {
|
||||
NS_ERROR("Microsecond timestamp overflow");
|
||||
return Err(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR);
|
||||
|
|
@ -421,7 +421,7 @@ Result<RefPtr<EncodedFrame>, nsresult> VP8TrackEncoder::ExtractEncodedData() {
|
|||
}
|
||||
|
||||
media::TimeUnit totalDuration =
|
||||
media::TimeUnit(mExtractedDuration.value(), mTrackRate);
|
||||
FramesToTimeUnit(mExtractedDuration.value(), mTrackRate);
|
||||
if (!totalDuration.IsValid()) {
|
||||
NS_ERROR("Duration overflow");
|
||||
return Err(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR);
|
||||
|
|
@ -511,12 +511,12 @@ nsresult VP8TrackEncoder::Encode(VideoSegment* aSegment) {
|
|||
// Sum duration of non-key frames and force keyframe if exceeded the
|
||||
// given keyframe interval
|
||||
if (mKeyFrameInterval > TimeDuration::FromSeconds(0)) {
|
||||
if (media::TimeUnit(mDurationSinceLastKeyframe, mTrackRate)
|
||||
if (FramesToTimeUnit(mDurationSinceLastKeyframe, mTrackRate)
|
||||
.ToTimeDuration() >= mKeyFrameInterval) {
|
||||
VP8LOG(LogLevel::Warning,
|
||||
"Reached mKeyFrameInterval without seeing a keyframe. Forcing "
|
||||
"one. time: %.2f, interval: %.2f",
|
||||
media::TimeUnit(mDurationSinceLastKeyframe, mTrackRate)
|
||||
FramesToTimeUnit(mDurationSinceLastKeyframe, mTrackRate)
|
||||
.ToSeconds(),
|
||||
mKeyFrameInterval.ToSeconds());
|
||||
mDurationSinceLastKeyframe = 0;
|
||||
|
|
@ -570,7 +570,7 @@ nsresult VP8TrackEncoder::Encode(VideoSegment* aSegment) {
|
|||
}
|
||||
|
||||
media::TimeUnit totalDuration =
|
||||
media::TimeUnit(mExtractedDuration.value(), mTrackRate);
|
||||
FramesToTimeUnit(mExtractedDuration.value(), mTrackRate);
|
||||
media::TimeUnit skippedDuration = totalDuration - mExtractedDurationUs;
|
||||
mExtractedDurationUs = totalDuration;
|
||||
if (!skippedDuration.IsValid()) {
|
||||
|
|
@ -587,7 +587,7 @@ nsresult VP8TrackEncoder::Encode(VideoSegment* aSegment) {
|
|||
|
||||
mMeanFrameEncodeDuration.insert(TimeStamp::Now() - timebase);
|
||||
mMeanFrameDuration.insert(
|
||||
media::TimeUnit(chunk.GetDuration(), mTrackRate).ToTimeDuration());
|
||||
FramesToTimeUnit(chunk.GetDuration(), mTrackRate).ToTimeDuration());
|
||||
nextEncodeOperation = GetNextEncodeOperation(
|
||||
mMeanFrameEncodeDuration.mean(), mMeanFrameDuration.mean());
|
||||
|
||||
|
|
|
|||
|
|
@ -334,7 +334,7 @@ class Frame {
|
|||
return TimeUnit::Invalid();
|
||||
}
|
||||
MOZ_ASSERT(Header().Info().mRate, "Invalid Frame. Need Header");
|
||||
return media::TimeUnit(Header().mIndex, Header().Info().mRate);
|
||||
return FramesToTimeUnit(Header().mIndex, Header().Info().mRate);
|
||||
}
|
||||
|
||||
TimeUnit Duration() const {
|
||||
|
|
@ -342,7 +342,7 @@ class Frame {
|
|||
return TimeUnit();
|
||||
}
|
||||
MOZ_ASSERT(Header().Info().mRate, "Invalid Frame. Need Header");
|
||||
return media::TimeUnit(mDuration, Header().Info().mRate);
|
||||
return FramesToTimeUnit(mDuration, Header().Info().mRate);
|
||||
}
|
||||
|
||||
// Returns the parsed frame header.
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ Result<Ok, nsresult> FlacFrameParser::DecodeHeaderBlock(const uint8_t* aPacket,
|
|||
blockDataStart, blockDataSize);
|
||||
mInfo.mCodecSpecificConfig =
|
||||
AudioCodecSpecificVariant{std::move(flacCodecSpecificData)};
|
||||
auto duration = media::TimeUnit(mNumFrames, sampleRate);
|
||||
auto duration = FramesToTimeUnit(mNumFrames, sampleRate);
|
||||
mInfo.mDuration = duration.IsValid() ? duration : media::TimeUnit::Zero();
|
||||
mParser = MakeUnique<OpusParser>();
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -54,33 +54,31 @@ struct MP3Resource {
|
|||
|
||||
Duration(int64_t aMicroseconds, float aTolerableRate)
|
||||
: mMicroseconds(aMicroseconds), mTolerableRate(aTolerableRate) {}
|
||||
int64_t Tolerance() const {
|
||||
return AssertedCast<int64_t>(mTolerableRate *
|
||||
static_cast<float>(mMicroseconds));
|
||||
}
|
||||
int64_t Tolerance() const { return mTolerableRate * mMicroseconds; }
|
||||
};
|
||||
|
||||
const char* mFilePath{};
|
||||
bool mIsVBR{};
|
||||
HeaderType mHeaderType{HeaderType::NONE};
|
||||
int64_t mFileSize{};
|
||||
uint32_t mMPEGLayer{};
|
||||
uint32_t mMPEGVersion{};
|
||||
uint8_t mID3MajorVersion{};
|
||||
uint8_t mID3MinorVersion{};
|
||||
uint8_t mID3Flags{};
|
||||
uint32_t mID3Size{};
|
||||
const char* mFilePath;
|
||||
bool mIsVBR;
|
||||
HeaderType mHeaderType;
|
||||
int64_t mFileSize;
|
||||
uint32_t mMPEGLayer;
|
||||
uint32_t mMPEGVersion;
|
||||
uint8_t mID3MajorVersion;
|
||||
uint8_t mID3MinorVersion;
|
||||
uint8_t mID3Flags;
|
||||
uint32_t mID3Size;
|
||||
|
||||
Maybe<Duration> mDuration;
|
||||
float mSeekError{};
|
||||
uint32_t mSampleRate{};
|
||||
uint32_t mSamplesPerFrame{};
|
||||
uint32_t mNumSamples{};
|
||||
uint32_t mPadding{};
|
||||
uint32_t mEncoderDelay{};
|
||||
uint32_t mBitrate{};
|
||||
uint32_t mSlotSize{};
|
||||
int32_t mPrivate{};
|
||||
float mSeekError;
|
||||
uint32_t mSampleRate;
|
||||
uint32_t mSamplesPerFrame;
|
||||
uint32_t mNumSamples;
|
||||
// TODO: temp solution, we could parse them instead or account for them
|
||||
// otherwise.
|
||||
int32_t mNumTrailingFrames;
|
||||
uint32_t mBitrate;
|
||||
uint32_t mSlotSize;
|
||||
int32_t mPrivate;
|
||||
|
||||
// The first n frame offsets.
|
||||
std::vector<int32_t> mSyncOffsets;
|
||||
|
|
@ -103,16 +101,12 @@ class MP3DemuxerTest : public ::testing::Test {
|
|||
res.mID3MinorVersion = 0;
|
||||
res.mID3Flags = 0;
|
||||
res.mID3Size = 2141;
|
||||
// The tolerance comes from the fact that this file has ID3v1 information
|
||||
// at the end, this trips our CBR duration calculation. The file has
|
||||
// however the correct duration when decoded / demuxed completely.
|
||||
res.mDuration = Some(MP3Resource::Duration{30093062, 0.00015f});
|
||||
res.mDuration = Some(MP3Resource::Duration{30067000, 0.001f});
|
||||
res.mSeekError = 0.02f;
|
||||
res.mSampleRate = 44100;
|
||||
res.mSamplesPerFrame = 1152;
|
||||
res.mNumSamples = 1327104;
|
||||
res.mPadding = 0;
|
||||
res.mEncoderDelay = 0;
|
||||
res.mNumSamples = 1325952;
|
||||
res.mNumTrailingFrames = 2;
|
||||
res.mBitrate = 256000;
|
||||
res.mSlotSize = 1;
|
||||
res.mPrivate = 0;
|
||||
|
|
@ -150,16 +144,12 @@ class MP3DemuxerTest : public ::testing::Test {
|
|||
res.mID3MinorVersion = 0;
|
||||
res.mID3Flags = 0;
|
||||
res.mID3Size = 115304;
|
||||
// The tolerance comes from the fact that this file has ID3v1 information
|
||||
// at the end, this trips our CBR duration calculation. The file has
|
||||
// however the correct duration when decoded / demuxed completely.
|
||||
res.mDuration = Some(MP3Resource::Duration{3160833, 0.0017f});
|
||||
res.mDuration = Some(MP3Resource::Duration{3166167, 0.001f});
|
||||
res.mSeekError = 0.02f;
|
||||
res.mSampleRate = 44100;
|
||||
res.mSamplesPerFrame = 1152;
|
||||
res.mNumSamples = 139392;
|
||||
res.mPadding = 0;
|
||||
res.mEncoderDelay = 0;
|
||||
res.mNumTrailingFrames = 0;
|
||||
res.mBitrate = 192000;
|
||||
res.mSlotSize = 1;
|
||||
res.mPrivate = 1;
|
||||
|
|
@ -192,13 +182,12 @@ class MP3DemuxerTest : public ::testing::Test {
|
|||
res.mID3MinorVersion = 0;
|
||||
res.mID3Flags = 0;
|
||||
res.mID3Size = 2221;
|
||||
res.mDuration = Some(MP3Resource::Duration{30081065, 0.f});
|
||||
res.mDuration = Some(MP3Resource::Duration{30081000, 0.005f});
|
||||
res.mSeekError = 0.02f;
|
||||
res.mSampleRate = 44100;
|
||||
res.mSamplesPerFrame = 1152;
|
||||
res.mNumSamples = 1326575;
|
||||
res.mPadding = 576;
|
||||
res.mEncoderDelay = 2257;
|
||||
res.mNumTrailingFrames = 3;
|
||||
res.mBitrate = 154000;
|
||||
res.mSlotSize = 1;
|
||||
res.mPrivate = 0;
|
||||
|
|
@ -231,13 +220,12 @@ class MP3DemuxerTest : public ::testing::Test {
|
|||
res.mID3MinorVersion = 0;
|
||||
res.mID3Flags = 0;
|
||||
res.mID3Size = 24;
|
||||
res.mDuration = Some(MP3Resource::Duration{301473, 0.f});
|
||||
res.mDuration = Some(MP3Resource::Duration{336686, 0.01f});
|
||||
res.mSeekError = 0.2f;
|
||||
res.mSampleRate = 44100;
|
||||
res.mSamplesPerFrame = 1152;
|
||||
res.mNumSamples = 12;
|
||||
res.mPadding = 0;
|
||||
res.mEncoderDelay = 1152 + 529;
|
||||
res.mNumTrailingFrames = 0;
|
||||
res.mBitrate = 256000;
|
||||
res.mSlotSize = 1;
|
||||
res.mPrivate = 0;
|
||||
|
|
@ -272,13 +260,12 @@ class MP3DemuxerTest : public ::testing::Test {
|
|||
res.mID3MinorVersion = 0;
|
||||
res.mID3Flags = 0;
|
||||
res.mID3Size = 24;
|
||||
res.mDuration = Some(MP3Resource::Duration{301473, 0.f});
|
||||
res.mDuration = Some(MP3Resource::Duration{336686, 0.01f});
|
||||
res.mSeekError = 0.2f;
|
||||
res.mSampleRate = 44100;
|
||||
res.mSamplesPerFrame = 1152;
|
||||
res.mNumSamples = 12;
|
||||
res.mPadding = 0;
|
||||
res.mEncoderDelay = 1681;
|
||||
res.mNumTrailingFrames = 0;
|
||||
res.mBitrate = 256000;
|
||||
res.mSlotSize = 1;
|
||||
res.mPrivate = 0;
|
||||
|
|
@ -311,13 +298,12 @@ class MP3DemuxerTest : public ::testing::Test {
|
|||
res.mID3MinorVersion = 0;
|
||||
res.mID3Flags = 0;
|
||||
res.mID3Size = 24;
|
||||
res.mDuration = Some(MP3Resource::Duration{301473, 0.f});
|
||||
res.mDuration = Some(MP3Resource::Duration{336686, 0.01f});
|
||||
res.mSeekError = 0.2f;
|
||||
res.mSampleRate = 44100;
|
||||
res.mSamplesPerFrame = 1152;
|
||||
res.mNumSamples = 12;
|
||||
res.mPadding = 0;
|
||||
res.mEncoderDelay = 1681;
|
||||
res.mNumTrailingFrames = 0;
|
||||
res.mBitrate = 256000;
|
||||
res.mSlotSize = 1;
|
||||
res.mPrivate = 0;
|
||||
|
|
@ -350,13 +336,12 @@ class MP3DemuxerTest : public ::testing::Test {
|
|||
res.mID3MinorVersion = 0;
|
||||
res.mID3Flags = 0;
|
||||
res.mID3Size = 4202;
|
||||
res.mDuration = Some(MP3Resource::Duration{731428, 0.f});
|
||||
res.mDuration = Some(MP3Resource::Duration{783660, 0.01f});
|
||||
res.mSeekError = 0.02f;
|
||||
res.mSampleRate = 44100;
|
||||
res.mSamplesPerFrame = 1152;
|
||||
res.mNumSamples = 29;
|
||||
res.mPadding = 0;
|
||||
res.mEncoderDelay = 1152;
|
||||
res.mNumTrailingFrames = 0;
|
||||
res.mBitrate = 0;
|
||||
res.mSlotSize = 1;
|
||||
res.mPrivate = 0;
|
||||
|
|
@ -410,6 +395,9 @@ TEST_F(MP3DemuxerTest, VBRHeader) {
|
|||
|
||||
if (target.mHeaderType == MP3Resource::HeaderType::XING) {
|
||||
EXPECT_EQ(FrameParser::VBRHeader::XING, vbr.Type());
|
||||
// TODO: find reference number which accounts for trailing headers.
|
||||
// EXPECT_EQ(target.mNumSamples / target.mSamplesPerFrame,
|
||||
// vbr.NumAudioFrames().value());
|
||||
} else if (target.mHeaderType == MP3Resource::HeaderType::VBRI) {
|
||||
EXPECT_TRUE(target.mIsVBR);
|
||||
EXPECT_EQ(FrameParser::VBRHeader::VBRI, vbr.Type());
|
||||
|
|
@ -422,7 +410,6 @@ TEST_F(MP3DemuxerTest, VBRHeader) {
|
|||
|
||||
TEST_F(MP3DemuxerTest, FrameParsing) {
|
||||
for (const auto& target : mTargets) {
|
||||
printf("Testing: %s\n", target.mFilePath);
|
||||
RefPtr<MediaRawData> frameData(target.mDemuxer->DemuxSample());
|
||||
ASSERT_TRUE(frameData);
|
||||
EXPECT_EQ(target.mFileSize, target.mDemuxer->StreamLength());
|
||||
|
|
@ -442,7 +429,7 @@ TEST_F(MP3DemuxerTest, FrameParsing) {
|
|||
}
|
||||
|
||||
++numFrames;
|
||||
parsedLength += AssertedCast<int64_t>(frameData->Size());
|
||||
parsedLength += frameData->Size();
|
||||
|
||||
const auto& frame = target.mDemuxer->LastFrame();
|
||||
const auto& header = frame.Header();
|
||||
|
|
@ -466,8 +453,9 @@ TEST_F(MP3DemuxerTest, FrameParsing) {
|
|||
frameData = target.mDemuxer->DemuxSample();
|
||||
}
|
||||
|
||||
EXPECT_EQ(target.mPadding, target.mDemuxer->PaddingFrames());
|
||||
EXPECT_EQ(target.mEncoderDelay, target.mDemuxer->EncoderDelayFrames());
|
||||
// TODO: find reference number which accounts for trailing headers.
|
||||
// EXPECT_EQ(target.mNumSamples / target.mSamplesPerFrame, numFrames);
|
||||
// EXPECT_EQ(target.mNumSamples, numSamples);
|
||||
EXPECT_GE(numSamples, 0u);
|
||||
|
||||
// There may be trailing headers which we don't parse, so the stream length
|
||||
|
|
@ -485,7 +473,6 @@ TEST_F(MP3DemuxerTest, FrameParsing) {
|
|||
|
||||
TEST_F(MP3DemuxerTest, Duration) {
|
||||
for (const auto& target : mTargets) {
|
||||
printf("Testing: %s\n", target.mFilePath);
|
||||
RefPtr<MediaRawData> frameData(target.mDemuxer->DemuxSample());
|
||||
ASSERT_TRUE(frameData);
|
||||
EXPECT_EQ(target.mFileSize, target.mDemuxer->StreamLength());
|
||||
|
|
@ -501,16 +488,10 @@ TEST_F(MP3DemuxerTest, Duration) {
|
|||
}
|
||||
frameData = target.mDemuxer->DemuxSample();
|
||||
}
|
||||
if (target.mDuration) {
|
||||
// At the end, the durations should always be exact.
|
||||
EXPECT_EQ(target.mDuration->mMicroseconds,
|
||||
target.mDemuxer->Duration()->ToMicroseconds());
|
||||
}
|
||||
}
|
||||
|
||||
// Seek out of range tests.
|
||||
for (const auto& target : mTargets) {
|
||||
printf("Testing %s\n", target.mFilePath);
|
||||
// Skip tests for stream media resources because of lacking duration.
|
||||
if (target.mFileSize <= 0) {
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ TEST(TestOggWriter, MultiPageInput)
|
|||
}
|
||||
|
||||
size_t inputBytes = 0;
|
||||
const size_t USECS_PER_MS = 1000;
|
||||
auto frameData = MakeRefPtr<EncodedFrame::FrameData>();
|
||||
frameData->SetLength(320); // 320B per 20ms == 128kbps
|
||||
PodZero(frameData->Elements(), frameData->Length());
|
||||
|
|
|
|||
|
|
@ -4,191 +4,22 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "TimeUnits.h"
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "TimeUnits.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::media;
|
||||
using TimeUnit = mozilla::media::TimeUnit;
|
||||
|
||||
TEST(TimeUnit, BasicArithmetic)
|
||||
{
|
||||
const TimeUnit a(1000, 44100);
|
||||
{
|
||||
TimeUnit b = a * 10;
|
||||
EXPECT_EQ(b.mBase, 44100);
|
||||
EXPECT_EQ(b.mTicks.value(), a.mTicks.value() * 10);
|
||||
EXPECT_EQ(a * 10, b);
|
||||
}
|
||||
{
|
||||
TimeUnit b = a / 10;
|
||||
EXPECT_EQ(b.mBase, 44100);
|
||||
EXPECT_EQ(b.mTicks.value(), a.mTicks.value() / 10);
|
||||
EXPECT_EQ(a / 10, b);
|
||||
}
|
||||
{
|
||||
TimeUnit b = TimeUnit(10, 44100);
|
||||
b += a;
|
||||
EXPECT_EQ(b.mBase, 44100);
|
||||
EXPECT_EQ(b.mTicks.value(), a.mTicks.value() + 10);
|
||||
EXPECT_EQ(b - a, TimeUnit(10, 44100));
|
||||
}
|
||||
{
|
||||
TimeUnit b = TimeUnit(1010, 44100);
|
||||
b -= a; // now 10
|
||||
EXPECT_EQ(b.mBase, 44100);
|
||||
EXPECT_EQ(b.mTicks.value(), 10);
|
||||
EXPECT_EQ(a + b, TimeUnit(1010, 44100));
|
||||
}
|
||||
{
|
||||
TimeUnit b = TimeUnit(4010, 44100);
|
||||
TimeUnit c = b % a; // now 10
|
||||
EXPECT_EQ(c.mBase, 44100);
|
||||
EXPECT_EQ(c.mTicks.value(), 10);
|
||||
}
|
||||
{
|
||||
// Adding 6s in nanoseconds (e.g. coming from script) to a typical number
|
||||
// from an mp4, 9001 in base 90000
|
||||
TimeUnit b = TimeUnit(6000000000, 1000000000);
|
||||
TimeUnit c = TimeUnit(9001, 90000);
|
||||
TimeUnit d = c + b;
|
||||
EXPECT_EQ(d.mBase, 90000);
|
||||
EXPECT_EQ(d.mTicks.value(), 549001);
|
||||
}
|
||||
{
|
||||
// Subtracting 9001 in base 9000 from 6s in nanoseconds (e.g. coming from
|
||||
// script), converting to back to base 9000.
|
||||
TimeUnit b = TimeUnit(6000000000, 1000000000);
|
||||
TimeUnit c = TimeUnit(9001, 90000);
|
||||
TimeUnit d = (b - c).ToBase(90000);
|
||||
EXPECT_EQ(d.mBase, 90000);
|
||||
EXPECT_EQ(d.mTicks.value(), 530998);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TimeUnit, Base)
|
||||
{
|
||||
{
|
||||
TimeUnit a = TimeUnit::FromSeconds(1);
|
||||
EXPECT_EQ(a.mTicks.value(), 1000000);
|
||||
EXPECT_EQ(a.mBase, 1000000);
|
||||
}
|
||||
{
|
||||
TimeUnit a = TimeUnit::FromMicroseconds(44100000000);
|
||||
EXPECT_EQ(a.mTicks.value(), 44100000000);
|
||||
EXPECT_EQ(a.mBase, 1000000);
|
||||
}
|
||||
{
|
||||
TimeUnit a = TimeUnit::FromSeconds(6.0);
|
||||
EXPECT_EQ(a.mTicks.value(), 6000000);
|
||||
EXPECT_EQ(a.mBase, 1000000);
|
||||
double error;
|
||||
TimeUnit b = a.ToBase(90000, error);
|
||||
EXPECT_EQ(error, 0);
|
||||
EXPECT_EQ(b.mTicks.value(), 540000);
|
||||
EXPECT_EQ(b.mBase, 90000);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TimeUnit, Rounding)
|
||||
{
|
||||
int64_t usecs = 662617;
|
||||
double seconds = TimeUnit::FromMicroseconds(usecs).ToSeconds();
|
||||
TimeUnit fromSeconds = TimeUnit::FromSeconds(seconds);
|
||||
EXPECT_EQ(fromSeconds.mTicks.value(), usecs);
|
||||
// TimeUnit base is microseconds if not explicitly passed.
|
||||
EXPECT_EQ(fromSeconds.mBase, 1000000);
|
||||
EXPECT_EQ(fromSeconds.ToMicroseconds(), usecs);
|
||||
int64_t usecs = 66261715;
|
||||
double seconds = media::TimeUnit::FromMicroseconds(usecs).ToSeconds();
|
||||
EXPECT_EQ(media::TimeUnit::FromSeconds(seconds).ToMicroseconds(), usecs);
|
||||
|
||||
seconds = 4.169470123;
|
||||
int64_t nsecs = 4169470123;
|
||||
EXPECT_EQ(TimeUnit::FromSeconds(seconds, 1e9).ToNanoseconds(), nsecs);
|
||||
EXPECT_EQ(TimeUnit::FromSeconds(seconds, 1e9).ToMicroseconds(), nsecs / 1000);
|
||||
|
||||
seconds = 2312312.16947012;
|
||||
nsecs = 2312312169470120;
|
||||
EXPECT_EQ(TimeUnit::FromSeconds(seconds, 1e9).ToNanoseconds(), nsecs);
|
||||
EXPECT_EQ(TimeUnit::FromSeconds(seconds, 1e9).ToMicroseconds(), nsecs / 1000);
|
||||
|
||||
seconds = 2312312.169470123;
|
||||
nsecs = 2312312169470123;
|
||||
// A double doesn't have enough precision to roundtrip this time value
|
||||
// correctly in this base, but the number of microseconds is still correct.
|
||||
// This value is about 142.5 days however.
|
||||
// This particular calculation results in exactly 1ns of difference after
|
||||
// roundtrip. Enable this after remoing the MOZ_CRASH in TimeUnit::FromSeconds
|
||||
// EXPECT_EQ(TimeUnit::FromSeconds(seconds, 1e9).ToNanoseconds() - nsecs, 1);
|
||||
EXPECT_EQ(TimeUnit::FromSeconds(seconds, 1e9).ToMicroseconds(), nsecs / 1000);
|
||||
}
|
||||
|
||||
TEST(TimeUnit, Comparisons)
|
||||
{
|
||||
TimeUnit a(0, 1e9);
|
||||
TimeUnit b(1, 1e9);
|
||||
TimeUnit c(1, 1e6);
|
||||
|
||||
EXPECT_GE(b, a);
|
||||
EXPECT_GE(c, a);
|
||||
EXPECT_GE(c, b);
|
||||
|
||||
EXPECT_GT(b, a);
|
||||
EXPECT_GT(c, a);
|
||||
EXPECT_GT(c, b);
|
||||
|
||||
EXPECT_LE(a, b);
|
||||
EXPECT_LE(a, c);
|
||||
EXPECT_LE(b, c);
|
||||
|
||||
EXPECT_LT(a, b);
|
||||
EXPECT_LT(a, c);
|
||||
EXPECT_LT(b, c);
|
||||
|
||||
// Equivalence of zero regardless of the base
|
||||
TimeUnit d(0, 1);
|
||||
TimeUnit e(0, 1000);
|
||||
EXPECT_EQ(a, d);
|
||||
EXPECT_EQ(a, e);
|
||||
|
||||
// Equivalence of time accross bases
|
||||
TimeUnit f(1000, 1e9);
|
||||
TimeUnit g(1, 1e6);
|
||||
EXPECT_EQ(f, g);
|
||||
|
||||
// Comparisons with infinity, same base
|
||||
TimeUnit h = TimeUnit::FromInfinity();
|
||||
TimeUnit i = TimeUnit::Zero();
|
||||
EXPECT_LE(i, h);
|
||||
EXPECT_LT(i, h);
|
||||
EXPECT_GE(h, i);
|
||||
EXPECT_GT(h, i);
|
||||
|
||||
// Comparisons with infinity, different base
|
||||
TimeUnit j = TimeUnit::FromInfinity();
|
||||
TimeUnit k = TimeUnit::Zero(1000000);
|
||||
EXPECT_LE(k, j);
|
||||
EXPECT_LT(k, j);
|
||||
EXPECT_GE(j, k);
|
||||
EXPECT_GT(j, k);
|
||||
|
||||
// Comparison of very big numbers, different base that have a gcd that makes
|
||||
// it easy to reduce, to test the fraction reduction code
|
||||
TimeUnit l = TimeUnit(123123120000000, 1000000000);
|
||||
TimeUnit m = TimeUnit(123123120000000, 1000);
|
||||
EXPECT_LE(l, m);
|
||||
EXPECT_LT(l, m);
|
||||
EXPECT_GE(m, l);
|
||||
EXPECT_GT(m, l);
|
||||
|
||||
// Comparison of very big numbers, different base that are co-prime: worst
|
||||
// cast scenario.
|
||||
TimeUnit n = TimeUnit(123123123123123, 1000000000);
|
||||
TimeUnit o = TimeUnit(123123123123123, 1000000001);
|
||||
EXPECT_LE(n, o);
|
||||
EXPECT_LT(n, o);
|
||||
EXPECT_GE(o, n);
|
||||
EXPECT_GT(o, n);
|
||||
seconds = 4.169470;
|
||||
usecs = 4169470;
|
||||
EXPECT_EQ(media::TimeUnit::FromSeconds(seconds).ToMicroseconds(), usecs);
|
||||
}
|
||||
|
||||
TEST(TimeUnit, InfinityMath)
|
||||
|
|
|
|||
|
|
@ -263,7 +263,7 @@ TEST(VP8VideoTrackEncoder, RoundingErrorFramesEncode)
|
|||
TimeStamp now = TimeStamp::Now();
|
||||
|
||||
// Pass nine frames with timestamps not expressable in 90kHz sample rate,
|
||||
// then one frame to make the total duration close to one second.
|
||||
// then one frame to make the total duration one second.
|
||||
VideoSegment segment;
|
||||
uint32_t usPerFrame = 99999; // 99.999ms
|
||||
for (uint32_t i = 0; i < 9; ++i) {
|
||||
|
|
@ -291,8 +291,7 @@ TEST(VP8VideoTrackEncoder, RoundingErrorFramesEncode)
|
|||
while (RefPtr<EncodedFrame> frame = encoder.mEncodedVideoQueue.PopFront()) {
|
||||
totalDuration += frame->mDuration;
|
||||
}
|
||||
// Not exact, the stream is encoded in time base 90kHz.
|
||||
const uint64_t oneSecond = PR_USEC_PER_SEC - 1;
|
||||
const uint64_t oneSecond = PR_USEC_PER_SEC;
|
||||
EXPECT_EQ(oneSecond, totalDuration);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
include("/dom/media/webrtc/third_party_build/webrtc.mozbuild")
|
||||
|
||||
DEFINES["ENABLE_SET_CUBEB_BACKEND"] = True
|
||||
DEFINES["VISIBLE_TIMEUNIT_INTERNALS"] = True
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
"/dom/media/mediasink",
|
||||
|
|
|
|||
|
|
@ -160,11 +160,11 @@ struct TestFileData {
|
|||
bool mParseResult;
|
||||
uint32_t mNumberVideoTracks;
|
||||
bool mHasVideoIndice;
|
||||
double mVideoDuration; // For first video track, -1 if N/A, in seconds.
|
||||
int64_t mVideoDuration; // For first video track, -1 if N/A.
|
||||
int32_t mWidth;
|
||||
int32_t mHeight;
|
||||
uint32_t mNumberAudioTracks;
|
||||
double mAudioDuration; // For first audio track, -1 if N/A, in seconds.
|
||||
int64_t mAudioDuration; // For first audio track, -1 if N/A.
|
||||
bool mHasCrypto; // Note, MP4Metadata only considers pssh box for crypto.
|
||||
uint64_t mMoofReachedOffset; // or 0 for the end.
|
||||
bool mValidMoof;
|
||||
|
|
@ -174,40 +174,40 @@ struct TestFileData {
|
|||
static const TestFileData testFiles[] = {
|
||||
// filename parses? #V hasVideoIndex vDur w h #A aDur hasCrypto? moofOffset
|
||||
// validMoof? audio_profile
|
||||
{"test_case_1156505.mp4", false, 0, false, -1, 0, 0, 0, -1., false, 152,
|
||||
{"test_case_1156505.mp4", false, 0, false, -1, 0, 0, 0, -1, false, 152,
|
||||
false, 0}, // invalid ''trak box
|
||||
{"test_case_1181213.mp4", true, 1, true, 0.41666666, 320, 240, 1,
|
||||
0.47746032, true, 0, false, 2},
|
||||
{"test_case_1181213.mp4", true, 1, true, 416666, 320, 240, 1, 477460, true,
|
||||
0, false, 2},
|
||||
{"test_case_1181215.mp4", true, 0, false, -1, 0, 0, 0, -1, false, 0, false,
|
||||
0},
|
||||
{"test_case_1181223.mp4", false, 0, false, 0.41666666, 320, 240, 0, -1,
|
||||
false, 0, false, 0},
|
||||
{"test_case_1181223.mp4", false, 0, false, 416666, 320, 240, 0, -1, false,
|
||||
0, false, 0},
|
||||
{"test_case_1181719.mp4", false, 0, false, -1, 0, 0, 0, -1, false, 0, false,
|
||||
0},
|
||||
{"test_case_1185230.mp4", true, 2, true, 0.41666666, 320, 240, 2,
|
||||
0.0000059754907, false, 0, false, 2},
|
||||
{"test_case_1187067.mp4", true, 1, true, 0.080000, 160, 90, 0, -1, false, 0,
|
||||
{"test_case_1185230.mp4", true, 2, true, 416666, 320, 240, 2, 5, false, 0,
|
||||
false, 2},
|
||||
{"test_case_1187067.mp4", true, 1, true, 80000, 160, 90, 0, -1, false, 0,
|
||||
false, 0},
|
||||
{"test_case_1200326.mp4", false, 0, false, -1, 0, 0, 0, -1, false, 0, false,
|
||||
0},
|
||||
{"test_case_1204580.mp4", true, 1, true, 0.502500, 320, 180, 0, -1, false,
|
||||
0, false, 0},
|
||||
{"test_case_1204580.mp4", true, 1, true, 502500, 320, 180, 0, -1, false, 0,
|
||||
false, 0},
|
||||
{"test_case_1216748.mp4", false, 0, false, -1, 0, 0, 0, -1, false, 152,
|
||||
false, 0}, // invalid 'trak' box
|
||||
{"test_case_1296473.mp4", false, 0, false, -1, 0, 0, 0, -1, false, 0, false,
|
||||
0},
|
||||
{"test_case_1296532.mp4", true, 1, true, 5.589333, 560, 320, 1, 5.589333,
|
||||
{"test_case_1296532.mp4", true, 1, true, 5589333, 560, 320, 1, 5589333,
|
||||
true, 0, true, 2},
|
||||
{"test_case_1301065.mp4", true, 0, false, -1, 0, 0, 1, 100079991719, false,
|
||||
0, false, 2},
|
||||
{"test_case_1301065-u32max.mp4", true, 0, false, -1, 0, 0, 1, 97391.548639,
|
||||
{"test_case_1301065.mp4", true, 0, false, -1, 0, 0, 1, 100079991719000000,
|
||||
false, 0, false, 2},
|
||||
{"test_case_1301065-u32max.mp4", true, 0, false, -1, 0, 0, 1, 97391548639,
|
||||
false, 0, false, 2},
|
||||
{"test_case_1301065-max-ez.mp4", true, 0, false, -1, 0, 0, 1,
|
||||
209146758.205306, false, 0, false, 2},
|
||||
209146758205306, false, 0, false, 2},
|
||||
{"test_case_1301065-harder.mp4", true, 0, false, -1, 0, 0, 1,
|
||||
209146758.205328, false, 0, false, 2},
|
||||
209146758205328, false, 0, false, 2},
|
||||
{"test_case_1301065-max-ok.mp4", true, 0, false, -1, 0, 0, 1,
|
||||
9223372036854.775, false, 0, false, 2},
|
||||
9223372036854775804, false, 0, false, 2},
|
||||
// The duration is overflow for int64_t in TestFileData, parser uses
|
||||
// uint64_t so
|
||||
// this file is ignore.
|
||||
|
|
@ -215,36 +215,36 @@ static const TestFileData testFiles[] = {
|
|||
// false, 0,
|
||||
// false, 2
|
||||
// },
|
||||
{"test_case_1301065-i64max.mp4", true, 0, false, -1, 0, 0, 1,
|
||||
std::numeric_limits<double>::infinity(), false, 0, false, 2},
|
||||
{"test_case_1301065-i64min.mp4", true, 0, false, -1, 0, 0, 1,
|
||||
-std::numeric_limits<double>::infinity(), false, 0, false, 2},
|
||||
{"test_case_1301065-i64max.mp4", true, 0, false, -1, 0, 0, 0, -1, false, 0,
|
||||
false, 0},
|
||||
{"test_case_1301065-i64min.mp4", true, 0, false, -1, 0, 0, 0, -1, false, 0,
|
||||
false, 0},
|
||||
{"test_case_1301065-u64max.mp4", true, 0, false, -1, 0, 0, 1, 0, false, 0,
|
||||
false, 2},
|
||||
{"test_case_1329061.mov", false, 0, false, -1, 0, 0, 1, 234567981, false, 0,
|
||||
false, 2},
|
||||
{"test_case_1351094.mp4", true, 0, false, -1, 0, 0, 0, -1, false, 0, true,
|
||||
0},
|
||||
{"test_case_1389299.mp4", true, 1, true, 5.589333, 560, 320, 1, 5.589333,
|
||||
{"test_case_1389299.mp4", true, 1, true, 5589333, 560, 320, 1, 5589333,
|
||||
true, 0, true, 2},
|
||||
|
||||
{"test_case_1389527.mp4", true, 1, false, 5.005000, 80, 128, 1, 4.992000,
|
||||
{"test_case_1389527.mp4", true, 1, false, 5005000, 80, 128, 1, 4992000,
|
||||
false, 0, false, 2},
|
||||
{"test_case_1395244.mp4", true, 1, true, 0.41666666, 320, 240, 1,
|
||||
0.47746032, false, 0, false, 2},
|
||||
{"test_case_1388991.mp4", true, 0, false, -1, 0, 0, 1, 30.000181, false, 0,
|
||||
{"test_case_1395244.mp4", true, 1, true, 416666, 320, 240, 1, 477460, false,
|
||||
0, false, 2},
|
||||
{"test_case_1388991.mp4", true, 0, false, -1, 0, 0, 1, 30000181, false, 0,
|
||||
false, 2},
|
||||
{"test_case_1410565.mp4", false, 0, false, 0, 0, 0, 0, 0, false, 955100,
|
||||
true, 2}, // negative 'timescale'
|
||||
{"test_case_1513651-2-sample-description-entries.mp4", true, 1, true,
|
||||
9.843344, 400, 300, 0, -1, true, 0, false, 0},
|
||||
9843344, 400, 300, 0, -1, true, 0, false, 0},
|
||||
{"test_case_1519617-cenc-init-with-track_id-0.mp4", true, 1, true, 0, 1272,
|
||||
530, 0, -1, false, 0, false,
|
||||
0}, // Uses bad track id 0 and has a sinf but no pssh
|
||||
{"test_case_1519617-track2-trafs-removed.mp4", true, 1, true, 10.032000,
|
||||
400, 300, 1, 10.032000, false, 0, true, 2},
|
||||
{"test_case_1519617-video-has-track_id-0.mp4", true, 1, true, 10.032000,
|
||||
400, 300, 1, 10.032000, false, 0, true, 2}, // Uses bad track id 0
|
||||
{"test_case_1519617-track2-trafs-removed.mp4", true, 1, true, 10032000, 400,
|
||||
300, 1, 10032000, false, 0, true, 2},
|
||||
{"test_case_1519617-video-has-track_id-0.mp4", true, 1, true, 10032000, 400,
|
||||
300, 1, 10032000, false, 0, true, 2}, // Uses bad track id 0
|
||||
// The following file has multiple sample description entries with the same
|
||||
// crypto information. This does not cover multiple entries with different
|
||||
// crypto information which is tracked by
|
||||
|
|
@ -302,13 +302,9 @@ TEST(MP4Metadata, test_case_mp4)
|
|||
ASSERT_TRUE(!!videoInfo);
|
||||
EXPECT_TRUE(videoInfo->IsValid()) << tests[test].mFilename;
|
||||
EXPECT_TRUE(videoInfo->IsVideo()) << tests[test].mFilename;
|
||||
if (std::isinf(tests[test].mVideoDuration)) {
|
||||
ASSERT_TRUE(std::isinf(videoInfo->mDuration.ToSeconds()));
|
||||
} else {
|
||||
EXPECT_FLOAT_EQ(tests[test].mVideoDuration,
|
||||
videoInfo->mDuration.ToSeconds())
|
||||
<< tests[test].mFilename;
|
||||
}
|
||||
EXPECT_EQ(tests[test].mVideoDuration,
|
||||
videoInfo->mDuration.ToMicroseconds())
|
||||
<< tests[test].mFilename;
|
||||
EXPECT_EQ(tests[test].mWidth, videoInfo->mDisplay.width)
|
||||
<< tests[test].mFilename;
|
||||
EXPECT_EQ(tests[test].mHeight, videoInfo->mDisplay.height)
|
||||
|
|
@ -340,16 +336,14 @@ TEST(MP4Metadata, test_case_mp4)
|
|||
ASSERT_TRUE(!!audioInfo);
|
||||
EXPECT_TRUE(audioInfo->IsValid()) << tests[test].mFilename;
|
||||
EXPECT_TRUE(audioInfo->IsAudio()) << tests[test].mFilename;
|
||||
if (std::isinf(tests[test].mAudioDuration)) {
|
||||
ASSERT_TRUE(std::isinf(audioInfo->mDuration.ToSeconds()))
|
||||
<< tests[test].mFilename;
|
||||
} else {
|
||||
EXPECT_FLOAT_EQ(tests[test].mAudioDuration,
|
||||
audioInfo->mDuration.ToSeconds())
|
||||
<< tests[test].mFilename;
|
||||
}
|
||||
EXPECT_EQ(tests[test].mAudioDuration,
|
||||
audioInfo->mDuration.ToMicroseconds())
|
||||
<< tests[test].mFilename;
|
||||
EXPECT_EQ(tests[test].mAudioProfile, audioInfo->mProfile)
|
||||
<< tests[test].mFilename;
|
||||
if (tests[test].mAudioDuration != audioInfo->mDuration.ToMicroseconds()) {
|
||||
MOZ_RELEASE_ASSERT(false);
|
||||
}
|
||||
|
||||
MP4Metadata::ResultAndIndice indices =
|
||||
metadata.GetTrackIndice(audioInfo->mTrackId);
|
||||
|
|
|
|||
|
|
@ -113,15 +113,11 @@ struct ParamTraits<mozilla::AacCodecSpecificData> {
|
|||
static void Write(MessageWriter* aWriter, const paramType& aParam) {
|
||||
WriteParam(aWriter, *aParam.mEsDescriptorBinaryBlob);
|
||||
WriteParam(aWriter, *aParam.mDecoderConfigDescriptorBinaryBlob);
|
||||
WriteParam(aWriter, aParam.mEncoderDelayFrames);
|
||||
WriteParam(aWriter, aParam.mMediaFrameCount);
|
||||
}
|
||||
static bool Read(MessageReader* aReader, paramType* aResult) {
|
||||
return ReadParam(aReader, aResult->mEsDescriptorBinaryBlob.get()) &&
|
||||
ReadParam(aReader,
|
||||
aResult->mDecoderConfigDescriptorBinaryBlob.get()) &&
|
||||
ReadParam(aReader, &aResult->mEncoderDelayFrames) &&
|
||||
ReadParam(aReader, &aResult->mMediaFrameCount);
|
||||
aResult->mDecoderConfigDescriptorBinaryBlob.get());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -218,25 +214,21 @@ struct ParamTraits<mozilla::MediaDataDecoder::ConversionRequired>
|
|||
|
||||
template <>
|
||||
struct ParamTraits<mozilla::media::TimeUnit> {
|
||||
using paramType = mozilla::media::TimeUnit;
|
||||
typedef mozilla::media::TimeUnit paramType;
|
||||
|
||||
static void Write(MessageWriter* aWriter, const paramType& aParam) {
|
||||
WriteParam(aWriter, aParam.IsValid());
|
||||
WriteParam(aWriter, aParam.IsValid() ? aParam.mTicks.value() : 0);
|
||||
WriteParam(aWriter,
|
||||
aParam.IsValid() ? aParam.mBase : 1); // base can't be 0
|
||||
WriteParam(aWriter, aParam.IsValid() ? aParam.ToMicroseconds() : 0);
|
||||
}
|
||||
|
||||
static bool Read(MessageReader* aReader, paramType* aResult) {
|
||||
bool valid;
|
||||
int64_t ticks;
|
||||
int64_t base;
|
||||
if (ReadParam(aReader, &valid) && ReadParam(aReader, &ticks) &&
|
||||
ReadParam(aReader, &base)) {
|
||||
if (valid) {
|
||||
*aResult = mozilla::media::TimeUnit(ticks, base);
|
||||
} else {
|
||||
int64_t value;
|
||||
if (ReadParam(aReader, &valid) && ReadParam(aReader, &value)) {
|
||||
if (!valid) {
|
||||
*aResult = mozilla::media::TimeUnit::Invalid();
|
||||
} else {
|
||||
*aResult = mozilla::media::TimeUnit::FromMicroseconds(value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ void AudioSink::ReenqueueUnplayedAudioDataIfNeeded() {
|
|||
while (!packetsToReenqueue.IsEmpty()) {
|
||||
auto packetData = packetsToReenqueue.PopLastElement();
|
||||
uint32_t packetFrameCount = packetData.Length() / channelCount;
|
||||
auto duration = TimeUnit(packetFrameCount, rate);
|
||||
auto duration = FramesToTimeUnit(packetFrameCount, rate);
|
||||
if (!duration.IsValid()) {
|
||||
NS_WARNING("Int overflow in AudioSink");
|
||||
mErrored = true;
|
||||
|
|
@ -333,8 +333,7 @@ void AudioSink::SetPlaying(bool aPlaying) {
|
|||
}
|
||||
|
||||
TimeUnit AudioSink::GetEndTime() const {
|
||||
uint64_t written = mWritten;
|
||||
TimeUnit played = media::TimeUnit(written, mOutputRate) + mStartTime;
|
||||
TimeUnit played = FramesToTimeUnit(mWritten, mOutputRate) + mStartTime;
|
||||
if (!played.IsValid()) {
|
||||
NS_WARNING("Int overflow calculating audio end time");
|
||||
return TimeUnit::Zero();
|
||||
|
|
@ -590,7 +589,7 @@ already_AddRefed<AudioData> AudioSink::CreateAudioFromBuffer(
|
|||
if (!frames) {
|
||||
return nullptr;
|
||||
}
|
||||
auto duration = media::TimeUnit(frames, mOutputRate);
|
||||
auto duration = FramesToTimeUnit(frames, mOutputRate);
|
||||
if (!duration.IsValid()) {
|
||||
NS_WARNING("Int overflow in AudioSink");
|
||||
mErrored = true;
|
||||
|
|
|
|||
|
|
@ -478,8 +478,7 @@ RefPtr<DecodedStream::EndedPromise> DecodedStream::OnEnded(TrackType aType) {
|
|||
|
||||
if (aType == TrackInfo::kAudioTrack && mInfo.HasAudio()) {
|
||||
return mAudioEndedPromise;
|
||||
}
|
||||
if (aType == TrackInfo::kVideoTrack && mInfo.HasVideo()) {
|
||||
} else if (aType == TrackInfo::kVideoTrack && mInfo.HasVideo()) {
|
||||
return mVideoEndedPromise;
|
||||
}
|
||||
return nullptr;
|
||||
|
|
@ -791,7 +790,7 @@ already_AddRefed<AudioData> DecodedStream::CreateSilenceDataIfGapExists(
|
|||
NS_WARNING("OOM in DecodedStream::CreateSilenceDataIfGapExists");
|
||||
return nullptr;
|
||||
}
|
||||
auto duration = media::TimeUnit(missingFrames.value(), aNextAudio->mRate);
|
||||
auto duration = FramesToTimeUnit(missingFrames.value(), aNextAudio->mRate);
|
||||
if (!duration.IsValid()) {
|
||||
NS_WARNING("Int overflow in DecodedStream::CreateSilenceDataIfGapExists");
|
||||
return nullptr;
|
||||
|
|
@ -1059,7 +1058,7 @@ TimeUnit DecodedStream::GetEndTime(TrackType aType) const {
|
|||
TRACE("DecodedStream::GetEndTime");
|
||||
if (aType == TrackInfo::kAudioTrack && mInfo.HasAudio() && mData) {
|
||||
auto t = mStartTime.ref() +
|
||||
media::TimeUnit(mData->mAudioFramesWritten, mInfo.mAudio.mRate);
|
||||
FramesToTimeUnit(mData->mAudioFramesWritten, mInfo.mAudio.mRate);
|
||||
if (t.IsValid()) {
|
||||
return t;
|
||||
}
|
||||
|
|
@ -1156,8 +1155,7 @@ void DecodedStream::GetDebugInfo(dom::MediaSinkDebugInfo& aInfo) {
|
|||
aInfo.mDecodedStream.mLastAudio =
|
||||
lastAudio ? lastAudio->GetEndTime().ToMicroseconds() : -1;
|
||||
aInfo.mDecodedStream.mAudioQueueFinished = mAudioQueue.IsFinished();
|
||||
aInfo.mDecodedStream.mAudioQueueSize =
|
||||
AssertedCast<int>(mAudioQueue.GetSize());
|
||||
aInfo.mDecodedStream.mAudioQueueSize = mAudioQueue.GetSize();
|
||||
if (mData) {
|
||||
mData->GetDebugInfo(aInfo.mDecodedStream.mData);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,8 +65,8 @@ MediaResult ContainerParser::IsMediaSegmentPresent(const MediaSpan& aData) {
|
|||
}
|
||||
|
||||
MediaResult ContainerParser::ParseStartAndEndTimestamps(const MediaSpan& aData,
|
||||
media::TimeUnit& aStart,
|
||||
media::TimeUnit& aEnd) {
|
||||
int64_t& aStart,
|
||||
int64_t& aEnd) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
|
|
@ -140,8 +140,8 @@ class WebMContainerParser
|
|||
}
|
||||
|
||||
MediaResult ParseStartAndEndTimestamps(const MediaSpan& aData,
|
||||
media::TimeUnit& aStart,
|
||||
media::TimeUnit& aEnd) override {
|
||||
int64_t& aStart,
|
||||
int64_t& aEnd) override {
|
||||
bool initSegment = NS_SUCCEEDED(IsInitSegmentPresent(aData));
|
||||
|
||||
if (mLastMapping &&
|
||||
|
|
@ -284,16 +284,14 @@ class WebMContainerParser
|
|||
? mapping[completeIdx + 1].mTimecode -
|
||||
mapping[completeIdx].mTimecode
|
||||
: mapping[completeIdx].mTimecode - previousMapping.ref().mTimecode;
|
||||
aStart = media::TimeUnit::FromNanoseconds(
|
||||
AssertedCast<int64_t>(mapping[0].mTimecode));
|
||||
aEnd = media::TimeUnit::FromNanoseconds(
|
||||
AssertedCast<int64_t>(mapping[completeIdx].mTimecode + frameDuration));
|
||||
aStart = mapping[0].mTimecode / NS_PER_USEC;
|
||||
aEnd = (mapping[completeIdx].mTimecode + frameDuration) / NS_PER_USEC;
|
||||
|
||||
MSE_DEBUG("[%" PRId64 ", %" PRId64 "] [fso=%" PRId64 ", leo=%" PRId64
|
||||
", l=%zu processedIdx=%u fs=%" PRId64 "]",
|
||||
aStart.ToMicroseconds(), aEnd.ToMicroseconds(),
|
||||
mapping[0].mSyncOffset, mapping[completeIdx].mEndOffset,
|
||||
mapping.Length(), completeIdx, mCompleteMediaSegmentRange.mEnd);
|
||||
aStart, aEnd, mapping[0].mSyncOffset,
|
||||
mapping[completeIdx].mEndOffset, mapping.Length(), completeIdx,
|
||||
mCompleteMediaSegmentRange.mEnd);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
@ -514,8 +512,8 @@ class MP4ContainerParser : public ContainerParser,
|
|||
|
||||
public:
|
||||
MediaResult ParseStartAndEndTimestamps(const MediaSpan& aData,
|
||||
media::TimeUnit& aStart,
|
||||
media::TimeUnit& aEnd) override {
|
||||
int64_t& aStart,
|
||||
int64_t& aEnd) override {
|
||||
bool initSegment = NS_SUCCEEDED(IsInitSegmentPresent(aData));
|
||||
if (initSegment) {
|
||||
mResource = new SourceBufferResource();
|
||||
|
|
@ -565,7 +563,7 @@ class MP4ContainerParser : public ContainerParser,
|
|||
}
|
||||
mTotalParsed += aData.Length();
|
||||
|
||||
MP4Interval<media::TimeUnit> compositionRange =
|
||||
MP4Interval<Microseconds> compositionRange =
|
||||
mParser->GetCompositionRange(byteRanges);
|
||||
|
||||
mCompleteMediaHeaderRange =
|
||||
|
|
@ -582,8 +580,7 @@ class MP4ContainerParser : public ContainerParser,
|
|||
}
|
||||
aStart = compositionRange.start;
|
||||
aEnd = compositionRange.end;
|
||||
MSE_DEBUG("[%" PRId64 ", %" PRId64 "]", aStart.ToMicroseconds(),
|
||||
aEnd.ToMicroseconds());
|
||||
MSE_DEBUG("[%" PRId64 ", %" PRId64 "]", aStart, aEnd);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
@ -696,8 +693,8 @@ class ADTSContainerParser
|
|||
}
|
||||
|
||||
MediaResult ParseStartAndEndTimestamps(const MediaSpan& aData,
|
||||
media::TimeUnit& aStart,
|
||||
media::TimeUnit& aEnd) override {
|
||||
int64_t& aStart,
|
||||
int64_t& aEnd) override {
|
||||
// ADTS header.
|
||||
Header header;
|
||||
if (!Parse(aData, header)) {
|
||||
|
|
@ -727,8 +724,7 @@ class ADTSContainerParser
|
|||
// media segment.
|
||||
mCompleteMediaHeaderRange = mCompleteMediaSegmentRange;
|
||||
|
||||
MSE_DEBUG("[%" PRId64 ", %" PRId64 "]", aStart.ToMicroseconds(),
|
||||
aEnd.ToMicroseconds());
|
||||
MSE_DEBUG("[%" PRId64 ", %" PRId64 "]", aStart, aEnd);
|
||||
// We don't update timestamps, regardless.
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,8 +47,8 @@ class ContainerParser : public DecoderDoctorLifeLogger<ContainerParser> {
|
|||
// if aStart and aEnd have been updated and NS_ERROR_NOT_AVAILABLE otherwise
|
||||
// when no error were encountered.
|
||||
virtual MediaResult ParseStartAndEndTimestamps(const MediaSpan& aData,
|
||||
media::TimeUnit& aStart,
|
||||
media::TimeUnit& aEnd);
|
||||
int64_t& aStart,
|
||||
int64_t& aEnd);
|
||||
|
||||
// Compare aLhs and rHs, considering any error that may exist in the
|
||||
// timestamps from the format's base representation. Return true if aLhs
|
||||
|
|
|
|||
|
|
@ -265,9 +265,9 @@ void MediaSource::SetDuration(double aDuration, ErrorResult& aRv) {
|
|||
aRv.ErrorCodeAsInt());
|
||||
}
|
||||
|
||||
void MediaSource::SetDuration(const media::TimeUnit& aDuration) {
|
||||
void MediaSource::SetDuration(double aDuration) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MSE_API("SetDuration(aDuration=%f)", aDuration.ToSeconds());
|
||||
MSE_API("SetDuration(aDuration=%f)", aDuration);
|
||||
mDecoder->SetMediaSourceDuration(aDuration);
|
||||
}
|
||||
|
||||
|
|
@ -391,8 +391,7 @@ void MediaSource::EndOfStream(
|
|||
SetReadyState(MediaSourceReadyState::Ended);
|
||||
mSourceBuffers->Ended();
|
||||
if (!aError.WasPassed()) {
|
||||
DurationChange(mSourceBuffers->GetHighestBufferedEndTime().ToBase(1000000),
|
||||
aRv);
|
||||
DurationChange(mSourceBuffers->GetHighestBufferedEndTime(), aRv);
|
||||
// Notify reader that all data is now available.
|
||||
mDecoder->Ended(true);
|
||||
return;
|
||||
|
|
@ -589,13 +588,12 @@ void MediaSource::QueueAsyncSimpleEvent(const char* aName) {
|
|||
mAbstractMainThread->Dispatch(event.forget());
|
||||
}
|
||||
|
||||
void MediaSource::DurationChange(const media::TimeUnit& aNewDuration,
|
||||
ErrorResult& aRv) {
|
||||
void MediaSource::DurationChange(double aNewDuration, ErrorResult& aRv) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MSE_DEBUG("DurationChange(aNewDuration=%s)", aNewDuration.ToString().get());
|
||||
MSE_DEBUG("DurationChange(aNewDuration=%f)", aNewDuration);
|
||||
|
||||
// 1. If the current value of duration is equal to new duration, then return.
|
||||
if (mDecoder->GetDuration() == aNewDuration.ToSeconds()) {
|
||||
if (mDecoder->GetDuration() == aNewDuration) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -609,43 +607,14 @@ void MediaSource::DurationChange(const media::TimeUnit& aNewDuration,
|
|||
|
||||
// 3. Let highest end time be the largest track buffer ranges end time across
|
||||
// all the track buffers across all SourceBuffer objects in sourceBuffers.
|
||||
media::TimeUnit highestEndTime = mSourceBuffers->HighestEndTime();
|
||||
double highestEndTime = mSourceBuffers->HighestEndTime();
|
||||
// 4. If new duration is less than highest end time, then
|
||||
// 4.1 Update new duration to equal highest end time.
|
||||
media::TimeUnit newDuration = std::max(aNewDuration, highestEndTime);
|
||||
aNewDuration = std::max(aNewDuration, highestEndTime);
|
||||
|
||||
// 5. Update the media duration to new duration and run the HTMLMediaElement
|
||||
// duration change algorithm.
|
||||
mDecoder->SetMediaSourceDuration(newDuration);
|
||||
}
|
||||
|
||||
void MediaSource::DurationChange(double aNewDuration, ErrorResult& aRv) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MSE_DEBUG("DurationChange(aNewDuration=%f)", aNewDuration);
|
||||
|
||||
// 1. If the current value of duration is equal to new duration, then return.
|
||||
if (mDecoder->GetDuration() == aNewDuration) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. If new duration is less than the highest starting presentation timestamp
|
||||
// of any buffered coded frames for all SourceBuffer objects in sourceBuffers,
|
||||
// then throw an InvalidStateError exception and abort these steps.
|
||||
if (aNewDuration < mSourceBuffers->HighestStartTime().ToSeconds()) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Let highest end time be the largest track buffer ranges end time across
|
||||
// all the track buffers across all SourceBuffer objects in sourceBuffers.
|
||||
double highestEndTime = mSourceBuffers->HighestEndTime().ToSeconds();
|
||||
// 4. If new duration is less than highest end time, then
|
||||
// 4.1 Update new duration to equal highest end time.
|
||||
double newDuration = std::max(aNewDuration, highestEndTime);
|
||||
|
||||
// 5. Update the media duration to new duration and run the HTMLMediaElement
|
||||
// duration change algorithm.
|
||||
mDecoder->SetMediaSourceDuration(newDuration);
|
||||
mDecoder->SetMediaSourceDuration(aNewDuration);
|
||||
}
|
||||
|
||||
already_AddRefed<Promise> MediaSource::MozDebugReaderData(ErrorResult& aRv) {
|
||||
|
|
|
|||
|
|
@ -139,11 +139,10 @@ class MediaSource final : public DOMEventTargetHelper,
|
|||
void DispatchSimpleEvent(const char* aName);
|
||||
void QueueAsyncSimpleEvent(const char* aName);
|
||||
|
||||
void DurationChange(const media::TimeUnit& aNewDuration, ErrorResult& aRv);
|
||||
void DurationChange(double aNewDuration, ErrorResult& aRv);
|
||||
|
||||
// SetDuration with no checks.
|
||||
void SetDuration(const media::TimeUnit& aDuration);
|
||||
void SetDuration(double aDuration);
|
||||
|
||||
typedef MozPromise<bool, MediaResult, /* IsExclusive = */ true>
|
||||
ActiveCompletionPromise;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
#include "base/process_util.h"
|
||||
#include "mozilla/Logging.h"
|
||||
#include "ExternalEngineStateMachine.h"
|
||||
#include "MediaDecoder.h"
|
||||
#include "MediaDecoderStateMachine.h"
|
||||
#include "MediaShutdownManager.h"
|
||||
#include "MediaSource.h"
|
||||
|
|
@ -80,15 +79,14 @@ nsresult MediaSourceDecoder::Load(nsIPrincipal* aPrincipal) {
|
|||
return CreateAndInitStateMachine(!mEnded);
|
||||
}
|
||||
|
||||
template <typename IntervalType>
|
||||
IntervalType MediaSourceDecoder::GetSeekableImpl() {
|
||||
media::TimeIntervals MediaSourceDecoder::GetSeekable() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (!mMediaSource) {
|
||||
NS_WARNING("MediaSource element isn't attached");
|
||||
return IntervalType();
|
||||
return media::TimeIntervals::Invalid();
|
||||
}
|
||||
|
||||
TimeIntervals seekable;
|
||||
media::TimeIntervals seekable;
|
||||
double duration = mMediaSource->Duration();
|
||||
if (std::isnan(duration)) {
|
||||
// Return empty range.
|
||||
|
|
@ -106,33 +104,18 @@ IntervalType MediaSourceDecoder::GetSeekableImpl() {
|
|||
// union ranges and abort these steps.
|
||||
seekable +=
|
||||
media::TimeInterval(unionRanges.GetStart(), unionRanges.GetEnd());
|
||||
return IntervalType(seekable);
|
||||
return seekable;
|
||||
}
|
||||
|
||||
if (!buffered.IsEmpty()) {
|
||||
seekable += media::TimeInterval(TimeUnit::Zero(), buffered.GetEnd());
|
||||
}
|
||||
} else {
|
||||
if constexpr (std::is_same<IntervalType, TimeRanges>::value) {
|
||||
// Common case: seekable in entire range of the media.
|
||||
return TimeRanges(TimeRange(0, duration));
|
||||
} else if constexpr (std::is_same<IntervalType, TimeIntervals>::value) {
|
||||
seekable += media::TimeInterval(TimeUnit::Zero(),
|
||||
mDuration.match(DurationToTimeUnit()));
|
||||
} else {
|
||||
MOZ_RELEASE_ASSERT(false);
|
||||
}
|
||||
seekable +=
|
||||
media::TimeInterval(TimeUnit::Zero(), TimeUnit::FromSeconds(duration));
|
||||
}
|
||||
MSE_DEBUG("ranges=%s", DumpTimeRanges(seekable).get());
|
||||
return IntervalType(seekable);
|
||||
}
|
||||
|
||||
media::TimeIntervals MediaSourceDecoder::GetSeekable() {
|
||||
return GetSeekableImpl<media::TimeIntervals>();
|
||||
}
|
||||
|
||||
media::TimeRanges MediaSourceDecoder::GetSeekableTimeRanges() {
|
||||
return GetSeekableImpl<media::TimeRanges>();
|
||||
return seekable;
|
||||
}
|
||||
|
||||
media::TimeIntervals MediaSourceDecoder::GetBuffered() {
|
||||
|
|
@ -220,30 +203,31 @@ void MediaSourceDecoder::AddSizeOfResources(ResourceSizes* aSizes) {
|
|||
}
|
||||
}
|
||||
|
||||
void MediaSourceDecoder::SetInitialDuration(const TimeUnit& aDuration) {
|
||||
void MediaSourceDecoder::SetInitialDuration(int64_t aDuration) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
// Only use the decoded duration if one wasn't already
|
||||
// set.
|
||||
if (!mMediaSource || !std::isnan(ExplicitDuration())) {
|
||||
return;
|
||||
}
|
||||
SetMediaSourceDuration(aDuration);
|
||||
}
|
||||
|
||||
void MediaSourceDecoder::SetMediaSourceDuration(const TimeUnit& aDuration) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!IsShutdown());
|
||||
if (aDuration.IsPositiveOrZero()) {
|
||||
SetExplicitDuration(ToMicrosecondResolution(aDuration.ToSeconds()));
|
||||
} else {
|
||||
SetExplicitDuration(PositiveInfinity<double>());
|
||||
double duration = aDuration;
|
||||
// A duration of -1 is +Infinity.
|
||||
if (aDuration >= 0) {
|
||||
duration /= USECS_PER_S;
|
||||
}
|
||||
SetMediaSourceDuration(duration);
|
||||
}
|
||||
|
||||
void MediaSourceDecoder::SetMediaSourceDuration(double aDuration) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!IsShutdown());
|
||||
if (aDuration >= 0) {
|
||||
int64_t checkedDuration;
|
||||
if (NS_FAILED(SecondsToUsecs(aDuration, checkedDuration))) {
|
||||
// INT64_MAX is used as infinity by the state machine.
|
||||
// We want a very bigger number, but not infinity.
|
||||
checkedDuration = INT64_MAX - 1;
|
||||
}
|
||||
SetExplicitDuration(aDuration);
|
||||
} else {
|
||||
SetExplicitDuration(PositiveInfinity<double>());
|
||||
|
|
@ -315,12 +299,12 @@ bool MediaSourceDecoder::CanPlayThroughImpl() {
|
|||
}
|
||||
// If we have data up to the mediasource's duration or 3s ahead, we can
|
||||
// assume that we can play without interruption.
|
||||
dom::SourceBufferList* sourceBuffers = mMediaSource->ActiveSourceBuffers();
|
||||
TimeUnit bufferedEnd = sourceBuffers->GetHighestBufferedEndTime();
|
||||
TimeIntervals buffered = GetBuffered();
|
||||
buffered.SetFuzz(MediaSourceDemuxer::EOS_FUZZ / 2);
|
||||
TimeUnit timeAhead =
|
||||
std::min(duration, currentPosition + TimeUnit::FromSeconds(3));
|
||||
TimeInterval interval(currentPosition, timeAhead);
|
||||
return bufferedEnd >= timeAhead;
|
||||
return buffered.ContainsWithStrictEnd(ClampIntervalToEnd(interval));
|
||||
}
|
||||
|
||||
TimeInterval MediaSourceDecoder::ClampIntervalToEnd(
|
||||
|
|
@ -330,7 +314,7 @@ TimeInterval MediaSourceDecoder::ClampIntervalToEnd(
|
|||
if (!mEnded) {
|
||||
return aInterval;
|
||||
}
|
||||
TimeUnit duration = mDuration.match(DurationToTimeUnit());
|
||||
TimeUnit duration = TimeUnit::FromSeconds(GetDuration());
|
||||
if (duration < aInterval.mStart) {
|
||||
return aInterval;
|
||||
}
|
||||
|
|
@ -340,6 +324,7 @@ TimeInterval MediaSourceDecoder::ClampIntervalToEnd(
|
|||
|
||||
void MediaSourceDecoder::NotifyInitDataArrived() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (mDemuxer) {
|
||||
mDemuxer->NotifyInitDataArrived();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ class MediaSourceDecoder : public MediaDecoder,
|
|||
|
||||
nsresult Load(nsIPrincipal* aPrincipal);
|
||||
media::TimeIntervals GetSeekable() override;
|
||||
media::TimeRanges GetSeekableTimeRanges() override;
|
||||
media::TimeIntervals GetBuffered() override;
|
||||
|
||||
void Shutdown() override;
|
||||
|
|
@ -44,8 +43,7 @@ class MediaSourceDecoder : public MediaDecoder,
|
|||
// Return the duration of the video in seconds.
|
||||
double GetDuration() override;
|
||||
|
||||
void SetInitialDuration(const media::TimeUnit& aDuration);
|
||||
void SetMediaSourceDuration(const media::TimeUnit& aDuration);
|
||||
void SetInitialDuration(int64_t aDuration);
|
||||
void SetMediaSourceDuration(double aDuration);
|
||||
|
||||
MediaSourceDemuxer* GetDemuxer() { return mDemuxer; }
|
||||
|
|
@ -77,10 +75,6 @@ class MediaSourceDecoder : public MediaDecoder,
|
|||
private:
|
||||
MediaDecoderStateMachineBase* CreateStateMachine(
|
||||
bool aDisableExternalEngine) override;
|
||||
|
||||
template <typename IntervalType>
|
||||
IntervalType GetSeekableImpl();
|
||||
|
||||
void DoSetMediaSourceDuration(double aDuration);
|
||||
media::TimeInterval ClampIntervalToEnd(const media::TimeInterval& aInterval);
|
||||
bool CanPlayThroughImpl() override;
|
||||
|
|
|
|||
|
|
@ -28,22 +28,4 @@ nsCString DumpTimeRanges(const media::TimeIntervals& aRanges) {
|
|||
return dump;
|
||||
}
|
||||
|
||||
nsCString DumpTimeRangesRaw(const media::TimeIntervals& aRanges) {
|
||||
nsCString dump;
|
||||
|
||||
dump = "[";
|
||||
|
||||
for (uint32_t i = 0; i < aRanges.Length(); ++i) {
|
||||
if (i > 0) {
|
||||
dump += ", ";
|
||||
}
|
||||
dump += nsPrintfCString("(%s, %s)", aRanges.Start(i).ToString().get(),
|
||||
aRanges.End(i).ToString().get());
|
||||
}
|
||||
|
||||
dump += "]";
|
||||
|
||||
return dump;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
namespace mozilla {
|
||||
|
||||
nsCString DumpTimeRanges(const media::TimeIntervals& aRanges);
|
||||
nsCString DumpTimeRangesRaw(const media::TimeIntervals& aRanges);
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
|
|||
|
|
@ -102,11 +102,6 @@ void SourceBuffer::SetTimestampOffset(double aTimestampOffset,
|
|||
}
|
||||
}
|
||||
|
||||
media::TimeIntervals SourceBuffer::GetBufferedIntervals() {
|
||||
MOZ_ASSERT(mTrackBuffersManager);
|
||||
return mTrackBuffersManager->Buffered();
|
||||
}
|
||||
|
||||
TimeRanges* SourceBuffer::GetBuffered(ErrorResult& aRv) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
// http://w3c.github.io/media-source/index.html#widl-SourceBuffer-buffered
|
||||
|
|
@ -129,15 +124,13 @@ TimeRanges* SourceBuffer::GetBuffered(ErrorResult& aRv) {
|
|||
// as the current value of this attribute, then update the current value of
|
||||
// this attribute to intersection ranges.
|
||||
if (rangeChanged) {
|
||||
mBuffered = new TimeRanges(ToSupports(this),
|
||||
intersection.ToMicrosecondResolution());
|
||||
mBuffered = new TimeRanges(ToSupports(this), intersection);
|
||||
}
|
||||
// 6. Return the current value of this attribute.
|
||||
return mBuffered;
|
||||
}
|
||||
|
||||
media::TimeIntervals SourceBuffer::GetTimeIntervals() {
|
||||
MOZ_ASSERT(mTrackBuffersManager);
|
||||
return mTrackBuffersManager->Buffered();
|
||||
}
|
||||
|
||||
|
|
@ -539,9 +532,9 @@ void SourceBuffer::AbortUpdating() {
|
|||
void SourceBuffer::CheckEndTime() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
// Check if we need to update mMediaSource duration
|
||||
TimeUnit endTime = mCurrentAttributes.GetGroupEndTimestamp();
|
||||
double endTime = mCurrentAttributes.GetGroupEndTimestamp().ToSeconds();
|
||||
double duration = mMediaSource->Duration();
|
||||
if (!std::isnan(duration) && endTime > TimeUnit::FromSeconds(duration)) {
|
||||
if (endTime > duration) {
|
||||
mMediaSource->SetDuration(endTime);
|
||||
}
|
||||
}
|
||||
|
|
@ -715,23 +708,32 @@ already_AddRefed<MediaByteBuffer> SourceBuffer::PrepareAppend(
|
|||
return data.forget();
|
||||
}
|
||||
|
||||
TimeUnit SourceBuffer::GetBufferedEnd() {
|
||||
double SourceBuffer::GetBufferedStart() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
ErrorResult dummy;
|
||||
media::TimeIntervals intervals = GetBufferedIntervals();
|
||||
return intervals.GetEnd();
|
||||
RefPtr<TimeRanges> ranges = GetBuffered(dummy);
|
||||
return ranges->Length() > 0 ? ranges->GetStartTime() : 0;
|
||||
}
|
||||
|
||||
TimeUnit SourceBuffer::HighestStartTime() {
|
||||
double SourceBuffer::GetBufferedEnd() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mTrackBuffersManager);
|
||||
return mTrackBuffersManager->HighestStartTime();
|
||||
ErrorResult dummy;
|
||||
RefPtr<TimeRanges> ranges = GetBuffered(dummy);
|
||||
return ranges->Length() > 0 ? ranges->GetEndTime() : 0;
|
||||
}
|
||||
|
||||
TimeUnit SourceBuffer::HighestEndTime() {
|
||||
double SourceBuffer::HighestStartTime() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mTrackBuffersManager);
|
||||
return mTrackBuffersManager->HighestEndTime();
|
||||
return mTrackBuffersManager
|
||||
? mTrackBuffersManager->HighestStartTime().ToSeconds()
|
||||
: 0.0;
|
||||
}
|
||||
|
||||
double SourceBuffer::HighestEndTime() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return mTrackBuffersManager
|
||||
? mTrackBuffersManager->HighestEndTime().ToSeconds()
|
||||
: 0.0;
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(SourceBuffer)
|
||||
|
|
|
|||
|
|
@ -120,10 +120,10 @@ class SourceBuffer final : public DOMEventTargetHelper,
|
|||
|
||||
void Ended();
|
||||
|
||||
media::TimeIntervals GetBufferedIntervals();
|
||||
media::TimeUnit GetBufferedEnd();
|
||||
media::TimeUnit HighestStartTime();
|
||||
media::TimeUnit HighestEndTime();
|
||||
double GetBufferedStart();
|
||||
double GetBufferedEnd();
|
||||
double HighestStartTime();
|
||||
double HighestEndTime();
|
||||
|
||||
// Runs the range removal algorithm as defined by the MSE spec.
|
||||
void RangeRemoval(double aStart, double aEnd);
|
||||
|
|
|
|||
|
|
@ -29,8 +29,6 @@ extern mozilla::LogModule* GetMediaSourceAPILog();
|
|||
struct JSContext;
|
||||
class JSObject;
|
||||
|
||||
using TimeUnit = mozilla::media::TimeUnit;
|
||||
|
||||
namespace mozilla::dom {
|
||||
|
||||
SourceBufferList::~SourceBufferList() = default;
|
||||
|
|
@ -117,9 +115,9 @@ void SourceBufferList::Ended() {
|
|||
}
|
||||
}
|
||||
|
||||
TimeUnit SourceBufferList::GetHighestBufferedEndTime() {
|
||||
double SourceBufferList::GetHighestBufferedEndTime() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
TimeUnit highestEndTime = TimeUnit::Zero();
|
||||
double highestEndTime = 0;
|
||||
for (uint32_t i = 0; i < mSourceBuffers.Length(); ++i) {
|
||||
highestEndTime =
|
||||
std::max(highestEndTime, mSourceBuffers[i]->GetBufferedEnd());
|
||||
|
|
@ -149,9 +147,9 @@ SourceBufferList::SourceBufferList(MediaSource* aMediaSource)
|
|||
|
||||
MediaSource* SourceBufferList::GetParentObject() const { return mMediaSource; }
|
||||
|
||||
TimeUnit SourceBufferList::HighestStartTime() {
|
||||
double SourceBufferList::HighestStartTime() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
TimeUnit highestStartTime = TimeUnit::Zero();
|
||||
double highestStartTime = 0;
|
||||
for (auto& sourceBuffer : mSourceBuffers) {
|
||||
highestStartTime =
|
||||
std::max(sourceBuffer->HighestStartTime(), highestStartTime);
|
||||
|
|
@ -159,9 +157,9 @@ TimeUnit SourceBufferList::HighestStartTime() {
|
|||
return highestStartTime;
|
||||
}
|
||||
|
||||
TimeUnit SourceBufferList::HighestEndTime() {
|
||||
double SourceBufferList::HighestEndTime() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
TimeUnit highestEndTime = TimeUnit::Zero();
|
||||
double highestEndTime = 0;
|
||||
for (auto& sourceBuffer : mSourceBuffers) {
|
||||
highestEndTime = std::max(sourceBuffer->HighestEndTime(), highestEndTime);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ class SourceBufferList final : public DOMEventTargetHelper {
|
|||
void Ended();
|
||||
|
||||
// Returns the highest end time of any of the Sourcebuffers.
|
||||
media::TimeUnit GetHighestBufferedEndTime();
|
||||
double GetHighestBufferedEndTime();
|
||||
|
||||
// Append a SourceBuffer to the list. No event is fired.
|
||||
void AppendSimple(SourceBuffer* aSourceBuffer);
|
||||
|
|
@ -88,8 +88,8 @@ class SourceBufferList final : public DOMEventTargetHelper {
|
|||
// No event is fired and no action is performed on the sourcebuffers.
|
||||
void ClearSimple();
|
||||
|
||||
media::TimeUnit HighestStartTime();
|
||||
media::TimeUnit HighestEndTime();
|
||||
double HighestStartTime();
|
||||
double HighestEndTime();
|
||||
|
||||
private:
|
||||
~SourceBufferList();
|
||||
|
|
|
|||
|
|
@ -631,11 +631,11 @@ bool TrackBuffersManager::CodedFrameRemoval(TimeInterval aInterval) {
|
|||
#if DEBUG
|
||||
if (HasVideo()) {
|
||||
MSE_DEBUG("before video ranges=%s",
|
||||
DumpTimeRangesRaw(mVideoTracks.mBufferedRanges).get());
|
||||
DumpTimeRanges(mVideoTracks.mBufferedRanges).get());
|
||||
}
|
||||
if (HasAudio()) {
|
||||
MSE_DEBUG("before audio ranges=%s",
|
||||
DumpTimeRangesRaw(mAudioTracks.mBufferedRanges).get());
|
||||
DumpTimeRanges(mAudioTracks.mBufferedRanges).get());
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
@ -796,11 +796,11 @@ void TrackBuffersManager::UpdateBufferedRanges() {
|
|||
#if DEBUG
|
||||
if (HasVideo()) {
|
||||
MSE_DEBUG("after video ranges=%s",
|
||||
DumpTimeRangesRaw(mVideoTracks.mBufferedRanges).get());
|
||||
DumpTimeRanges(mVideoTracks.mBufferedRanges).get());
|
||||
}
|
||||
if (HasAudio()) {
|
||||
MSE_DEBUG("after audio ranges=%s",
|
||||
DumpTimeRangesRaw(mAudioTracks.mBufferedRanges).get());
|
||||
DumpTimeRanges(mAudioTracks.mBufferedRanges).get());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
@ -869,7 +869,8 @@ void TrackBuffersManager::SegmentParserLoop() {
|
|||
mSourceBufferAttributes->GetAppendState() ==
|
||||
AppendState::PARSING_MEDIA_SEGMENT);
|
||||
|
||||
TimeUnit start, end;
|
||||
int64_t start = 0;
|
||||
int64_t end = 0;
|
||||
MediaResult newData = NS_ERROR_NOT_AVAILABLE;
|
||||
|
||||
if (mSourceBufferAttributes->GetAppendState() ==
|
||||
|
|
@ -916,7 +917,7 @@ void TrackBuffersManager::SegmentParserLoop() {
|
|||
// monotonically increasing data.
|
||||
if (mNewMediaSegmentStarted) {
|
||||
if (NS_SUCCEEDED(newData) && mLastParsedEndTime.isSome() &&
|
||||
start < mLastParsedEndTime.ref()) {
|
||||
start < mLastParsedEndTime.ref().ToMicroseconds()) {
|
||||
MSE_DEBUG("Re-creating demuxer");
|
||||
ResetDemuxingState();
|
||||
return;
|
||||
|
|
@ -1119,7 +1120,7 @@ void TrackBuffersManager::OnDemuxerResetDone(const MediaResult& aResult) {
|
|||
if (mPendingInputBuffer) {
|
||||
// We had a partial media segment header stashed aside.
|
||||
// Reparse its content so we can continue parsing the current input buffer.
|
||||
TimeUnit start, end;
|
||||
int64_t start, end;
|
||||
mParser->ParseStartAndEndTimestamps(*mPendingInputBuffer, start, end);
|
||||
mProcessedInput += mPendingInputBuffer->Length();
|
||||
}
|
||||
|
|
@ -1270,16 +1271,17 @@ void TrackBuffersManager::OnDemuxerInitDone(const MediaResult& aResult) {
|
|||
info.mAudio.mTrackId = 1;
|
||||
}
|
||||
|
||||
TimeUnit videoDuration = numVideos ? info.mVideo.mDuration : TimeUnit::Zero();
|
||||
TimeUnit audioDuration = numAudios ? info.mAudio.mDuration : TimeUnit::Zero();
|
||||
int64_t videoDuration =
|
||||
numVideos ? info.mVideo.mDuration.ToMicroseconds() : 0;
|
||||
int64_t audioDuration =
|
||||
numAudios ? info.mAudio.mDuration.ToMicroseconds() : 0;
|
||||
|
||||
TimeUnit duration = std::max(videoDuration, audioDuration);
|
||||
int64_t duration = std::max(videoDuration, audioDuration);
|
||||
// 1. Update the duration attribute if it currently equals NaN.
|
||||
// Those steps are performed by the MediaSourceDecoder::SetInitialDuration
|
||||
mAbstractMainThread->Dispatch(NewRunnableMethod<TimeUnit>(
|
||||
mAbstractMainThread->Dispatch(NewRunnableMethod<int64_t>(
|
||||
"MediaSourceDecoder::SetInitialDuration", mParentDecoder.get(),
|
||||
&MediaSourceDecoder::SetInitialDuration,
|
||||
!duration.IsZero() ? duration : TimeUnit::FromInfinity()));
|
||||
&MediaSourceDecoder::SetInitialDuration, duration ? duration : -1));
|
||||
|
||||
// 2. If the initialization segment has no audio, video, or text tracks, then
|
||||
// run the append error algorithm with the decode error parameter set to true
|
||||
|
|
@ -1893,9 +1895,7 @@ void TrackBuffersManager::ProcessFrames(TrackBuffer& aSamples,
|
|||
TimeUnit sampleTime = sample->mTime;
|
||||
TimeUnit sampleTimecode = sample->mTimecode;
|
||||
TimeUnit sampleDuration = sample->mDuration;
|
||||
// Keep the timestamp, set by js, in the time base of the container.
|
||||
TimeUnit timestampOffset =
|
||||
mSourceBufferAttributes->GetTimestampOffset().ToBase(sample->mTime);
|
||||
TimeUnit timestampOffset = mSourceBufferAttributes->GetTimestampOffset();
|
||||
|
||||
TimeInterval sampleInterval =
|
||||
mSourceBufferAttributes->mGenerateTimestamps
|
||||
|
|
@ -1920,6 +1920,7 @@ void TrackBuffersManager::ProcessFrames(TrackBuffer& aSamples,
|
|||
// is less than last decode timestamp: OR If last decode timestamp for track
|
||||
// buffer is set and the difference between decode timestamp and last decode
|
||||
// timestamp is greater than 2 times last frame duration:
|
||||
|
||||
if (needDiscontinuityCheck && trackBuffer.mLastDecodeTimestamp.isSome() &&
|
||||
(decodeTimestamp < trackBuffer.mLastDecodeTimestamp.ref() ||
|
||||
(decodeTimestamp - trackBuffer.mLastDecodeTimestamp.ref() >
|
||||
|
|
@ -2463,7 +2464,7 @@ void TrackBuffersManager::RecreateParser(bool aReuseInitData) {
|
|||
mParser = ContainerParser::CreateForMIMEType(mType);
|
||||
DDLINKCHILD("parser", mParser.get());
|
||||
if (aReuseInitData && mInitData) {
|
||||
TimeUnit start, end;
|
||||
int64_t start, end;
|
||||
mParser->ParseStartAndEndTimestamps(MediaSpan(mInitData), start, end);
|
||||
mProcessedInput = mInitData->Length();
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
#include "mozilla/gtest/MozAssertions.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using TimeUnit = mozilla::media::TimeUnit;
|
||||
|
||||
TEST(ContainerParser, MIMETypes)
|
||||
{
|
||||
|
|
@ -82,8 +81,8 @@ TEST(ContainerParser, ADTSHeader)
|
|||
header = make_adts_header();
|
||||
EXPECT_FALSE(NS_SUCCEEDED(parser->IsMediaSegmentPresent(MediaSpan(header))))
|
||||
<< "Found media segment when there was just a header.";
|
||||
TimeUnit start;
|
||||
TimeUnit end;
|
||||
int64_t start = 0;
|
||||
int64_t end = 0;
|
||||
EXPECT_TRUE(NS_FAILED(
|
||||
parser->ParseStartAndEndTimestamps(MediaSpan(header), start, end)));
|
||||
|
||||
|
|
@ -123,13 +122,13 @@ TEST(ContainerParser, ADTSBlankMedia)
|
|||
<< "Rejected a valid header.";
|
||||
EXPECT_TRUE(NS_SUCCEEDED(parser->IsMediaSegmentPresent(MediaSpan(header))))
|
||||
<< "Rejected a full (but zeroed) media segment.";
|
||||
TimeUnit start;
|
||||
TimeUnit end;
|
||||
int64_t start = 0;
|
||||
int64_t end = 0;
|
||||
// We don't report timestamps from ADTS.
|
||||
EXPECT_TRUE(NS_FAILED(
|
||||
parser->ParseStartAndEndTimestamps(MediaSpan(header), start, end)));
|
||||
EXPECT_TRUE(start.IsZero());
|
||||
EXPECT_TRUE(end.IsZero());
|
||||
EXPECT_EQ(start, 0);
|
||||
EXPECT_EQ(end, 0);
|
||||
|
||||
// Verify the parser calculated header and packet data boundaries.
|
||||
EXPECT_TRUE(parser->HasInitData());
|
||||
|
|
|
|||
|
|
@ -209,5 +209,6 @@ skip-if = os == 'win' # bug 1487973,
|
|||
[test_WaitingOnMissingData_mp4.html]
|
||||
[test_WaitingOnMissingDataEnded_mp4.html]
|
||||
[test_WaitingToEndedTransition_mp4.html]
|
||||
skip-if = (os == 'linux') # Bug 1495167
|
||||
[test_WebMTagsBeforeCluster.html]
|
||||
[test_WMFUnmatchedAudioDataTime.html]
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ runWithMSE(async (ms, v) => {
|
|||
await fetchAndLoad(sb, "bipbop/bipbop_video", ["2"], ".m4s");
|
||||
is(v.buffered.length, 1, "Continuous buffered range");
|
||||
is(v.buffered.start(0), 0, "Buffered range starts at 0");
|
||||
ok(sb.timestampOffset >= 0, "SourceBuffer.timestampOffset set to allow continuous range");
|
||||
ok(sb.timestampOffset > 0, "SourceBuffer.timestampOffset set to allow continuous range");
|
||||
SimpleTest.finish();
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -311,7 +311,6 @@ UNIFIED_SOURCES += [
|
|||
"QueueObject.cpp",
|
||||
"ReaderProxy.cpp",
|
||||
"SeekJob.cpp",
|
||||
"TimeUnits.cpp",
|
||||
"Tracing.cpp",
|
||||
"VideoFrameContainer.cpp",
|
||||
"VideoPlaybackQuality.cpp",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ extern mozilla::LazyLogModule gMediaDemuxerLog;
|
|||
#define MP3LOGV(msg, ...) \
|
||||
DDMOZ_LOG(gMediaDemuxerLog, LogLevel::Verbose, msg, ##__VA_ARGS__)
|
||||
|
||||
using mozilla::BufferReader;
|
||||
using mozilla::media::TimeInterval;
|
||||
using mozilla::media::TimeIntervals;
|
||||
using mozilla::media::TimeUnit;
|
||||
|
|
@ -129,9 +130,10 @@ bool MP3TrackDemuxer::Init() {
|
|||
mInfo->mCodecSpecificConfig =
|
||||
AudioCodecSpecificVariant{std::move(mp3CodecData)};
|
||||
|
||||
MP3LOG("Init mInfo={mRate=%d mChannels=%d mBitDepth=%d mDuration=%s (%lfs)}",
|
||||
MP3LOG("Init mInfo={mRate=%d mChannels=%d mBitDepth=%d mDuration=%" PRId64
|
||||
"}",
|
||||
mInfo->mRate, mInfo->mChannels, mInfo->mBitDepth,
|
||||
mInfo->mDuration.ToString().get(), mInfo->mDuration.ToSeconds());
|
||||
mInfo->mDuration.ToMicroseconds());
|
||||
|
||||
return mSamplesPerSecond && mChannels;
|
||||
}
|
||||
|
|
@ -180,13 +182,15 @@ TimeUnit MP3TrackDemuxer::FastSeek(const TimeUnit& aTime) {
|
|||
mFrameIndex, mOffset);
|
||||
|
||||
const auto& vbr = mParser.VBRInfo();
|
||||
if (aTime.IsZero()) {
|
||||
if (!aTime.ToMicroseconds()) {
|
||||
// Quick seek to the beginning of the stream.
|
||||
mFrameIndex = 0;
|
||||
} else if (vbr.IsTOCPresent() && Duration() &&
|
||||
*Duration() != TimeUnit::Zero()) {
|
||||
// Use TOC for more precise seeking.
|
||||
mFrameIndex = FrameIndexFromOffset(vbr.Offset(aTime, Duration().value()));
|
||||
const float durationFrac = static_cast<float>(aTime.ToMicroseconds()) /
|
||||
Duration()->ToMicroseconds();
|
||||
mFrameIndex = FrameIndexFromOffset(vbr.Offset(durationFrac));
|
||||
} else if (AverageFrameLength() > 0) {
|
||||
mFrameIndex = FrameIndexFromTime(aTime);
|
||||
}
|
||||
|
|
@ -215,7 +219,7 @@ TimeUnit MP3TrackDemuxer::ScanUntil(const TimeUnit& aTime) {
|
|||
aTime.ToMicroseconds(), AverageFrameLength(), mNumParsedFrames,
|
||||
mFrameIndex, mOffset);
|
||||
|
||||
if (aTime.IsZero()) {
|
||||
if (!aTime.ToMicroseconds()) {
|
||||
return FastSeek(aTime);
|
||||
}
|
||||
|
||||
|
|
@ -353,7 +357,7 @@ media::NullableTimeUnit MP3TrackDemuxer::Duration() const {
|
|||
if (numAudioFrames) {
|
||||
// VBR headers don't include the VBR header frame.
|
||||
numFrames = numAudioFrames.value() + 1;
|
||||
return Some(Duration(numFrames) - (EncoderDelay() + Padding()));
|
||||
return Some(Duration(numFrames));
|
||||
}
|
||||
|
||||
const int64_t streamLen = StreamLength();
|
||||
|
|
@ -365,34 +369,30 @@ media::NullableTimeUnit MP3TrackDemuxer::Duration() const {
|
|||
// since some live radio will give an opening remark before playing music
|
||||
// and the duration of the opening talk can be calculated by numAudioFrames.
|
||||
|
||||
int64_t size = streamLen - mFirstFrameOffset;
|
||||
const int64_t size = streamLen - mFirstFrameOffset;
|
||||
MOZ_ASSERT(size);
|
||||
|
||||
if (mParser.ID3v1MetadataFound() && size > 128) {
|
||||
size -= 128;
|
||||
}
|
||||
|
||||
// If it's CBR, calculate the duration by bitrate.
|
||||
if (!mParser.VBRInfo().IsValid()) {
|
||||
const uint32_t bitrate = mParser.CurrentFrame().Header().Bitrate();
|
||||
const int32_t bitrate = mParser.CurrentFrame().Header().Bitrate();
|
||||
return Some(
|
||||
media::TimeUnit::FromSeconds(static_cast<double>(size) * 8 / bitrate));
|
||||
}
|
||||
|
||||
if (AverageFrameLength() > 0) {
|
||||
numFrames = std::lround(AssertedCast<double>(size) / AverageFrameLength());
|
||||
numFrames = size / AverageFrameLength();
|
||||
}
|
||||
|
||||
return Some(Duration(numFrames) - (EncoderDelay() + Padding()));
|
||||
return Some(Duration(numFrames));
|
||||
}
|
||||
|
||||
TimeUnit MP3TrackDemuxer::Duration(int64_t aNumFrames) const {
|
||||
if (!mSamplesPerSecond) {
|
||||
return TimeUnit::Invalid();
|
||||
return TimeUnit::FromMicroseconds(-1);
|
||||
}
|
||||
|
||||
const int64_t frameCount = aNumFrames * mSamplesPerFrame;
|
||||
return TimeUnit(frameCount, mSamplesPerSecond);
|
||||
const double usPerFrame = USECS_PER_S * mSamplesPerFrame / mSamplesPerSecond;
|
||||
return TimeUnit::FromMicroseconds(aNumFrames * usPerFrame);
|
||||
}
|
||||
|
||||
MediaByteRange MP3TrackDemuxer::FindFirstFrame() {
|
||||
|
|
@ -528,10 +528,7 @@ MediaByteRange MP3TrackDemuxer::FindNextFrame() {
|
|||
|
||||
if ((mOffset - startOffset > maxSkippableBytes) ||
|
||||
(read = Read(buffer, mOffset, BUFFER_SIZE)) == 0) {
|
||||
MP3LOG(
|
||||
"FindNext() EOS or exceeded maxSkippeableBytes without a frame "
|
||||
"(read: %d)",
|
||||
read);
|
||||
MP3LOG("FindNext() EOS or exceeded maxSkippeableBytes without a frame");
|
||||
// This is not a valid MPEG audio stream or we've reached EOS, give up.
|
||||
break;
|
||||
}
|
||||
|
|
@ -549,7 +546,6 @@ MediaByteRange MP3TrackDemuxer::FindNextFrame() {
|
|||
|
||||
if (foundFrame && mParser.FirstFrame().Length() &&
|
||||
!VerifyFrameConsistency(mParser.FirstFrame(), mParser.CurrentFrame())) {
|
||||
MP3LOG("Skipping frame");
|
||||
// We've likely hit a false-positive, ignore it and proceed with the
|
||||
// search for the next valid frame.
|
||||
foundFrame = false;
|
||||
|
|
@ -564,23 +560,24 @@ MediaByteRange MP3TrackDemuxer::FindNextFrame() {
|
|||
}
|
||||
}
|
||||
|
||||
mEOS = frameHeaderOffset + mParser.CurrentFrame().Length() + BUFFER_SIZE >
|
||||
StreamLength();
|
||||
|
||||
if (!foundFrame || !mParser.CurrentFrame().Length()) {
|
||||
MP3LOG("FindNext() Exit foundFrame=%d mParser.CurrentFrame().Length()=%d ",
|
||||
foundFrame, mParser.CurrentFrame().Length());
|
||||
return {0, 0};
|
||||
}
|
||||
|
||||
if (frameHeaderOffset + mParser.CurrentFrame().Length() + BUFFER_SIZE >
|
||||
StreamLength()) {
|
||||
mEOS = true;
|
||||
}
|
||||
|
||||
MP3LOGV("FindNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
|
||||
" mFrameIndex=%" PRId64 " frameHeaderOffset=%" PRId64
|
||||
" mTotalFrameLen=%" PRIu64
|
||||
" mSamplesPerFrame=%d mSamplesPerSecond=%d"
|
||||
" mChannels=%d, mEOS=%s",
|
||||
" mChannels=%d",
|
||||
mOffset, mNumParsedFrames, mFrameIndex, frameHeaderOffset,
|
||||
mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond, mChannels,
|
||||
mEOS ? "true" : "false");
|
||||
mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond, mChannels);
|
||||
|
||||
return {frameHeaderOffset,
|
||||
frameHeaderOffset + mParser.CurrentFrame().Length()};
|
||||
|
|
@ -604,18 +601,6 @@ bool MP3TrackDemuxer::SkipNextFrame(const MediaByteRange& aRange) {
|
|||
return true;
|
||||
}
|
||||
|
||||
media::TimeUnit MP3TrackDemuxer::EncoderDelay() const {
|
||||
return media::TimeUnit(mEncoderDelay, mSamplesPerSecond);
|
||||
}
|
||||
|
||||
uint32_t MP3TrackDemuxer::EncoderDelayFrames() const { return mEncoderDelay; }
|
||||
|
||||
media::TimeUnit MP3TrackDemuxer::Padding() const {
|
||||
return media::TimeUnit(mEncoderPadding, mSamplesPerSecond);
|
||||
}
|
||||
|
||||
uint32_t MP3TrackDemuxer::PaddingFrames() const { return mEncoderPadding; }
|
||||
|
||||
already_AddRefed<MediaRawData> MP3TrackDemuxer::GetNextFrame(
|
||||
const MediaByteRange& aRange) {
|
||||
MP3LOG("GetNext() Begin({mStart=%" PRId64 " Length()=%" PRId64 "})",
|
||||
|
|
@ -643,6 +628,15 @@ already_AddRefed<MediaRawData> MP3TrackDemuxer::GetNextFrame(
|
|||
|
||||
UpdateState(aRange);
|
||||
|
||||
frame->mTime = Duration(mFrameIndex - 1);
|
||||
frame->mDuration = Duration(1);
|
||||
frame->mTimecode = frame->mTime;
|
||||
frame->mKeyframe = true;
|
||||
frame->mEOS = mEOS;
|
||||
|
||||
MOZ_ASSERT(!frame->mTime.IsNegative());
|
||||
MOZ_ASSERT(frame->mDuration.IsPositive());
|
||||
|
||||
if (mNumParsedFrames == 1) {
|
||||
// First frame parsed, let's read VBR info if available.
|
||||
BufferReader reader(frame->Data(), frame->Size());
|
||||
|
|
@ -651,77 +645,14 @@ already_AddRefed<MediaRawData> MP3TrackDemuxer::GetNextFrame(
|
|||
if (mParser.ParseVBRHeader(&reader)) {
|
||||
// Parsing was successful
|
||||
if (mParser.VBRInfo().Type() == FrameParser::VBRHeader::XING) {
|
||||
MP3LOG("XING header present, skipping encoder delay (%u frames)",
|
||||
mParser.VBRInfo().EncoderDelay());
|
||||
MP3LOGV("XING header present, skipping encoder delay (%u frames)",
|
||||
mParser.VBRInfo().EncoderDelay());
|
||||
mEncoderDelay = mParser.VBRInfo().EncoderDelay();
|
||||
mEncoderPadding = mParser.VBRInfo().EncoderPadding();
|
||||
if (mEncoderDelay == 0) {
|
||||
// Skip the VBR frame + the decoder delay, that is always 529 frames
|
||||
// in practice for the decoder we're using.
|
||||
mEncoderDelay = mSamplesPerFrame + 529;
|
||||
MP3LOG(
|
||||
"No explicit delay present in vbr header, delay is assumed to be "
|
||||
"%u frames\n",
|
||||
mEncoderDelay);
|
||||
}
|
||||
} else if (mParser.VBRInfo().Type() == FrameParser::VBRHeader::VBRI) {
|
||||
MP3LOG("VBRI header present, skipping encoder delay (%u frames)",
|
||||
mParser.VBRInfo().EncoderDelay());
|
||||
mEncoderDelay = mParser.VBRInfo().EncoderDelay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TimeUnit rawPts = Duration(mFrameIndex - 1) - EncoderDelay();
|
||||
TimeUnit rawDuration = Duration(1);
|
||||
TimeUnit rawEnd = rawPts + rawDuration;
|
||||
|
||||
frame->mTime = std::max(TimeUnit::Zero(mSamplesPerSecond), rawPts);
|
||||
|
||||
frame->mDuration = Duration(1);
|
||||
frame->mTimecode = frame->mTime;
|
||||
frame->mKeyframe = true;
|
||||
frame->mEOS = mEOS;
|
||||
|
||||
// Handle decoder delay. A packet must be trimmed if its pts, adjusted for
|
||||
// decoder delay, is negative. A packet can be trimmed entirely.
|
||||
if (rawPts.IsNegative()) {
|
||||
frame->mDuration =
|
||||
std::max(TimeUnit::Zero(mSamplesPerSecond), rawEnd - frame->mTime);
|
||||
}
|
||||
|
||||
// It's possible to create an mp3 file that has a padding value that somehow
|
||||
// spans multiple packets. In that case the duration is probably known,
|
||||
// because it's probably a VBR file with a XING header (that has a duration
|
||||
// field). Use the duration to be able to set the correct duration on
|
||||
// packets that aren't the last one.
|
||||
// For most files, the padding is less than a packet, it's simply substracted.
|
||||
if (mParser.VBRInfo().Type() == FrameParser::VBRHeader::XING &&
|
||||
Padding().IsPositive() &&
|
||||
frame->GetEndTime() > Duration().valueOr(TimeUnit::FromInfinity())) {
|
||||
TimeUnit duration = Duration().value();
|
||||
TimeUnit inPaddingZone = frame->GetEndTime() - duration;
|
||||
TimeUnit originalEnd = frame->GetEndTime();
|
||||
TimeUnit originalPts = frame->mTime;
|
||||
frame->mDuration -= inPaddingZone;
|
||||
// Packet is entirely padding and will be completely discarded by the
|
||||
// decoder.
|
||||
if (frame->mDuration.IsNegative()) {
|
||||
frame->mDuration = TimeUnit::Zero(mSamplesPerSecond);
|
||||
}
|
||||
MP3LOG(
|
||||
"Found padding spanning multiple packets -- trimming [%s, %s] to "
|
||||
"[%s,%s] (stream duration: %s)",
|
||||
originalPts.ToString().get(), originalEnd.ToString().get(),
|
||||
frame->mTime.ToString().get(), frame->GetEndTime().ToString().get(),
|
||||
duration.ToString().get());
|
||||
} else if (frame->mEOS && Padding() <= frame->mDuration) {
|
||||
frame->mDuration -= Padding();
|
||||
MOZ_ASSERT(frame->mDuration.IsPositiveOrZero());
|
||||
MP3LOG("Trimming last packet %s to [%s,%s]", Padding().ToString().get(),
|
||||
frame->mTime.ToString().get(), frame->GetEndTime().ToString().get());
|
||||
}
|
||||
|
||||
MP3LOGV("GetNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
|
||||
" mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64
|
||||
" mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d, mEOS=%s",
|
||||
|
|
@ -729,22 +660,6 @@ already_AddRefed<MediaRawData> MP3TrackDemuxer::GetNextFrame(
|
|||
mSamplesPerFrame, mSamplesPerSecond, mChannels,
|
||||
mEOS ? "true" : "false");
|
||||
|
||||
// It's possible for the duration of a frame to be zero if the frame is to be
|
||||
// trimmed entirely because it's fully comprised of decoder delay samples.
|
||||
// This is common at the beginning of an stream.
|
||||
MOZ_ASSERT(frame->mDuration.IsPositiveOrZero());
|
||||
|
||||
MP3LOG("Packet demuxed: pts [%s, %s] (duration: %s)",
|
||||
frame->mTime.ToString().get(), frame->GetEndTime().ToString().get(),
|
||||
frame->mDuration.ToString().get());
|
||||
|
||||
// Indicate original packet information to trim after decoding.
|
||||
if (frame->mDuration != rawDuration) {
|
||||
frame->mOriginalPresentationWindow = Some(TimeInterval{rawPts, rawEnd});
|
||||
MP3LOG("Total packet time excluding trimming: [%s, %s]",
|
||||
rawPts.ToString().get(), rawEnd.ToString().get());
|
||||
}
|
||||
|
||||
return frame.forget();
|
||||
}
|
||||
|
||||
|
|
@ -756,9 +671,7 @@ int64_t MP3TrackDemuxer::OffsetFromFrameIndex(int64_t aFrameIndex) const {
|
|||
offset = mFirstFrameOffset + aFrameIndex * vbr.NumBytes().value() /
|
||||
vbr.NumAudioFrames().value();
|
||||
} else if (AverageFrameLength() > 0) {
|
||||
offset = mFirstFrameOffset +
|
||||
AssertedCast<int64_t>(static_cast<float>(aFrameIndex) *
|
||||
AverageFrameLength());
|
||||
offset = mFirstFrameOffset + aFrameIndex * AverageFrameLength();
|
||||
}
|
||||
|
||||
MP3LOGV("OffsetFromFrameIndex(%" PRId64 ") -> %" PRId64, aFrameIndex, offset);
|
||||
|
|
@ -770,14 +683,11 @@ int64_t MP3TrackDemuxer::FrameIndexFromOffset(int64_t aOffset) const {
|
|||
const auto& vbr = mParser.VBRInfo();
|
||||
|
||||
if (vbr.IsComplete()) {
|
||||
frameIndex =
|
||||
AssertedCast<int64_t>(static_cast<float>(aOffset - mFirstFrameOffset) /
|
||||
static_cast<float>(vbr.NumBytes().value()) *
|
||||
static_cast<float>(vbr.NumAudioFrames().value()));
|
||||
frameIndex = static_cast<float>(aOffset - mFirstFrameOffset) /
|
||||
vbr.NumBytes().value() * vbr.NumAudioFrames().value();
|
||||
frameIndex = std::min<int64_t>(vbr.NumAudioFrames().value(), frameIndex);
|
||||
} else if (AverageFrameLength() > 0) {
|
||||
frameIndex = AssertedCast<int64_t>(
|
||||
static_cast<float>(aOffset - mFirstFrameOffset) / AverageFrameLength());
|
||||
frameIndex = (aOffset - mFirstFrameOffset) / AverageFrameLength();
|
||||
}
|
||||
|
||||
MP3LOGV("FrameIndexFromOffset(%" PRId64 ") -> %" PRId64, aOffset, frameIndex);
|
||||
|
|
@ -788,8 +698,7 @@ int64_t MP3TrackDemuxer::FrameIndexFromTime(
|
|||
const media::TimeUnit& aTime) const {
|
||||
int64_t frameIndex = 0;
|
||||
if (mSamplesPerSecond > 0 && mSamplesPerFrame > 0) {
|
||||
frameIndex = AssertedCast<int64_t>(
|
||||
aTime.ToSeconds() * mSamplesPerSecond / mSamplesPerFrame - 1);
|
||||
frameIndex = aTime.ToSeconds() * mSamplesPerSecond / mSamplesPerFrame - 1;
|
||||
}
|
||||
|
||||
MP3LOGV("FrameIndexFromOffset(%fs) -> %" PRId64, aTime.ToSeconds(),
|
||||
|
|
@ -826,13 +735,13 @@ void MP3TrackDemuxer::UpdateState(const MediaByteRange& aRange) {
|
|||
}
|
||||
|
||||
uint32_t MP3TrackDemuxer::Read(uint8_t* aBuffer, int64_t aOffset,
|
||||
uint32_t aSize) {
|
||||
int32_t aSize) {
|
||||
MP3LOGV("MP3TrackDemuxer::Read(%p %" PRId64 " %d)", aBuffer, aOffset, aSize);
|
||||
|
||||
const int64_t streamLen = StreamLength();
|
||||
if (mInfo && streamLen > 0) {
|
||||
// Prevent blocking reads after successful initialization.
|
||||
int64_t max = streamLen > aOffset ? streamLen - aOffset : 0;
|
||||
uint64_t max = streamLen > aOffset ? streamLen - aOffset : 0;
|
||||
aSize = std::min<int64_t>(aSize, max);
|
||||
}
|
||||
|
||||
|
|
@ -846,8 +755,7 @@ uint32_t MP3TrackDemuxer::Read(uint8_t* aBuffer, int64_t aOffset,
|
|||
|
||||
double MP3TrackDemuxer::AverageFrameLength() const {
|
||||
if (mNumParsedFrames) {
|
||||
return static_cast<double>(mTotalFrameLen) /
|
||||
static_cast<double>(mNumParsedFrames);
|
||||
return static_cast<double>(mTotalFrameLen) / mNumParsedFrames;
|
||||
}
|
||||
const auto& vbr = mParser.VBRInfo();
|
||||
if (vbr.IsComplete() && vbr.NumAudioFrames().value() + 1) {
|
||||
|
|
|
|||
|
|
@ -77,10 +77,6 @@ class MP3TrackDemuxer : public MediaTrackDemuxer,
|
|||
const media::TimeUnit& aTimeThreshold) override;
|
||||
int64_t GetResourceOffset() const override;
|
||||
media::TimeIntervals GetBuffered() override;
|
||||
// Return the duration in frames of the encoder delay.
|
||||
uint32_t EncoderDelayFrames() const;
|
||||
// Return the duration in frames of the padding.
|
||||
uint32_t PaddingFrames() const;
|
||||
|
||||
private:
|
||||
// Destructor.
|
||||
|
|
@ -121,7 +117,7 @@ class MP3TrackDemuxer : public MediaTrackDemuxer,
|
|||
|
||||
// Reads aSize bytes into aBuffer from the source starting at aOffset.
|
||||
// Returns the actual size read.
|
||||
uint32_t Read(uint8_t* aBuffer, int64_t aOffset, uint32_t aSize);
|
||||
uint32_t Read(uint8_t* aBuffer, int64_t aOffset, int32_t aSize);
|
||||
|
||||
// Returns the average frame length derived from the previously parsed frames.
|
||||
double AverageFrameLength() const;
|
||||
|
|
@ -130,12 +126,6 @@ class MP3TrackDemuxer : public MediaTrackDemuxer,
|
|||
// otherwise.
|
||||
Maybe<uint32_t> ValidNumAudioFrames() const;
|
||||
|
||||
// Return the duration of the encoder delay.
|
||||
media::TimeUnit EncoderDelay() const;
|
||||
|
||||
// Return the duration of the padding.
|
||||
media::TimeUnit Padding() const;
|
||||
|
||||
// The (hopefully) MPEG resource.
|
||||
MediaResourceIndex mSource;
|
||||
|
||||
|
|
@ -161,7 +151,7 @@ class MP3TrackDemuxer : public MediaTrackDemuxer,
|
|||
int64_t mTotalFrameLen;
|
||||
|
||||
// Samples per frame metric derived from frame headers or 0 if none available.
|
||||
uint32_t mSamplesPerFrame;
|
||||
int32_t mSamplesPerFrame;
|
||||
|
||||
// Samples per second metric derived from frame headers or 0 if none
|
||||
// available.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
#include <algorithm>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "TimeUnits.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/EndianUtils.h"
|
||||
#include "mozilla/ResultExtensions.h"
|
||||
|
|
@ -70,11 +69,7 @@ const ID3Parser::ID3Header& FrameParser::ID3Header() const {
|
|||
}
|
||||
|
||||
uint32_t FrameParser::TotalID3HeaderSize() const {
|
||||
uint32_t ID3v1Size = 0;
|
||||
if (mID3v1MetadataFound) {
|
||||
ID3v1Size = 128;
|
||||
}
|
||||
return ID3v1Size + mID3Parser.TotalHeadersSize();
|
||||
return mID3Parser.TotalHeadersSize();
|
||||
}
|
||||
|
||||
const FrameParser::VBRHeader& FrameParser::VBRInfo() const {
|
||||
|
|
@ -86,18 +81,10 @@ Result<bool, nsresult> FrameParser::Parse(BufferReader* aReader,
|
|||
MOZ_ASSERT(aReader && aBytesToSkip);
|
||||
*aBytesToSkip = 0;
|
||||
|
||||
if (ID3Parser::IsBufferStartingWithID3v1Tag(aReader)) {
|
||||
// This is at the end of the file, and is always 128 bytes, that can simply
|
||||
// be skipped.
|
||||
aReader->Read(128);
|
||||
*aBytesToSkip = 128;
|
||||
mID3v1MetadataFound = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ID3Parser::IsBufferStartingWithID3Tag(aReader) && !mFirstFrame.Length()) {
|
||||
// No MP3 frames have been parsed yet, look for ID3v2 headers at file begin.
|
||||
// ID3v1 tags may only be at file end.
|
||||
// TODO: should we try to read ID3 tags at end of file/mid-stream, too?
|
||||
const size_t prevReaderOffset = aReader->Offset();
|
||||
const uint32_t tagSize = mID3Parser.Parse(aReader);
|
||||
if (!!tagSize) {
|
||||
|
|
@ -275,8 +262,6 @@ bool FrameParser::FrameHeader::ParseNext(uint8_t c) {
|
|||
return IsValid();
|
||||
}
|
||||
|
||||
bool FrameParser::ID3v1MetadataFound() const { return mID3v1MetadataFound; }
|
||||
|
||||
bool FrameParser::FrameHeader::IsValid(int aPos) const {
|
||||
if (aPos >= SIZE) {
|
||||
return true;
|
||||
|
|
@ -328,8 +313,7 @@ const Maybe<uint32_t>& FrameParser::VBRHeader::NumBytes() const {
|
|||
const Maybe<uint32_t>& FrameParser::VBRHeader::Scale() const { return mScale; }
|
||||
|
||||
bool FrameParser::VBRHeader::IsTOCPresent() const {
|
||||
// This doesn't use VBRI TOC
|
||||
return !mTOC.empty() && mType != VBRI;
|
||||
return mTOC.size() == vbr_header::TOC_SIZE;
|
||||
}
|
||||
|
||||
bool FrameParser::VBRHeader::IsValid() const { return mType != NONE; }
|
||||
|
|
@ -341,31 +325,23 @@ bool FrameParser::VBRHeader::IsComplete() const {
|
|||
;
|
||||
}
|
||||
|
||||
int64_t FrameParser::VBRHeader::Offset(media::TimeUnit aTime,
|
||||
media::TimeUnit aDuration) const {
|
||||
int64_t FrameParser::VBRHeader::Offset(float aDurationFac) const {
|
||||
if (!IsTOCPresent()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int64_t offset = -1;
|
||||
if (mType == XING) {
|
||||
// Constrain the duration percentage to [0, 99].
|
||||
double percent = 100. * aTime.ToSeconds() / aDuration.ToSeconds();
|
||||
const double durationPer = std::clamp(percent, 0., 99.);
|
||||
double integer;
|
||||
const double fractional = modf(durationPer, &integer);
|
||||
size_t integerPer = AssertedCast<size_t>(integer);
|
||||
// Constrain the duration percentage to [0, 99].
|
||||
const float durationPer =
|
||||
100.0f * std::min(0.99f, std::max(0.0f, aDurationFac));
|
||||
const size_t fullPer = durationPer;
|
||||
const float rest = durationPer - fullPer;
|
||||
|
||||
MOZ_ASSERT(integerPer < mTOC.size());
|
||||
offset = mTOC.at(integerPer);
|
||||
if (fractional > 0.0 && integerPer + 1 < mTOC.size()) {
|
||||
offset += AssertedCast<int64_t>(fractional) *
|
||||
(mTOC.at(integerPer + 1) - offset);
|
||||
}
|
||||
MOZ_ASSERT(fullPer < mTOC.size());
|
||||
int64_t offset = mTOC.at(fullPer);
|
||||
|
||||
if (rest > 0.0 && fullPer + 1 < mTOC.size()) {
|
||||
offset += rest * (mTOC.at(fullPer + 1) - offset);
|
||||
}
|
||||
// TODO: VBRI TOC seeking
|
||||
MP3LOG("VBRHeader::Offset (%s): %f is at byte %" PRId64 "",
|
||||
mType == XING ? "XING" : "VBRI", aTime.ToSeconds(), offset);
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
|
@ -424,8 +400,7 @@ Result<bool, nsresult> FrameParser::VBRHeader::ParseXing(BufferReader* aReader,
|
|||
uint8_t data;
|
||||
for (size_t i = 0; i < vbr_header::TOC_SIZE; ++i) {
|
||||
MOZ_TRY_VAR(data, aReader->ReadU8());
|
||||
mTOC.push_back(
|
||||
AssertedCast<uint32_t>(1.0f / 256.0f * data * mNumBytes.value()));
|
||||
mTOC.push_back(1.0f / 256.0f * data * mNumBytes.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -473,16 +448,11 @@ Result<bool, nsresult> FrameParser::VBRHeader::ParseXing(BufferReader* aReader,
|
|||
return mType == XING;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
int readAndConvertToInt(BufferReader* aReader) {
|
||||
int value = AssertedCast<int>(aReader->ReadType<T>());
|
||||
return value;
|
||||
}
|
||||
|
||||
Result<bool, nsresult> FrameParser::VBRHeader::ParseVBRI(
|
||||
BufferReader* aReader) {
|
||||
static const uint32_t TAG = BigEndian::readUint32("VBRI");
|
||||
static const uint32_t OFFSET = 32 + FrameParser::FrameHeader::SIZE;
|
||||
static const uint32_t FRAME_COUNT_OFFSET = OFFSET + 14;
|
||||
static const uint32_t MIN_FRAME_SIZE = OFFSET + 26;
|
||||
|
||||
MOZ_ASSERT(aReader);
|
||||
|
|
@ -501,75 +471,12 @@ Result<bool, nsresult> FrameParser::VBRHeader::ParseVBRI(
|
|||
// VBRI have a fixed relative position, so let's check for it there.
|
||||
if (aReader->Remaining() > MIN_FRAME_SIZE) {
|
||||
aReader->Seek(prevReaderOffset + OFFSET);
|
||||
uint32_t tag;
|
||||
uint32_t tag, frames;
|
||||
MOZ_TRY_VAR(tag, aReader->ReadU32());
|
||||
if (tag == TAG) {
|
||||
uint16_t vbriEncoderVersion, vbriEncoderDelay, vbriQuality;
|
||||
uint32_t vbriBytes, vbriFrames;
|
||||
uint16_t vbriSeekOffsetsTableSize, vbriSeekOffsetsScaleFactor,
|
||||
vbriSeekOffsetsBytesPerEntry, vbriSeekOffsetsFramesPerEntry;
|
||||
MOZ_TRY_VAR(vbriEncoderVersion, aReader->ReadU16());
|
||||
MOZ_TRY_VAR(vbriEncoderDelay, aReader->ReadU16());
|
||||
MOZ_TRY_VAR(vbriQuality, aReader->ReadU16());
|
||||
MOZ_TRY_VAR(vbriBytes, aReader->ReadU32());
|
||||
MOZ_TRY_VAR(vbriFrames, aReader->ReadU32());
|
||||
MOZ_TRY_VAR(vbriSeekOffsetsTableSize, aReader->ReadU16());
|
||||
MOZ_TRY_VAR(vbriSeekOffsetsScaleFactor, aReader->ReadU32());
|
||||
MOZ_TRY_VAR(vbriSeekOffsetsBytesPerEntry, aReader->ReadU16());
|
||||
MOZ_TRY_VAR(vbriSeekOffsetsFramesPerEntry, aReader->ReadU16());
|
||||
|
||||
mTOC.reserve(vbriSeekOffsetsTableSize + 1);
|
||||
|
||||
int (*readFunc)(BufferReader*);
|
||||
switch (vbriSeekOffsetsBytesPerEntry) {
|
||||
case 1:
|
||||
readFunc = &readAndConvertToInt<uint8_t>;
|
||||
break;
|
||||
case 2:
|
||||
readFunc = &readAndConvertToInt<int16_t>;
|
||||
break;
|
||||
case 4:
|
||||
readFunc = &readAndConvertToInt<int32_t>;
|
||||
break;
|
||||
case 8:
|
||||
readFunc = &readAndConvertToInt<int64_t>;
|
||||
break;
|
||||
default:
|
||||
MP3LOG("Unhandled vbriSeekOffsetsBytesPerEntry size of %hd",
|
||||
vbriSeekOffsetsBytesPerEntry);
|
||||
break;
|
||||
}
|
||||
for (uint32_t i = 0; i < vbriSeekOffsetsTableSize; i++) {
|
||||
int entry = readFunc(aReader);
|
||||
mTOC.push_back(entry * vbriSeekOffsetsScaleFactor);
|
||||
}
|
||||
MP3LOG(
|
||||
"Header::Parse found valid header: EncoderVersion=%hu "
|
||||
"EncoderDelay=%hu "
|
||||
"Quality=%hu "
|
||||
"Bytes=%u "
|
||||
"Frames=%u "
|
||||
"SeekOffsetsTableSize=%u "
|
||||
"SeekOffsetsScaleFactor=%hu "
|
||||
"SeekOffsetsBytesPerEntry=%hu "
|
||||
"SeekOffsetsFramesPerEntry=%hu",
|
||||
vbriEncoderVersion, vbriEncoderDelay, vbriQuality, vbriBytes,
|
||||
vbriFrames, vbriSeekOffsetsTableSize, vbriSeekOffsetsScaleFactor,
|
||||
vbriSeekOffsetsBytesPerEntry, vbriSeekOffsetsFramesPerEntry);
|
||||
// Adjust the number of frames so it's counted the same way as in the XING
|
||||
// header
|
||||
if (vbriFrames < 1) {
|
||||
return false;
|
||||
}
|
||||
mNumAudioFrames = Some(vbriFrames - 1);
|
||||
mNumBytes = Some(vbriBytes);
|
||||
mEncoderDelay = vbriEncoderDelay;
|
||||
mVBRISeekOffsetsFramesPerEntry = vbriSeekOffsetsFramesPerEntry;
|
||||
MP3LOG("TOC:");
|
||||
for (auto entry : mTOC) {
|
||||
MP3LOG("%" PRId64, entry);
|
||||
}
|
||||
|
||||
aReader->Seek(prevReaderOffset + FRAME_COUNT_OFFSET);
|
||||
MOZ_TRY_VAR(frames, aReader->ReadU32());
|
||||
mNumAudioFrames = Some(frames);
|
||||
mType = VBRI;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -584,9 +491,9 @@ bool FrameParser::VBRHeader::Parse(BufferReader* aReader, size_t aFrameSize) {
|
|||
if (rv) {
|
||||
MP3LOG(
|
||||
"VBRHeader::Parse found valid VBR/CBR header: type=%s"
|
||||
" NumAudioFrames=%u NumBytes=%u Scale=%u TOC-size=%zu Delay=%u",
|
||||
" NumAudioFrames=%u NumBytes=%u Scale=%u TOC-size=%zu",
|
||||
vbr_header::TYPE_STR[Type()], NumAudioFrames().valueOr(0),
|
||||
NumBytes().valueOr(0), Scale().valueOr(0), mTOC.size(), mEncoderDelay);
|
||||
NumBytes().valueOr(0), Scale().valueOr(0), mTOC.size());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
|
@ -595,13 +502,13 @@ bool FrameParser::VBRHeader::Parse(BufferReader* aReader, size_t aFrameSize) {
|
|||
|
||||
void FrameParser::Frame::Reset() { mHeader.Reset(); }
|
||||
|
||||
uint32_t FrameParser::Frame::Length() const {
|
||||
int32_t FrameParser::Frame::Length() const {
|
||||
if (!mHeader.IsValid() || !mHeader.SampleRate()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const uint32_t bitsPerSample = mHeader.SamplesPerFrame() / 8;
|
||||
const uint32_t frameLen =
|
||||
const float bitsPerSample = mHeader.SamplesPerFrame() / 8.0f;
|
||||
const int32_t frameLen =
|
||||
bitsPerSample * mHeader.Bitrate() / mHeader.SampleRate() +
|
||||
mHeader.Padding() * mHeader.SlotSize();
|
||||
return frameLen;
|
||||
|
|
@ -632,29 +539,11 @@ static const int FLAGS_END = VERSION_END + FLAGS_LEN;
|
|||
static const int SIZE_END = FLAGS_END + SIZE_LEN;
|
||||
|
||||
static const uint8_t ID[ID_LEN] = {'I', 'D', '3'};
|
||||
static const uint8_t IDv1[ID_LEN] = {'T', 'A', 'G'};
|
||||
|
||||
static const uint8_t MIN_MAJOR_VER = 2;
|
||||
static const uint8_t MAX_MAJOR_VER = 4;
|
||||
} // namespace id3_header
|
||||
|
||||
bool ID3Parser::IsBufferStartingWithID3v1Tag(BufferReader* aReader) {
|
||||
mozilla::Result<uint32_t, nsresult> res = aReader->PeekU24();
|
||||
if (res.isErr()) {
|
||||
return false;
|
||||
}
|
||||
// If buffer starts with ID3v1 tag, `rv` would be reverse and its content
|
||||
// should be '3' 'D' 'I' from the lowest bit.
|
||||
uint32_t rv = res.unwrap();
|
||||
for (int idx = id3_header::ID_LEN - 1; idx >= 0; idx--) {
|
||||
if ((rv & 0xff) != id3_header::IDv1[idx]) {
|
||||
return false;
|
||||
}
|
||||
rv = rv >> 8;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool ID3Parser::IsBufferStartingWithID3Tag(BufferReader* aReader) {
|
||||
mozilla::Result<uint32_t, nsresult> res = aReader->PeekU24();
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ class ID3Parser {
|
|||
public:
|
||||
// The header size is static, see class comment.
|
||||
static const int SIZE = 10;
|
||||
static const int ID3v1_SIZE = 128;
|
||||
|
||||
// Constructor.
|
||||
ID3Header();
|
||||
|
|
@ -69,7 +68,7 @@ class ID3Parser {
|
|||
bool Update(uint8_t c);
|
||||
|
||||
// The currently parsed byte sequence.
|
||||
uint8_t mRaw[SIZE] = {};
|
||||
uint8_t mRaw[SIZE];
|
||||
|
||||
// The derived size as provided by the size fields.
|
||||
// The header size fields holds a 4 byte sequence with each MSB set to 0,
|
||||
|
|
@ -78,13 +77,11 @@ class ID3Parser {
|
|||
|
||||
// The current byte position in the parsed sequence. Reset via Reset and
|
||||
// incremented via Update.
|
||||
int mPos = 0;
|
||||
int mPos;
|
||||
};
|
||||
|
||||
// Check if the buffer is starting with ID3v2 tag.
|
||||
static bool IsBufferStartingWithID3Tag(BufferReader* aReader);
|
||||
// Similarly, if the buffer is starting with ID3v1 tag.
|
||||
static bool IsBufferStartingWithID3v1Tag(BufferReader* aReader);
|
||||
|
||||
// Returns the parsed ID3 header. Note: check for validity.
|
||||
const ID3Header& Header() const;
|
||||
|
|
@ -188,11 +185,11 @@ class FrameParser {
|
|||
bool Update(const uint8_t c);
|
||||
|
||||
// The currently parsed byte sequence.
|
||||
uint8_t mRaw[SIZE] = {};
|
||||
uint8_t mRaw[SIZE];
|
||||
|
||||
// The current byte position in the parsed sequence. Reset via Reset and
|
||||
// incremented via Update.
|
||||
int mPos = 0;
|
||||
int mPos;
|
||||
};
|
||||
|
||||
// VBR frames may contain Xing or VBRI headers for additional info, we use
|
||||
|
|
@ -230,7 +227,7 @@ class FrameParser {
|
|||
|
||||
// Returns the byte offset for the given duration percentage as a factor
|
||||
// (0: begin, 1.0: end).
|
||||
int64_t Offset(media::TimeUnit aTime, media::TimeUnit aDuration) const;
|
||||
int64_t Offset(float aDurationFac) const;
|
||||
|
||||
// Parses contents of given ByteReader for a valid VBR header.
|
||||
// The offset of the passed ByteReader needs to point to an MPEG frame
|
||||
|
|
@ -269,8 +266,6 @@ class FrameParser {
|
|||
// The detected VBR header type.
|
||||
VBRHeaderType mType;
|
||||
|
||||
uint16_t mVBRISeekOffsetsFramesPerEntry = 0;
|
||||
|
||||
// Delay and padding values found in the LAME header. The encoder delay is a
|
||||
// number of frames that has to be skipped at the beginning of the stream,
|
||||
// encoder padding is a number of frames that needs to be ignored in the
|
||||
|
|
@ -283,7 +278,7 @@ class FrameParser {
|
|||
class Frame {
|
||||
public:
|
||||
// Returns the length of the frame excluding the header in bytes.
|
||||
uint32_t Length() const;
|
||||
int32_t Length() const;
|
||||
|
||||
// Returns the parsed frame header.
|
||||
const FrameHeader& Header() const;
|
||||
|
|
@ -315,9 +310,6 @@ class FrameParser {
|
|||
// Returns the parsed ID3 header. Note: check for validity.
|
||||
const ID3Parser::ID3Header& ID3Header() const;
|
||||
|
||||
// Returns whether ID3 metadata have been found, at the end of the file.
|
||||
bool ID3v1MetadataFound() const;
|
||||
|
||||
// Returns the size of all parsed ID3 headers.
|
||||
uint32_t TotalID3HeaderSize() const;
|
||||
|
||||
|
|
@ -361,12 +353,6 @@ class FrameParser {
|
|||
Frame mFirstFrame;
|
||||
Frame mFrame;
|
||||
Frame mPrevFrame;
|
||||
// If this is true, ID3v1 metadata have been found at the end of the file, and
|
||||
// must be sustracted from the stream size in order to compute the stream
|
||||
// duration, when computing the duration of a CBR file based on its length in
|
||||
// bytes. This means that the duration can change at the moment we reach the
|
||||
// end of the file.
|
||||
bool mID3v1MetadataFound = false;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
|||
|
|
@ -10,16 +10,11 @@
|
|||
#include "mozilla/EndianUtils.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "VideoUtils.h"
|
||||
#include "MP4Metadata.h"
|
||||
#include "mozilla/Logging.h"
|
||||
|
||||
// OpusDecoder header is really needed only by MP4 in rust
|
||||
#include "OpusDecoder.h"
|
||||
#include "mp4parse.h"
|
||||
|
||||
#define LOG(...) \
|
||||
MOZ_LOG(gMP4MetadataLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
|
||||
|
||||
using mozilla::media::TimeUnit;
|
||||
|
||||
namespace mozilla {
|
||||
|
|
@ -137,17 +132,16 @@ static MediaResult VerifyAudioOrVideoInfoAndRecordTelemetry(
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
MediaResult MP4AudioInfo::Update(const Mp4parseTrackInfo* aTrack,
|
||||
const Mp4parseTrackAudioInfo* aAudio,
|
||||
const IndiceWrapper* aIndices) {
|
||||
auto rv = VerifyAudioOrVideoInfoAndRecordTelemetry(aAudio);
|
||||
MediaResult MP4AudioInfo::Update(const Mp4parseTrackInfo* track,
|
||||
const Mp4parseTrackAudioInfo* audio) {
|
||||
auto rv = VerifyAudioOrVideoInfoAndRecordTelemetry(audio);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
Mp4parseCodec codecType = aAudio->sample_info[0].codec_type;
|
||||
for (uint32_t i = 0; i < aAudio->sample_info_count; i++) {
|
||||
if (aAudio->sample_info[i].protected_data.is_encrypted) {
|
||||
auto rv = UpdateTrackProtectedInfo(*this,
|
||||
aAudio->sample_info[i].protected_data);
|
||||
Mp4parseCodec codecType = audio->sample_info[0].codec_type;
|
||||
for (uint32_t i = 0; i < audio->sample_info_count; i++) {
|
||||
if (audio->sample_info[i].protected_data.is_encrypted) {
|
||||
auto rv =
|
||||
UpdateTrackProtectedInfo(*this, audio->sample_info[i].protected_data);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
break;
|
||||
}
|
||||
|
|
@ -158,8 +152,8 @@ MediaResult MP4AudioInfo::Update(const Mp4parseTrackInfo* aTrack,
|
|||
// ever not hold. E.g. if we need to handle different codecs in a single
|
||||
// track, or if we have different numbers or channels in a single track.
|
||||
Mp4parseByteData mp4ParseSampleCodecSpecific =
|
||||
aAudio->sample_info[0].codec_specific_config;
|
||||
Mp4parseByteData extraData = aAudio->sample_info[0].extra_data;
|
||||
audio->sample_info[0].codec_specific_config;
|
||||
Mp4parseByteData extraData = audio->sample_info[0].extra_data;
|
||||
MOZ_ASSERT(mCodecSpecificConfig.is<NoCodecSpecificData>(),
|
||||
"Should have no codec specific data yet");
|
||||
if (codecType == MP4PARSE_CODEC_OPUS) {
|
||||
|
|
@ -174,9 +168,6 @@ MediaResult MP4AudioInfo::Update(const Mp4parseTrackInfo* aTrack,
|
|||
mp4ParseSampleCodecSpecific.data + 10);
|
||||
opusCodecSpecificData.mContainerCodecDelayMicroSeconds =
|
||||
mozilla::FramesToUsecs(preskip, 48000).value();
|
||||
LOG("Opus stream in MP4 container, %" PRId64
|
||||
" microseconds of encoder delay (%" PRIu16 ").",
|
||||
opusCodecSpecificData.mContainerCodecDelayMicroSeconds, preskip);
|
||||
} else {
|
||||
// This file will error later as it will be rejected by the opus decoder.
|
||||
opusCodecSpecificData.mContainerCodecDelayMicroSeconds = 0;
|
||||
|
|
@ -187,45 +178,7 @@ MediaResult MP4AudioInfo::Update(const Mp4parseTrackInfo* aTrack,
|
|||
AudioCodecSpecificVariant{std::move(opusCodecSpecificData)};
|
||||
} else if (codecType == MP4PARSE_CODEC_AAC) {
|
||||
mMimeType = "audio/mp4a-latm"_ns;
|
||||
int64_t codecDelayUS = aTrack->media_time;
|
||||
double USECS_PER_S = 1e6;
|
||||
// We can't use mozilla::UsecsToFrames here because we need to round, and it
|
||||
// floors.
|
||||
uint32_t encoderDelayFrameCount = 0;
|
||||
if (codecDelayUS > 0) {
|
||||
encoderDelayFrameCount = static_cast<uint32_t>(
|
||||
std::lround(static_cast<double>(codecDelayUS) *
|
||||
aAudio->sample_info->sample_rate / USECS_PER_S));
|
||||
LOG("AAC stream in MP4 container, %" PRIu32 " frames of encoder delay.",
|
||||
encoderDelayFrameCount);
|
||||
}
|
||||
|
||||
uint64_t mediaFrameCount = 0;
|
||||
// Pass the padding number, in frames, to the AAC decoder as well.
|
||||
if (aIndices) {
|
||||
MP4SampleIndex::Indice indice = {0};
|
||||
bool rv = aIndices->GetIndice(aIndices->Length() - 1, indice);
|
||||
if (rv) {
|
||||
// The `end_composition` member of the very last index member is the
|
||||
// duration of the media in microseconds, excluding decoder delay and
|
||||
// padding. Convert to frames and give to the decoder so that trimming
|
||||
// can be done properly.
|
||||
mediaFrameCount = AssertedCast<uint64_t>(
|
||||
static_cast<double>(indice.end_composition) *
|
||||
aAudio->sample_info->sample_rate / USECS_PER_S);
|
||||
LOG("AAC stream in MP4 container, total media duration is %" PRIu64
|
||||
" frames",
|
||||
mediaFrameCount);
|
||||
} else {
|
||||
LOG("AAC stream in MP4 container, couldn't determine total media time");
|
||||
}
|
||||
}
|
||||
|
||||
AacCodecSpecificData aacCodecSpecificData{};
|
||||
|
||||
aacCodecSpecificData.mEncoderDelayFrames = encoderDelayFrameCount;
|
||||
aacCodecSpecificData.mMediaFrameCount = mediaFrameCount;
|
||||
|
||||
// codec specific data is used to store the DecoderConfigDescriptor.
|
||||
aacCodecSpecificData.mDecoderConfigDescriptorBinaryBlob->AppendElements(
|
||||
mp4ParseSampleCodecSpecific.data, mp4ParseSampleCodecSpecific.length);
|
||||
|
|
@ -252,23 +205,17 @@ MediaResult MP4AudioInfo::Update(const Mp4parseTrackInfo* aTrack,
|
|||
mCodecSpecificConfig = AudioCodecSpecificVariant{Mp3CodecSpecificData{}};
|
||||
}
|
||||
|
||||
mRate = aAudio->sample_info[0].sample_rate;
|
||||
mChannels = aAudio->sample_info[0].channels;
|
||||
mBitDepth = aAudio->sample_info[0].bit_depth;
|
||||
mExtendedProfile =
|
||||
AssertedCast<int8_t>(aAudio->sample_info[0].extended_profile);
|
||||
if (aTrack->duration > TimeUnit::MaxTicks()) {
|
||||
mDuration = TimeUnit::FromInfinity();
|
||||
} else {
|
||||
mDuration =
|
||||
TimeUnit(AssertedCast<int64_t>(aTrack->duration), aTrack->time_scale);
|
||||
}
|
||||
mMediaTime = TimeUnit(aTrack->media_time, aTrack->time_scale);
|
||||
mTrackId = aTrack->track_id;
|
||||
mRate = audio->sample_info[0].sample_rate;
|
||||
mChannels = audio->sample_info[0].channels;
|
||||
mBitDepth = audio->sample_info[0].bit_depth;
|
||||
mExtendedProfile = audio->sample_info[0].extended_profile;
|
||||
mDuration = TimeUnit::FromMicroseconds(track->duration);
|
||||
mMediaTime = TimeUnit::FromMicroseconds(track->media_time);
|
||||
mTrackId = track->track_id;
|
||||
|
||||
// In stagefright, mProfile is kKeyAACProfile, mExtendedProfile is kKeyAACAOT.
|
||||
if (aAudio->sample_info[0].profile <= 4) {
|
||||
mProfile = AssertedCast<int8_t>(aAudio->sample_info[0].profile);
|
||||
if (audio->sample_info[0].profile <= 4) {
|
||||
mProfile = audio->sample_info[0].profile;
|
||||
}
|
||||
|
||||
if (mCodecSpecificConfig.is<NoCodecSpecificData>()) {
|
||||
|
|
@ -323,15 +270,10 @@ MediaResult MP4VideoInfo::Update(const Mp4parseTrackInfo* track,
|
|||
mMimeType = "video/mp4v-es"_ns;
|
||||
}
|
||||
mTrackId = track->track_id;
|
||||
if (track->duration > TimeUnit::MaxTicks()) {
|
||||
mDuration = TimeUnit::FromInfinity();
|
||||
} else {
|
||||
mDuration =
|
||||
TimeUnit(AssertedCast<int64_t>(track->duration), track->time_scale);
|
||||
}
|
||||
mMediaTime = TimeUnit(track->media_time, track->time_scale);
|
||||
mDisplay.width = AssertedCast<int32_t>(video->display_width);
|
||||
mDisplay.height = AssertedCast<int32_t>(video->display_height);
|
||||
mDuration = TimeUnit::FromMicroseconds(track->duration);
|
||||
mMediaTime = TimeUnit::FromMicroseconds(track->media_time);
|
||||
mDisplay.width = video->display_width;
|
||||
mDisplay.height = video->display_height;
|
||||
mImage.width = video->sample_info[0].image_width;
|
||||
mImage.height = video->sample_info[0].image_height;
|
||||
mRotation = ToSupportedRotation(video->rotation);
|
||||
|
|
@ -347,5 +289,3 @@ bool MP4VideoInfo::IsValid() const {
|
|||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#undef LOG
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
namespace mozilla {
|
||||
|
||||
class IndiceWrapper;
|
||||
class MP4Demuxer;
|
||||
|
||||
struct PsshInfo {
|
||||
|
|
@ -54,9 +53,8 @@ class MP4AudioInfo : public mozilla::AudioInfo {
|
|||
public:
|
||||
MP4AudioInfo() = default;
|
||||
|
||||
MediaResult Update(const Mp4parseTrackInfo* aTrack,
|
||||
const Mp4parseTrackAudioInfo* aAudio,
|
||||
const IndiceWrapper* aIndices);
|
||||
MediaResult Update(const Mp4parseTrackInfo* track,
|
||||
const Mp4parseTrackAudioInfo* audio);
|
||||
|
||||
virtual bool IsValid() const override;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
#include "MP4Metadata.h"
|
||||
#include "MoofParser.h"
|
||||
#include "ResourceStream.h"
|
||||
#include "TimeUnits.h"
|
||||
#include "VPXDecoder.h"
|
||||
#include "mozilla/Span.h"
|
||||
#include "mozilla/StaticPrefs_media.h"
|
||||
|
|
@ -34,32 +33,28 @@ mozilla::LogModule* GetDemuxerLog() { return gMediaDemuxerLog; }
|
|||
|
||||
namespace mozilla {
|
||||
|
||||
using TimeUnit = media::TimeUnit;
|
||||
using TimeInterval = media::TimeInterval;
|
||||
using TimeIntervals = media::TimeIntervals;
|
||||
|
||||
DDLoggedTypeDeclNameAndBase(MP4TrackDemuxer, MediaTrackDemuxer);
|
||||
|
||||
class MP4TrackDemuxer : public MediaTrackDemuxer,
|
||||
public DecoderDoctorLifeLogger<MP4TrackDemuxer> {
|
||||
public:
|
||||
MP4TrackDemuxer(MediaResource* aResource, UniquePtr<TrackInfo>&& aInfo,
|
||||
const IndiceWrapper& aIndices, int32_t aTimeScale);
|
||||
const IndiceWrapper& aIndices);
|
||||
|
||||
UniquePtr<TrackInfo> GetInfo() const override;
|
||||
|
||||
RefPtr<SeekPromise> Seek(const TimeUnit& aTime) override;
|
||||
RefPtr<SeekPromise> Seek(const media::TimeUnit& aTime) override;
|
||||
|
||||
RefPtr<SamplesPromise> GetSamples(int32_t aNumSamples = 1) override;
|
||||
|
||||
void Reset() override;
|
||||
|
||||
nsresult GetNextRandomAccessPoint(TimeUnit* aTime) override;
|
||||
nsresult GetNextRandomAccessPoint(media::TimeUnit* aTime) override;
|
||||
|
||||
RefPtr<SkipAccessPointPromise> SkipToNextRandomAccessPoint(
|
||||
const TimeUnit& aTimeThreshold) override;
|
||||
const media::TimeUnit& aTimeThreshold) override;
|
||||
|
||||
TimeIntervals GetBuffered() override;
|
||||
media::TimeIntervals GetBuffered() override;
|
||||
|
||||
void NotifyDataRemoved();
|
||||
void NotifyDataArrived();
|
||||
|
|
@ -73,11 +68,11 @@ class MP4TrackDemuxer : public MediaTrackDemuxer,
|
|||
UniquePtr<TrackInfo> mInfo;
|
||||
RefPtr<MP4SampleIndex> mIndex;
|
||||
UniquePtr<SampleIterator> mIterator;
|
||||
Maybe<TimeUnit> mNextKeyframeTime;
|
||||
Maybe<media::TimeUnit> mNextKeyframeTime;
|
||||
// Queued samples extracted by the demuxer, but not yet returned.
|
||||
RefPtr<MediaRawData> mQueuedSample;
|
||||
bool mNeedReIndex;
|
||||
enum CodecType { kH264, kVP9, kAAC, kOther } mType = kOther;
|
||||
enum CodecType { kH264, kVP9, kOther } mType = kOther;
|
||||
};
|
||||
|
||||
MP4Demuxer::MP4Demuxer(MediaResource* aResource)
|
||||
|
|
@ -189,9 +184,8 @@ RefPtr<MP4Demuxer::InitPromise> MP4Demuxer::Init() {
|
|||
}
|
||||
continue;
|
||||
}
|
||||
RefPtr<MP4TrackDemuxer> demuxer =
|
||||
new MP4TrackDemuxer(mResource, std::move(info.Ref()),
|
||||
*indices.Ref().get(), info.Ref()->mTimeScale);
|
||||
RefPtr<MP4TrackDemuxer> demuxer = new MP4TrackDemuxer(
|
||||
mResource, std::move(info.Ref()), *indices.Ref().get());
|
||||
DDLINKCHILD("audio demuxer", demuxer.get());
|
||||
mAudioDemuxers.AppendElement(std::move(demuxer));
|
||||
}
|
||||
|
|
@ -227,9 +221,8 @@ RefPtr<MP4Demuxer::InitPromise> MP4Demuxer::Init() {
|
|||
}
|
||||
continue;
|
||||
}
|
||||
RefPtr<MP4TrackDemuxer> demuxer =
|
||||
new MP4TrackDemuxer(mResource, std::move(info.Ref()),
|
||||
*indices.Ref().get(), info.Ref()->mTimeScale);
|
||||
RefPtr<MP4TrackDemuxer> demuxer = new MP4TrackDemuxer(
|
||||
mResource, std::move(info.Ref()), *indices.Ref().get());
|
||||
DDLINKCHILD("video demuxer", demuxer.get());
|
||||
mVideoDemuxers.AppendElement(std::move(demuxer));
|
||||
}
|
||||
|
|
@ -312,19 +305,17 @@ UniquePtr<EncryptionInfo> MP4Demuxer::GetCrypto() {
|
|||
|
||||
MP4TrackDemuxer::MP4TrackDemuxer(MediaResource* aResource,
|
||||
UniquePtr<TrackInfo>&& aInfo,
|
||||
const IndiceWrapper& aIndices,
|
||||
int32_t aTimeScale)
|
||||
const IndiceWrapper& aIndices)
|
||||
: mResource(aResource),
|
||||
mStream(new ResourceStream(aResource)),
|
||||
mInfo(std::move(aInfo)),
|
||||
mIndex(new MP4SampleIndex(aIndices, mStream, mInfo->mTrackId,
|
||||
mInfo->IsAudio(), aTimeScale)),
|
||||
mInfo->IsAudio())),
|
||||
mIterator(MakeUnique<SampleIterator>(mIndex)),
|
||||
mNeedReIndex(true) {
|
||||
EnsureUpToDateIndex(); // Force update of index
|
||||
|
||||
VideoInfo* videoInfo = mInfo->GetAsVideoInfo();
|
||||
AudioInfo* audioInfo = mInfo->GetAsAudioInfo();
|
||||
if (videoInfo && MP4Decoder::IsH264(mInfo->mMimeType)) {
|
||||
mType = kH264;
|
||||
RefPtr<MediaByteBuffer> extraData = videoInfo->mExtraData;
|
||||
|
|
@ -337,10 +328,10 @@ MP4TrackDemuxer::MP4TrackDemuxer(MediaResource* aResource,
|
|||
videoInfo->mDisplay.width = spsdata.display_width;
|
||||
videoInfo->mDisplay.height = spsdata.display_height;
|
||||
}
|
||||
} else if (videoInfo && VPXDecoder::IsVP9(mInfo->mMimeType)) {
|
||||
mType = kVP9;
|
||||
} else if (audioInfo && MP4Decoder::IsAAC(mInfo->mMimeType)) {
|
||||
mType = kAAC;
|
||||
} else {
|
||||
if (videoInfo && VPXDecoder::IsVP9(mInfo->mMimeType)) {
|
||||
mType = kVP9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -361,11 +352,11 @@ void MP4TrackDemuxer::EnsureUpToDateIndex() {
|
|||
}
|
||||
|
||||
RefPtr<MP4TrackDemuxer::SeekPromise> MP4TrackDemuxer::Seek(
|
||||
const TimeUnit& aTime) {
|
||||
const media::TimeUnit& aTime) {
|
||||
auto seekTime = aTime;
|
||||
mQueuedSample = nullptr;
|
||||
|
||||
mIterator->Seek(seekTime);
|
||||
mIterator->Seek(seekTime.ToMicroseconds());
|
||||
|
||||
// Check what time we actually seeked to.
|
||||
do {
|
||||
|
|
@ -447,61 +438,6 @@ already_AddRefed<MediaRawData> MP4TrackDemuxer::GetNextSample() {
|
|||
}
|
||||
}
|
||||
|
||||
// Adjust trimming information if needed.
|
||||
if (mInfo->GetAsAudioInfo()) {
|
||||
AudioInfo* info = mInfo->GetAsAudioInfo();
|
||||
TimeUnit originalPts = sample->mTime;
|
||||
TimeUnit originalEnd = sample->GetEndTime();
|
||||
if (sample->mTime.IsNegative()) {
|
||||
sample->mTime = TimeUnit::Zero(originalPts);
|
||||
sample->mDuration = std::max(TimeUnit::Zero(sample->mTime),
|
||||
originalPts + sample->mDuration);
|
||||
sample->mOriginalPresentationWindow =
|
||||
Some(TimeInterval{originalPts, originalEnd});
|
||||
}
|
||||
// The demuxer only knows the presentation time of the packet, not the
|
||||
// actual number of samples that will be decoded from this packet.
|
||||
// However we need to trim the last packet to the correct duration.
|
||||
// Find the actual size of the decoded packet to know how many samples to
|
||||
// trim. This only works because the packet size are constant.
|
||||
TimeUnit totalMediaDurationIncludingTrimming =
|
||||
info->mDuration - info->mMediaTime;
|
||||
if (mType == kAAC &&
|
||||
sample->GetEndTime() == totalMediaDurationIncludingTrimming) {
|
||||
MOZ_ASSERT(!mIterator->HasNext());
|
||||
// Seek backward a bit.
|
||||
mIterator->Seek(sample->mTime - sample->mDuration);
|
||||
RefPtr<MediaRawData> previousSample = mIterator->GetNext();
|
||||
if (previousSample) {
|
||||
TimeInterval fullPacketDuration{previousSample->mTime,
|
||||
previousSample->GetEndTime()};
|
||||
sample->mOriginalPresentationWindow = Some(TimeInterval{
|
||||
originalPts, originalPts + fullPacketDuration.Length()});
|
||||
}
|
||||
// Seek back so we're back at the original location -- there's no packet
|
||||
// left anyway.
|
||||
mIterator->Seek(sample->mTime);
|
||||
RefPtr<MediaRawData> dummy = mIterator->GetNext();
|
||||
MOZ_ASSERT(!mIterator->HasNext());
|
||||
}
|
||||
}
|
||||
|
||||
if (MOZ_LOG_TEST(GetDemuxerLog(), LogLevel::Verbose)) {
|
||||
bool isAudio = mInfo->GetAsAudioInfo();
|
||||
TimeUnit originalStart = TimeUnit::Invalid();
|
||||
TimeUnit originalEnd = TimeUnit::Invalid();
|
||||
if (sample->mOriginalPresentationWindow) {
|
||||
originalStart = sample->mOriginalPresentationWindow->mStart;
|
||||
originalEnd = sample->mOriginalPresentationWindow->mEnd;
|
||||
}
|
||||
LOG("%s packet demuxed (track id: %d): [%s,%s], duration: %s (original "
|
||||
"time: [%s,%s])",
|
||||
isAudio ? "Audio" : "Video", mInfo->mTrackId,
|
||||
sample->mTime.ToString().get(), sample->GetEndTime().ToString().get(),
|
||||
sample->mDuration.ToString().get(), originalStart.ToString().get(),
|
||||
originalEnd.ToString().get());
|
||||
}
|
||||
|
||||
return sample.forget();
|
||||
}
|
||||
|
||||
|
|
@ -544,23 +480,23 @@ RefPtr<MP4TrackDemuxer::SamplesPromise> MP4TrackDemuxer::GetSamples(
|
|||
|
||||
void MP4TrackDemuxer::SetNextKeyFrameTime() {
|
||||
mNextKeyframeTime.reset();
|
||||
TimeUnit frameTime = mIterator->GetNextKeyframeTime();
|
||||
if (frameTime.IsValid()) {
|
||||
mNextKeyframeTime.emplace(frameTime);
|
||||
Microseconds frameTime = mIterator->GetNextKeyframeTime();
|
||||
if (frameTime != -1) {
|
||||
mNextKeyframeTime.emplace(media::TimeUnit::FromMicroseconds(frameTime));
|
||||
}
|
||||
}
|
||||
|
||||
void MP4TrackDemuxer::Reset() {
|
||||
mQueuedSample = nullptr;
|
||||
// TODO: verify this
|
||||
mIterator->Seek(TimeUnit::FromNegativeInfinity());
|
||||
// TODO, Seek to first frame available, which isn't always 0.
|
||||
mIterator->Seek(0);
|
||||
SetNextKeyFrameTime();
|
||||
}
|
||||
|
||||
nsresult MP4TrackDemuxer::GetNextRandomAccessPoint(TimeUnit* aTime) {
|
||||
nsresult MP4TrackDemuxer::GetNextRandomAccessPoint(media::TimeUnit* aTime) {
|
||||
if (mNextKeyframeTime.isNothing()) {
|
||||
// There's no next key frame.
|
||||
*aTime = TimeUnit::FromInfinity();
|
||||
*aTime = media::TimeUnit::FromInfinity();
|
||||
} else {
|
||||
*aTime = mNextKeyframeTime.value();
|
||||
}
|
||||
|
|
@ -568,7 +504,8 @@ nsresult MP4TrackDemuxer::GetNextRandomAccessPoint(TimeUnit* aTime) {
|
|||
}
|
||||
|
||||
RefPtr<MP4TrackDemuxer::SkipAccessPointPromise>
|
||||
MP4TrackDemuxer::SkipToNextRandomAccessPoint(const TimeUnit& aTimeThreshold) {
|
||||
MP4TrackDemuxer::SkipToNextRandomAccessPoint(
|
||||
const media::TimeUnit& aTimeThreshold) {
|
||||
mQueuedSample = nullptr;
|
||||
// Loop until we reach the next keyframe after the threshold.
|
||||
uint32_t parsed = 0;
|
||||
|
|
@ -590,14 +527,14 @@ MP4TrackDemuxer::SkipToNextRandomAccessPoint(const TimeUnit& aTimeThreshold) {
|
|||
return SkipAccessPointPromise::CreateAndReject(std::move(failure), __func__);
|
||||
}
|
||||
|
||||
TimeIntervals MP4TrackDemuxer::GetBuffered() {
|
||||
media::TimeIntervals MP4TrackDemuxer::GetBuffered() {
|
||||
EnsureUpToDateIndex();
|
||||
AutoPinned<MediaResource> resource(mResource);
|
||||
MediaByteRangeSet byteRanges;
|
||||
nsresult rv = resource->GetCachedRanges(byteRanges);
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
return TimeIntervals();
|
||||
return media::TimeIntervals();
|
||||
}
|
||||
|
||||
return mIndex->ConvertByteRangesToTimeRanges(byteRanges);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ namespace mozilla {
|
|||
|
||||
template <typename T>
|
||||
struct MP4Interval {
|
||||
MP4Interval() : start{}, end{} {}
|
||||
MP4Interval() : start(0), end(0) {}
|
||||
MP4Interval(T aStart, T aEnd) : start(aStart), end(aEnd) {
|
||||
MOZ_ASSERT(aStart <= aEnd);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,10 +24,10 @@ using mozilla::media::TimeUnit;
|
|||
namespace mozilla {
|
||||
LazyLogModule gMP4MetadataLog("MP4Metadata");
|
||||
|
||||
IndiceWrapper::IndiceWrapper(Mp4parseByteData& aRustIndice) {
|
||||
IndiceWrapper::IndiceWrapper(Mp4parseByteData& aIndice) {
|
||||
mIndice.data = nullptr;
|
||||
mIndice.length = aRustIndice.length;
|
||||
mIndice.indices = aRustIndice.indices;
|
||||
mIndice.length = aIndice.length;
|
||||
mIndice.indices = aIndice.indices;
|
||||
}
|
||||
|
||||
size_t IndiceWrapper::Length() const { return mIndice.length; }
|
||||
|
|
@ -340,19 +340,6 @@ MP4Metadata::ResultAndTrackInfo MP4Metadata::GetTrackInfo(
|
|||
("track codec %s (%u)\n", codecString, codecType));
|
||||
#endif
|
||||
|
||||
Mp4parseTrackInfo track_info;
|
||||
rv = mp4parse_get_track_info(mParser.get(), trackIndex.value(), &track_info);
|
||||
if (rv != MP4PARSE_STATUS_OK) {
|
||||
MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
|
||||
("mp4parse_get_track_info returned error %d", rv));
|
||||
return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
|
||||
RESULT_DETAIL("Cannot parse %s track #%zu",
|
||||
TrackTypeToStr(aType), aTrackNumber)),
|
||||
nullptr};
|
||||
}
|
||||
|
||||
uint32_t timeScale = info.time_scale;
|
||||
|
||||
// This specialization interface is wild.
|
||||
UniquePtr<mozilla::TrackInfo> e;
|
||||
switch (aType) {
|
||||
|
|
@ -368,17 +355,8 @@ MP4Metadata::ResultAndTrackInfo MP4Metadata::GetTrackInfo(
|
|||
TrackTypeToStr(aType), aTrackNumber)),
|
||||
nullptr};
|
||||
}
|
||||
|
||||
auto indices = GetTrackIndice(info.track_id);
|
||||
if (!indices.Ref()) {
|
||||
// non fatal
|
||||
MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
|
||||
("Can't get index table for audio track, duration might be "
|
||||
"slightly incorrect"));
|
||||
}
|
||||
auto track = mozilla::MakeUnique<MP4AudioInfo>();
|
||||
MediaResult updateStatus =
|
||||
track->Update(&info, &audio, indices.Ref().get());
|
||||
MediaResult updateStatus = track->Update(&info, &audio);
|
||||
if (NS_FAILED(updateStatus)) {
|
||||
MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
|
||||
("Updating audio track failed with %s",
|
||||
|
|
@ -428,17 +406,12 @@ MP4Metadata::ResultAndTrackInfo MP4Metadata::GetTrackInfo(
|
|||
nullptr};
|
||||
}
|
||||
|
||||
e->mTimeScale = timeScale;
|
||||
|
||||
// No duration in track, use fragment_duration.
|
||||
if (e && !e->mDuration.IsPositive()) {
|
||||
Mp4parseFragmentInfo fragmentInfo;
|
||||
auto rv = mp4parse_get_fragment_info(mParser.get(), &fragmentInfo);
|
||||
Mp4parseFragmentInfo info;
|
||||
auto rv = mp4parse_get_fragment_info(mParser.get(), &info);
|
||||
if (rv == MP4PARSE_STATUS_OK) {
|
||||
// This doesn't use the time scale of the track, but the time scale
|
||||
// indicated in the mvhd box
|
||||
e->mDuration = TimeUnit(fragmentInfo.fragment_duration,
|
||||
AssertedCast<int64_t>(fragmentInfo.time_scale));
|
||||
e->mDuration = TimeUnit::FromMicroseconds(info.fragment_duration);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -459,8 +432,7 @@ MP4Metadata::ResultAndCryptoFile MP4Metadata::Crypto() const {
|
|||
return {NS_OK, &mCrypto};
|
||||
}
|
||||
|
||||
MP4Metadata::ResultAndIndice MP4Metadata::GetTrackIndice(
|
||||
uint32_t aTrackId) const {
|
||||
MP4Metadata::ResultAndIndice MP4Metadata::GetTrackIndice(uint32_t aTrackId) {
|
||||
Mp4parseByteData indiceRawData = {};
|
||||
|
||||
uint8_t fragmented = false;
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ class MP4Metadata : public DecoderDoctorLifeLogger<MP4Metadata> {
|
|||
ResultAndCryptoFile Crypto() const;
|
||||
|
||||
using ResultAndIndice = ResultAndType<mozilla::UniquePtr<IndiceWrapper>>;
|
||||
ResultAndIndice GetTrackIndice(uint32_t aTrackId) const;
|
||||
ResultAndIndice GetTrackIndice(uint32_t aTrackId);
|
||||
|
||||
nsresult Parse();
|
||||
|
||||
|
|
|
|||
|
|
@ -36,10 +36,13 @@ extern mozilla::LogModule* GetDemuxerLog();
|
|||
|
||||
namespace mozilla {
|
||||
|
||||
using TimeUnit = media::TimeUnit;
|
||||
|
||||
const uint32_t kKeyIdSize = 16;
|
||||
|
||||
// We ensure there are no gaps in samples' CTS between the last sample in a
|
||||
// Moof, and the first sample in the next Moof, if they're within these many
|
||||
// Microseconds of each other.
|
||||
const Microseconds CROSS_MOOF_CTS_MERGE_THRESHOLD = 1;
|
||||
|
||||
bool MoofParser::RebuildFragmentedIndex(const MediaByteRangeSet& aByteRanges) {
|
||||
BoxContext context(mSource, aByteRanges);
|
||||
return RebuildFragmentedIndex(context);
|
||||
|
|
@ -230,10 +233,10 @@ already_AddRefed<mozilla::MediaByteBuffer> MoofParser::Metadata() {
|
|||
return metadata.forget();
|
||||
}
|
||||
|
||||
MP4Interval<TimeUnit> MoofParser::GetCompositionRange(
|
||||
MP4Interval<Microseconds> MoofParser::GetCompositionRange(
|
||||
const MediaByteRangeSet& aByteRanges) {
|
||||
LOG_DEBUG(Moof, "Starting.");
|
||||
MP4Interval<TimeUnit> compositionRange;
|
||||
MP4Interval<Microseconds> compositionRange;
|
||||
BoxContext context(mSource, aByteRanges);
|
||||
for (size_t i = 0; i < mMoofs.Length(); i++) {
|
||||
Moof& moof = mMoofs[i];
|
||||
|
|
@ -245,8 +248,7 @@ MP4Interval<TimeUnit> MoofParser::GetCompositionRange(
|
|||
LOG_DEBUG(Moof,
|
||||
"Done, compositionRange.start=%" PRIi64
|
||||
", compositionRange.end=%" PRIi64 ".",
|
||||
compositionRange.start.ToMicroseconds(),
|
||||
compositionRange.end.ToMicroseconds());
|
||||
compositionRange.start, compositionRange.end);
|
||||
return compositionRange;
|
||||
}
|
||||
|
||||
|
|
@ -429,10 +431,7 @@ Moof::Moof(Box& aBox, const TrackParseMode& aTrackParseMode, Trex& aTrex,
|
|||
Mvhd& aMvhd, Mdhd& aMdhd, Edts& aEdts, Sinf& aSinf,
|
||||
uint64_t* aDecodeTime, bool aIsAudio,
|
||||
nsTArray<TrackEndCts>& aTracksEndCts)
|
||||
: mRange(aBox.Range()),
|
||||
mTfhd(aTrex),
|
||||
// Do not reporting discontuities less than 35ms
|
||||
mMaxRoundingError(TimeUnit::FromSeconds(0.035)) {
|
||||
: mRange(aBox.Range()), mTfhd(aTrex), mMaxRoundingError(35000) {
|
||||
LOG_DEBUG(
|
||||
Moof,
|
||||
"Starting, aTrackParseMode=%s, track#=%" PRIu32
|
||||
|
|
@ -491,12 +490,6 @@ Moof::Moof(Box& aBox, const TrackParseMode& aTrackParseMode, Trex& aTrex,
|
|||
// parsed, for the track we're parsing.
|
||||
for (auto& prevCts : aTracksEndCts) {
|
||||
if (prevCts.mTrackId == trackId) {
|
||||
// We ensure there are no gaps in samples' CTS between the last
|
||||
// sample in a Moof, and the first sample in the next Moof, if
|
||||
// they're within these many Microseconds of each other.
|
||||
const TimeUnit CROSS_MOOF_CTS_MERGE_THRESHOLD =
|
||||
TimeUnit::FromSeconds(aMvhd.mTimescale / 1000000.,
|
||||
aMvhd.mTimescale);
|
||||
// We have previously parsed a Moof for this track. Smooth the gap
|
||||
// between samples for this track across the Moof bounary.
|
||||
if (ctsOrder[0]->mCompositionRange.start > prevCts.mCtsEndTime &&
|
||||
|
|
@ -522,32 +515,31 @@ Moof::Moof(Box& aBox, const TrackParseMode& aTrackParseMode, Trex& aTrex,
|
|||
// sample as a Sample's duration is mCompositionRange.end -
|
||||
// mCompositionRange.start MSE's TrackBuffersManager expects dts that
|
||||
// increased by the sample's duration, so we rewrite the dts accordingly.
|
||||
TimeUnit presentationDuration =
|
||||
int64_t presentationDuration =
|
||||
ctsOrder.LastElement()->mCompositionRange.end -
|
||||
ctsOrder[0]->mCompositionRange.start;
|
||||
auto decodeOffset =
|
||||
aMdhd.ToTimeUnit((int64_t)*aDecodeTime - aEdts.mMediaStart);
|
||||
auto offsetOffset = aMvhd.ToTimeUnit(aEdts.mEmptyOffset);
|
||||
TimeUnit endDecodeTime =
|
||||
aMdhd.ToMicroseconds((int64_t)*aDecodeTime - aEdts.mMediaStart);
|
||||
auto offsetOffset = aMvhd.ToMicroseconds(aEdts.mEmptyOffset);
|
||||
int64_t endDecodeTime =
|
||||
(decodeOffset.isOk() && offsetOffset.isOk())
|
||||
? decodeOffset.unwrap() + offsetOffset.unwrap()
|
||||
: TimeUnit::Zero(aMvhd.mTimescale);
|
||||
TimeUnit decodeDuration = endDecodeTime - mIndex[0].mDecodeTime;
|
||||
double adjust = !presentationDuration.IsZero()
|
||||
? (double)decodeDuration.ToMicroseconds() /
|
||||
(double)presentationDuration.ToMicroseconds()
|
||||
: 0.;
|
||||
TimeUnit dtsOffset = mIndex[0].mDecodeTime;
|
||||
TimeUnit compositionDuration(0, aMvhd.mTimescale);
|
||||
: 0;
|
||||
int64_t decodeDuration = endDecodeTime - mIndex[0].mDecodeTime;
|
||||
double adjust = !!presentationDuration
|
||||
? (double)decodeDuration / presentationDuration
|
||||
: 0;
|
||||
int64_t dtsOffset = mIndex[0].mDecodeTime;
|
||||
int64_t compositionDuration = 0;
|
||||
// Adjust the dts, ensuring that the new adjusted dts will never be
|
||||
// greater than decodeTime (the next moof's decode start time).
|
||||
for (auto& sample : mIndex) {
|
||||
sample.mDecodeTime = dtsOffset + compositionDuration.MultDouble(adjust);
|
||||
sample.mDecodeTime = dtsOffset + int64_t(compositionDuration * adjust);
|
||||
compositionDuration += sample.mCompositionRange.Length();
|
||||
}
|
||||
mTimeRange =
|
||||
MP4Interval<TimeUnit>(ctsOrder[0]->mCompositionRange.start,
|
||||
ctsOrder.LastElement()->mCompositionRange.end);
|
||||
mTimeRange = MP4Interval<Microseconds>(
|
||||
ctsOrder[0]->mCompositionRange.start,
|
||||
ctsOrder.LastElement()->mCompositionRange.end);
|
||||
}
|
||||
ProcessCencAuxInfo(aSinf.mDefaultEncryptionType);
|
||||
}
|
||||
|
|
@ -732,8 +724,8 @@ void Moof::ParseTraf(Box& aBox, const TrackParseMode& aTrackParseMode,
|
|||
}
|
||||
|
||||
void Moof::FixRounding(const Moof& aMoof) {
|
||||
TimeUnit gap = aMoof.mTimeRange.start - mTimeRange.end;
|
||||
if (gap.IsPositive() && gap <= mMaxRoundingError) {
|
||||
Microseconds gap = aMoof.mTimeRange.start - mTimeRange.end;
|
||||
if (gap > 0 && gap <= mMaxRoundingError) {
|
||||
mTimeRange.end = aMoof.mTimeRange.start;
|
||||
}
|
||||
}
|
||||
|
|
@ -779,8 +771,8 @@ Result<Ok, nsresult> Moof::ParseTrun(Box& aBox, Mvhd& aMvhd, Mdhd& aMdhd,
|
|||
if (flags & 0x04) {
|
||||
MOZ_TRY_VAR(firstSampleFlags, reader->ReadU32());
|
||||
}
|
||||
nsTArray<MP4Interval<TimeUnit>> timeRanges;
|
||||
uint64_t decodeTime = *aDecodeTime;
|
||||
nsTArray<MP4Interval<Microseconds>> timeRanges;
|
||||
|
||||
if (!mIndex.SetCapacity(sampleCount, fallible)) {
|
||||
LOG_ERROR(Moof, "Out of Memory");
|
||||
|
|
@ -810,17 +802,19 @@ Result<Ok, nsresult> Moof::ParseTrun(Box& aBox, Mvhd& aMvhd, Mdhd& aMdhd,
|
|||
sample.mByteRange = MediaByteRange(offset, offset + sampleSize);
|
||||
offset += sampleSize;
|
||||
|
||||
TimeUnit decodeOffset, emptyOffset, startCts, endCts;
|
||||
MOZ_TRY_VAR(decodeOffset,
|
||||
aMdhd.ToTimeUnit((int64_t)decodeTime - aEdts.mMediaStart));
|
||||
MOZ_TRY_VAR(emptyOffset, aMvhd.ToTimeUnit(aEdts.mEmptyOffset));
|
||||
Microseconds decodeOffset, emptyOffset, startCts, endCts;
|
||||
MOZ_TRY_VAR(decodeOffset, aMdhd.ToMicroseconds((int64_t)decodeTime -
|
||||
aEdts.mMediaStart));
|
||||
MOZ_TRY_VAR(emptyOffset, aMvhd.ToMicroseconds(aEdts.mEmptyOffset));
|
||||
sample.mDecodeTime = decodeOffset + emptyOffset;
|
||||
MOZ_TRY_VAR(startCts, aMdhd.ToTimeUnit((int64_t)decodeTime + ctsOffset -
|
||||
aEdts.mMediaStart));
|
||||
MOZ_TRY_VAR(endCts, aMdhd.ToTimeUnit((int64_t)decodeTime + ctsOffset +
|
||||
sampleDuration - aEdts.mMediaStart));
|
||||
sample.mCompositionRange =
|
||||
MP4Interval<TimeUnit>(startCts + emptyOffset, endCts + emptyOffset);
|
||||
MOZ_TRY_VAR(startCts,
|
||||
aMdhd.ToMicroseconds((int64_t)decodeTime + ctsOffset -
|
||||
aEdts.mMediaStart));
|
||||
MOZ_TRY_VAR(endCts,
|
||||
aMdhd.ToMicroseconds((int64_t)decodeTime + ctsOffset +
|
||||
sampleDuration - aEdts.mMediaStart));
|
||||
sample.mCompositionRange = MP4Interval<Microseconds>(
|
||||
startCts + emptyOffset, endCts + emptyOffset);
|
||||
// Sometimes audio streams don't properly mark their samples as keyframes,
|
||||
// because every audio sample is a keyframe.
|
||||
sample.mSync = !(sampleFlags & 0x1010000) || aIsAudio;
|
||||
|
|
@ -832,9 +826,9 @@ Result<Ok, nsresult> Moof::ParseTrun(Box& aBox, Mvhd& aMvhd, Mdhd& aMdhd,
|
|||
}
|
||||
decodeTime += sampleDuration;
|
||||
}
|
||||
TimeUnit roundTime;
|
||||
MOZ_TRY_VAR(roundTime, aMdhd.ToTimeUnit(sampleCount));
|
||||
mMaxRoundingError = roundTime + mMaxRoundingError;
|
||||
Microseconds roundTime;
|
||||
MOZ_TRY_VAR(roundTime, aMdhd.ToMicroseconds(sampleCount));
|
||||
mMaxRoundingError += roundTime;
|
||||
|
||||
*aDecodeTime = decodeTime;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
#define MOOF_PARSER_H_
|
||||
|
||||
#include "mozilla/ResultExtensions.h"
|
||||
#include "TimeUnits.h"
|
||||
#include "mozilla/Variant.h"
|
||||
#include "Atom.h"
|
||||
#include "AtomType.h"
|
||||
|
|
@ -17,6 +16,8 @@
|
|||
|
||||
namespace mozilla {
|
||||
|
||||
typedef int64_t Microseconds;
|
||||
|
||||
class Box;
|
||||
class BoxContext;
|
||||
class BoxReader;
|
||||
|
|
@ -26,10 +27,10 @@ class Moof;
|
|||
// in the preceeding Moof, so that we can smooth tracks' timestamps
|
||||
// across Moofs.
|
||||
struct TrackEndCts {
|
||||
TrackEndCts(uint32_t aTrackId, const media::TimeUnit& aCtsEndTime)
|
||||
TrackEndCts(uint32_t aTrackId, Microseconds aCtsEndTime)
|
||||
: mTrackId(aTrackId), mCtsEndTime(aCtsEndTime) {}
|
||||
uint32_t mTrackId;
|
||||
media::TimeUnit mCtsEndTime;
|
||||
Microseconds mCtsEndTime;
|
||||
};
|
||||
|
||||
class Mvhd : public Atom {
|
||||
|
|
@ -38,12 +39,14 @@ class Mvhd : public Atom {
|
|||
: mCreationTime(0), mModificationTime(0), mTimescale(0), mDuration(0) {}
|
||||
explicit Mvhd(Box& aBox);
|
||||
|
||||
Result<media::TimeUnit, nsresult> ToTimeUnit(int64_t aTimescaleUnits) {
|
||||
Result<Microseconds, nsresult> ToMicroseconds(int64_t aTimescaleUnits) {
|
||||
if (!mTimescale) {
|
||||
NS_WARNING("invalid mTimescale");
|
||||
return Err(NS_ERROR_FAILURE);
|
||||
}
|
||||
return media::TimeUnit(aTimescaleUnits, mTimescale);
|
||||
int64_t major = aTimescaleUnits / mTimescale;
|
||||
int64_t remainder = aTimescaleUnits % mTimescale;
|
||||
return major * 1000000ll + remainder * 1000000ll / mTimescale;
|
||||
}
|
||||
|
||||
uint64_t mCreationTime;
|
||||
|
|
@ -138,8 +141,8 @@ class Edts : public Atom {
|
|||
struct Sample {
|
||||
mozilla::MediaByteRange mByteRange;
|
||||
mozilla::MediaByteRange mCencRange;
|
||||
media::TimeUnit mDecodeTime;
|
||||
MP4Interval<media::TimeUnit> mCompositionRange;
|
||||
Microseconds mDecodeTime;
|
||||
MP4Interval<Microseconds> mCompositionRange;
|
||||
bool mSync;
|
||||
};
|
||||
|
||||
|
|
@ -248,7 +251,7 @@ class Moof final : public Atom {
|
|||
|
||||
mozilla::MediaByteRange mRange;
|
||||
mozilla::MediaByteRange mMdatRange;
|
||||
MP4Interval<media::TimeUnit> mTimeRange;
|
||||
MP4Interval<Microseconds> mTimeRange;
|
||||
FallibleTArray<Sample> mIndex;
|
||||
|
||||
FallibleTArray<CencSampleEncryptionInfoEntry>
|
||||
|
|
@ -276,7 +279,7 @@ class Moof final : public Atom {
|
|||
// from that standard. I.e. this function is used to handle up auxiliary
|
||||
// information from the cenc and cbcs schemes.
|
||||
bool ProcessCencAuxInfo(AtomType aScheme);
|
||||
media::TimeUnit mMaxRoundingError;
|
||||
uint64_t mMaxRoundingError;
|
||||
};
|
||||
|
||||
DDLoggedTypeDeclName(MoofParser);
|
||||
|
|
@ -305,7 +308,7 @@ class MoofParser : public DecoderDoctorLifeLogger<MoofParser> {
|
|||
bool RebuildFragmentedIndex(const mozilla::MediaByteRangeSet& aByteRanges,
|
||||
bool* aCanEvict);
|
||||
bool RebuildFragmentedIndex(BoxContext& aContext);
|
||||
MP4Interval<media::TimeUnit> GetCompositionRange(
|
||||
MP4Interval<Microseconds> GetCompositionRange(
|
||||
const mozilla::MediaByteRangeSet& aByteRanges);
|
||||
bool ReachedEnd();
|
||||
void ParseMoov(Box& aBox);
|
||||
|
|
|
|||
|
|
@ -27,14 +27,14 @@ class MOZ_STACK_CLASS RangeFinder {
|
|||
// Ranges must be normalised for this to work
|
||||
}
|
||||
|
||||
bool Contains(const MediaByteRange& aByteRange);
|
||||
bool Contains(MediaByteRange aByteRange);
|
||||
|
||||
private:
|
||||
const MediaByteRangeSet& mRanges;
|
||||
size_t mIndex;
|
||||
};
|
||||
|
||||
bool RangeFinder::Contains(const MediaByteRange& aByteRange) {
|
||||
bool RangeFinder::Contains(MediaByteRange aByteRange) {
|
||||
if (mRanges.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -94,9 +94,9 @@ already_AddRefed<MediaRawData> SampleIterator::GetNext() {
|
|||
}
|
||||
|
||||
RefPtr<MediaRawData> sample = new MediaRawData();
|
||||
sample->mTimecode = s->mDecodeTime;
|
||||
sample->mTime = s->mCompositionRange.start;
|
||||
sample->mDuration = s->mCompositionRange.Length();
|
||||
sample->mTimecode = TimeUnit::FromMicroseconds(s->mDecodeTime);
|
||||
sample->mTime = TimeUnit::FromMicroseconds(s->mCompositionRange.start);
|
||||
sample->mDuration = TimeUnit::FromMicroseconds(s->mCompositionRange.Length());
|
||||
sample->mOffset = s->mByteRange.mStart;
|
||||
sample->mKeyframe = s->mSync;
|
||||
|
||||
|
|
@ -380,7 +380,7 @@ Sample* SampleIterator::Get() {
|
|||
|
||||
void SampleIterator::Next() { ++mCurrentSample; }
|
||||
|
||||
void SampleIterator::Seek(const TimeUnit& aTime) {
|
||||
void SampleIterator::Seek(Microseconds aTime) {
|
||||
size_t syncMoof = 0;
|
||||
size_t syncSample = 0;
|
||||
mCurrentMoof = 0;
|
||||
|
|
@ -403,7 +403,7 @@ void SampleIterator::Seek(const TimeUnit& aTime) {
|
|||
mCurrentSample = syncSample;
|
||||
}
|
||||
|
||||
TimeUnit SampleIterator::GetNextKeyframeTime() {
|
||||
Microseconds SampleIterator::GetNextKeyframeTime() {
|
||||
SampleIterator itr(*this);
|
||||
Sample* sample;
|
||||
while (!!(sample = itr.Get())) {
|
||||
|
|
@ -412,12 +412,12 @@ TimeUnit SampleIterator::GetNextKeyframeTime() {
|
|||
}
|
||||
itr.Next();
|
||||
}
|
||||
return TimeUnit::Invalid();
|
||||
return -1;
|
||||
}
|
||||
|
||||
MP4SampleIndex::MP4SampleIndex(const IndiceWrapper& aIndices,
|
||||
ByteStream* aSource, uint32_t aTrackId,
|
||||
bool aIsAudio, int32_t aTimeScale)
|
||||
bool aIsAudio)
|
||||
: mSource(aSource), mIsAudio(aIsAudio) {
|
||||
if (!aIndices.Length()) {
|
||||
mMoofParser =
|
||||
|
|
@ -427,16 +427,13 @@ MP4SampleIndex::MP4SampleIndex(const IndiceWrapper& aIndices,
|
|||
// OOM.
|
||||
return;
|
||||
}
|
||||
media::IntervalSet<TimeUnit> intervalTime;
|
||||
media::IntervalSet<int64_t> intervalTime;
|
||||
MediaByteRange intervalRange;
|
||||
bool haveSync = false;
|
||||
bool progressive = true;
|
||||
int64_t lastOffset = 0;
|
||||
for (size_t i = 0; i < aIndices.Length(); i++) {
|
||||
Indice indice{};
|
||||
int32_t timescale =
|
||||
mMoofParser ? AssertedCast<int32_t>(mMoofParser->mMvhd.mTimescale)
|
||||
: aTimeScale;
|
||||
Indice indice;
|
||||
if (!aIndices.GetIndice(i, indice)) {
|
||||
// Out of index?
|
||||
return;
|
||||
|
|
@ -450,10 +447,9 @@ MP4SampleIndex::MP4SampleIndex(const IndiceWrapper& aIndices,
|
|||
Sample sample;
|
||||
sample.mByteRange =
|
||||
MediaByteRange(indice.start_offset, indice.end_offset);
|
||||
sample.mCompositionRange = MP4Interval<media::TimeUnit>(
|
||||
TimeUnit(indice.start_composition, timescale),
|
||||
TimeUnit(indice.end_composition, timescale));
|
||||
sample.mDecodeTime = TimeUnit(indice.start_decode, timescale);
|
||||
sample.mCompositionRange = MP4Interval<Microseconds>(
|
||||
indice.start_composition, indice.end_composition);
|
||||
sample.mDecodeTime = indice.start_decode;
|
||||
sample.mSync = indice.sync || mIsAudio;
|
||||
// FIXME: Make this infallible after bug 968520 is done.
|
||||
MOZ_ALWAYS_TRUE(mIndex.AppendElement(sample, fallible));
|
||||
|
|
@ -479,11 +475,11 @@ MP4SampleIndex::MP4SampleIndex(const IndiceWrapper& aIndices,
|
|||
// OOM.
|
||||
return;
|
||||
}
|
||||
intervalTime = media::IntervalSet<TimeUnit>();
|
||||
intervalTime = media::IntervalSet<int64_t>();
|
||||
intervalRange = MediaByteRange();
|
||||
}
|
||||
intervalTime += media::Interval<TimeUnit>(sample.mCompositionRange.start,
|
||||
sample.mCompositionRange.end);
|
||||
intervalTime += media::Interval<int64_t>(sample.mCompositionRange.start,
|
||||
sample.mCompositionRange.end);
|
||||
intervalRange = intervalRange.Span(sample.mByteRange);
|
||||
}
|
||||
|
||||
|
|
@ -495,7 +491,7 @@ MP4SampleIndex::MP4SampleIndex(const IndiceWrapper& aIndices,
|
|||
auto& last = mDataOffset.LastElement();
|
||||
last.mEndOffset = indice.end_offset;
|
||||
last.mTime =
|
||||
MP4Interval<TimeUnit>(intervalTime.GetStart(), intervalTime.GetEnd());
|
||||
MP4Interval<int64_t>(intervalTime.GetStart(), intervalTime.GetEnd());
|
||||
} else {
|
||||
mDataOffset.Clear();
|
||||
}
|
||||
|
|
@ -536,33 +532,31 @@ void MP4SampleIndex::UpdateMoofIndex(const MediaByteRangeSet& aByteRanges,
|
|||
}
|
||||
}
|
||||
|
||||
TimeUnit MP4SampleIndex::GetEndCompositionIfBuffered(
|
||||
Microseconds MP4SampleIndex::GetEndCompositionIfBuffered(
|
||||
const MediaByteRangeSet& aByteRanges) {
|
||||
FallibleTArray<Sample>* index;
|
||||
if (mMoofParser) {
|
||||
int64_t base = mMoofParser->mMdhd.mTimescale;
|
||||
if (!mMoofParser->ReachedEnd() || mMoofParser->Moofs().IsEmpty()) {
|
||||
return TimeUnit::Zero(base);
|
||||
return 0;
|
||||
}
|
||||
index = &mMoofParser->Moofs().LastElement().mIndex;
|
||||
} else {
|
||||
index = &mIndex;
|
||||
}
|
||||
|
||||
int64_t base = mMoofParser->mMdhd.mTimescale;
|
||||
media::TimeUnit lastComposition = TimeUnit::Zero(base);
|
||||
Microseconds lastComposition = 0;
|
||||
RangeFinder rangeFinder(aByteRanges);
|
||||
for (size_t i = index->Length(); i--;) {
|
||||
const Sample& sample = (*index)[i];
|
||||
if (!rangeFinder.Contains(sample.mByteRange)) {
|
||||
return TimeUnit::Zero(base);
|
||||
return 0;
|
||||
}
|
||||
lastComposition = std::max(lastComposition, sample.mCompositionRange.end);
|
||||
if (sample.mSync) {
|
||||
return lastComposition;
|
||||
}
|
||||
}
|
||||
return TimeUnit::Zero(base);
|
||||
return 0;
|
||||
}
|
||||
|
||||
TimeIntervals MP4SampleIndex::ConvertByteRangesToTimeRanges(
|
||||
|
|
@ -591,15 +585,17 @@ TimeIntervals MP4SampleIndex::ConvertByteRangesToTimeRanges(
|
|||
for (size_t i = mDataOffset[start - 1].mIndex; i < mIndex.Length();
|
||||
i++) {
|
||||
if (range.ContainsStrict(mIndex[i].mByteRange)) {
|
||||
timeRanges += TimeInterval(mIndex[i].mCompositionRange.start,
|
||||
mIndex[i].mCompositionRange.end);
|
||||
timeRanges += TimeInterval(
|
||||
TimeUnit::FromMicroseconds(mIndex[i].mCompositionRange.start),
|
||||
TimeUnit::FromMicroseconds(mIndex[i].mCompositionRange.end));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (end > start) {
|
||||
for (uint32_t i = start; i < end; i++) {
|
||||
timeRanges += TimeInterval(mDataOffset[i].mTime.start,
|
||||
mDataOffset[i].mTime.end);
|
||||
timeRanges += TimeInterval(
|
||||
TimeUnit::FromMicroseconds(mDataOffset[i].mTime.start),
|
||||
TimeUnit::FromMicroseconds(mDataOffset[i].mTime.end));
|
||||
}
|
||||
}
|
||||
if (end < mDataOffset.Length()) {
|
||||
|
|
@ -607,8 +603,9 @@ TimeIntervals MP4SampleIndex::ConvertByteRangesToTimeRanges(
|
|||
for (size_t i = mDataOffset[end].mIndex;
|
||||
i < mIndex.Length() && range.ContainsStrict(mIndex[i].mByteRange);
|
||||
i++) {
|
||||
timeRanges += TimeInterval(mIndex[i].mCompositionRange.start,
|
||||
mIndex[i].mCompositionRange.end);
|
||||
timeRanges += TimeInterval(
|
||||
TimeUnit::FromMicroseconds(mIndex[i].mCompositionRange.start),
|
||||
TimeUnit::FromMicroseconds(mIndex[i].mCompositionRange.end));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -617,7 +614,7 @@ TimeIntervals MP4SampleIndex::ConvertByteRangesToTimeRanges(
|
|||
}
|
||||
|
||||
RangeFinder rangeFinder(aByteRanges);
|
||||
nsTArray<MP4Interval<media::TimeUnit>> timeRanges;
|
||||
nsTArray<MP4Interval<Microseconds>> timeRanges;
|
||||
nsTArray<FallibleTArray<Sample>*> indexes;
|
||||
if (mMoofParser) {
|
||||
// We take the index out of the moof parser and move it into a local
|
||||
|
|
@ -629,8 +626,8 @@ TimeIntervals MP4SampleIndex::ConvertByteRangesToTimeRanges(
|
|||
// We need the entire moof in order to play anything
|
||||
if (rangeFinder.Contains(moof.mRange)) {
|
||||
if (rangeFinder.Contains(moof.mMdatRange)) {
|
||||
MP4Interval<media::TimeUnit>::SemiNormalAppend(timeRanges,
|
||||
moof.mTimeRange);
|
||||
MP4Interval<Microseconds>::SemiNormalAppend(timeRanges,
|
||||
moof.mTimeRange);
|
||||
} else {
|
||||
indexes.AppendElement(&moof.mIndex);
|
||||
}
|
||||
|
|
@ -657,25 +654,26 @@ TimeIntervals MP4SampleIndex::ConvertByteRangesToTimeRanges(
|
|||
continue;
|
||||
}
|
||||
|
||||
MP4Interval<media::TimeUnit>::SemiNormalAppend(timeRanges,
|
||||
sample.mCompositionRange);
|
||||
MP4Interval<Microseconds>::SemiNormalAppend(timeRanges,
|
||||
sample.mCompositionRange);
|
||||
}
|
||||
}
|
||||
|
||||
// This fixes up when the compositon order differs from the byte range order
|
||||
nsTArray<MP4Interval<TimeUnit>> timeRangesNormalized;
|
||||
MP4Interval<media::TimeUnit>::Normalize(timeRanges, &timeRangesNormalized);
|
||||
nsTArray<MP4Interval<Microseconds>> timeRangesNormalized;
|
||||
MP4Interval<Microseconds>::Normalize(timeRanges, &timeRangesNormalized);
|
||||
// convert timeRanges.
|
||||
media::TimeIntervals ranges;
|
||||
for (size_t i = 0; i < timeRangesNormalized.Length(); i++) {
|
||||
ranges += media::TimeInterval(timeRangesNormalized[i].start,
|
||||
timeRangesNormalized[i].end);
|
||||
ranges += media::TimeInterval(
|
||||
media::TimeUnit::FromMicroseconds(timeRangesNormalized[i].start),
|
||||
media::TimeUnit::FromMicroseconds(timeRangesNormalized[i].end));
|
||||
}
|
||||
mLastBufferedRanges = ranges;
|
||||
return ranges;
|
||||
}
|
||||
|
||||
uint64_t MP4SampleIndex::GetEvictionOffset(const TimeUnit& aTime) {
|
||||
uint64_t MP4SampleIndex::GetEvictionOffset(Microseconds aTime) {
|
||||
uint64_t offset = std::numeric_limits<uint64_t>::max();
|
||||
if (mMoofParser) {
|
||||
// We need to keep the whole moof if we're keeping any of it because the
|
||||
|
|
@ -683,7 +681,7 @@ uint64_t MP4SampleIndex::GetEvictionOffset(const TimeUnit& aTime) {
|
|||
for (int i = 0; i < mMoofParser->Moofs().Length(); i++) {
|
||||
Moof& moof = mMoofParser->Moofs()[i];
|
||||
|
||||
if (!moof.mTimeRange.Length().IsZero() && moof.mTimeRange.end > aTime) {
|
||||
if (moof.mTimeRange.Length() && moof.mTimeRange.end > aTime) {
|
||||
offset = std::min(offset, uint64_t(std::min(moof.mRange.mStart,
|
||||
moof.mMdatRange.mStart)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,14 +21,16 @@ class IndiceWrapper;
|
|||
class MP4SampleIndex;
|
||||
struct Sample;
|
||||
|
||||
typedef int64_t Microseconds;
|
||||
|
||||
class SampleIterator {
|
||||
public:
|
||||
explicit SampleIterator(MP4SampleIndex* aIndex);
|
||||
~SampleIterator();
|
||||
bool HasNext();
|
||||
already_AddRefed<mozilla::MediaRawData> GetNext();
|
||||
void Seek(const media::TimeUnit& aTime);
|
||||
media::TimeUnit GetNextKeyframeTime();
|
||||
void Seek(Microseconds aTime);
|
||||
Microseconds GetNextKeyframeTime();
|
||||
|
||||
private:
|
||||
Sample* Get();
|
||||
|
|
@ -60,9 +62,9 @@ class MP4SampleIndex {
|
|||
struct Indice {
|
||||
uint64_t start_offset;
|
||||
uint64_t end_offset;
|
||||
int64_t start_composition;
|
||||
int64_t end_composition;
|
||||
int64_t start_decode;
|
||||
uint64_t start_composition;
|
||||
uint64_t end_composition;
|
||||
uint64_t start_decode;
|
||||
bool sync;
|
||||
};
|
||||
|
||||
|
|
@ -95,20 +97,20 @@ class MP4SampleIndex {
|
|||
uint32_t mIndex;
|
||||
int64_t mStartOffset;
|
||||
int64_t mEndOffset;
|
||||
MP4Interval<media::TimeUnit> mTime;
|
||||
MP4Interval<Microseconds> mTime;
|
||||
};
|
||||
|
||||
MP4SampleIndex(const mozilla::IndiceWrapper& aIndices, ByteStream* aSource,
|
||||
uint32_t aTrackId, bool aIsAudio, int32_t aTimeScale);
|
||||
uint32_t aTrackId, bool aIsAudio);
|
||||
|
||||
void UpdateMoofIndex(const mozilla::MediaByteRangeSet& aByteRanges,
|
||||
bool aCanEvict);
|
||||
void UpdateMoofIndex(const mozilla::MediaByteRangeSet& aByteRanges);
|
||||
media::TimeUnit GetEndCompositionIfBuffered(
|
||||
Microseconds GetEndCompositionIfBuffered(
|
||||
const mozilla::MediaByteRangeSet& aByteRanges);
|
||||
mozilla::media::TimeIntervals ConvertByteRangesToTimeRanges(
|
||||
const mozilla::MediaByteRangeSet& aByteRanges);
|
||||
uint64_t GetEvictionOffset(const media::TimeUnit& aTime);
|
||||
uint64_t GetEvictionOffset(Microseconds aTime);
|
||||
bool IsFragmented() { return !!mMoofParser; }
|
||||
|
||||
friend class SampleIterator;
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ static const uint32_t OGG_SEEK_FUZZ_USECS = 500000;
|
|||
|
||||
// The number of microseconds of "pre-roll" we use for Opus streams.
|
||||
// The specification recommends 80 ms.
|
||||
static const TimeUnit OGG_SEEK_OPUS_PREROLL = TimeUnit::FromMicroseconds(80000);
|
||||
static const int64_t OGG_SEEK_OPUS_PREROLL = 80 * USECS_PER_MS;
|
||||
|
||||
static Atomic<uint32_t> sStreamSourceID(0u);
|
||||
|
||||
|
|
@ -1095,8 +1095,7 @@ nsresult OggDemuxer::SeekInternal(TrackInfo::TrackType aType,
|
|||
int64_t startTime = StartTime(aType);
|
||||
int64_t endTime = mInfo.mMetadataDuration->ToMicroseconds() + startTime;
|
||||
if (aType == TrackInfo::kAudioTrack && mOpusState) {
|
||||
adjustedTarget =
|
||||
std::max(startTime, target - OGG_SEEK_OPUS_PREROLL.ToMicroseconds());
|
||||
adjustedTarget = std::max(startTime, target - OGG_SEEK_OPUS_PREROLL);
|
||||
}
|
||||
|
||||
if (!HaveStartTime(aType) || adjustedTarget == startTime) {
|
||||
|
|
@ -1870,8 +1869,7 @@ nsresult OggDemuxer::SeekInUnbuffered(TrackInfo::TrackType aType,
|
|||
}
|
||||
// Add in the Opus pre-roll if necessary, as well.
|
||||
if (aType == TrackInfo::kAudioTrack && mOpusState) {
|
||||
keyframeOffsetMs =
|
||||
std::max(keyframeOffsetMs, OGG_SEEK_OPUS_PREROLL.ToMilliseconds());
|
||||
keyframeOffsetMs = std::max(keyframeOffsetMs, OGG_SEEK_OPUS_PREROLL);
|
||||
}
|
||||
int64_t seekTarget = std::max(aStartTime, aTarget - keyframeOffsetMs);
|
||||
// Minimize the bisection search space using the known timestamps from the
|
||||
|
|
|
|||
|
|
@ -109,8 +109,6 @@ RefPtr<MediaDataDecoder::InitPromise> OpusDataDecoder::Init() {
|
|||
"Invalid Opus header: container CodecDelay and Opus pre-skip do not "
|
||||
"match!");
|
||||
}
|
||||
OPUS_DEBUG("Opus preskip in extradata: %" PRId64 "us",
|
||||
opusCodecSpecificData.mContainerCodecDelayMicroSeconds);
|
||||
|
||||
if (mInfo.mRate != (uint32_t)mOpusParser->mRate) {
|
||||
NS_WARNING("Invalid Opus header: container and codec rate do not match!");
|
||||
|
|
@ -257,23 +255,16 @@ RefPtr<MediaDataDecoder::DecodePromise> OpusDataDecoder::Decode(
|
|||
NS_ASSERTION(ret == frames, "Opus decoded too few audio samples");
|
||||
auto startTime = aSample->mTime;
|
||||
|
||||
OPUS_DEBUG("Decoding frames: [%lf, %lf]", aSample->mTime.ToSeconds(),
|
||||
aSample->GetEndTime().ToSeconds());
|
||||
|
||||
// Trim the initial frames while the decoder is settling.
|
||||
if (mSkip > 0) {
|
||||
int32_t skipFrames = std::min<int32_t>(mSkip, frames);
|
||||
int32_t keepFrames = frames - skipFrames;
|
||||
OPUS_DEBUG("Opus decoder trimming %d of %d frames", skipFrames, frames);
|
||||
OPUS_DEBUG("Opus decoder skipping %d of %d frames", skipFrames, frames);
|
||||
PodMove(buffer.get(), buffer.get() + skipFrames * channels,
|
||||
keepFrames * channels);
|
||||
startTime = startTime + media::TimeUnit(skipFrames, mOpusParser->mRate);
|
||||
startTime = startTime + FramesToTimeUnit(skipFrames, mOpusParser->mRate);
|
||||
frames = keepFrames;
|
||||
mSkip -= skipFrames;
|
||||
aSample->mTime += media::TimeUnit(skipFrames, 48000);
|
||||
aSample->mDuration -= media::TimeUnit(skipFrames, 48000);
|
||||
OPUS_DEBUG("Adjusted frame after trimming pre-roll: [%lf, %lf]",
|
||||
aSample->mTime.ToSeconds(), aSample->GetEndTime().ToSeconds());
|
||||
}
|
||||
|
||||
if (aSample->mDiscardPadding > 0) {
|
||||
|
|
@ -315,7 +306,7 @@ RefPtr<MediaDataDecoder::DecodePromise> OpusDataDecoder::Decode(
|
|||
}
|
||||
#endif
|
||||
|
||||
auto duration = media::TimeUnit(frames, mOpusParser->mRate);
|
||||
auto duration = FramesToTimeUnit(frames, mOpusParser->mRate);
|
||||
if (!duration.IsValid()) {
|
||||
return DecodePromise::CreateAndReject(
|
||||
MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
|
||||
|
|
@ -323,8 +314,8 @@ RefPtr<MediaDataDecoder::DecodePromise> OpusDataDecoder::Decode(
|
|||
__func__);
|
||||
}
|
||||
auto time = startTime -
|
||||
media::TimeUnit(mOpusParser->mPreSkip, mOpusParser->mRate) +
|
||||
media::TimeUnit(mFrames, mOpusParser->mRate);
|
||||
FramesToTimeUnit(mOpusParser->mPreSkip, mOpusParser->mRate) +
|
||||
FramesToTimeUnit(mFrames, mOpusParser->mRate);
|
||||
if (!time.IsValid()) {
|
||||
return DecodePromise::CreateAndReject(
|
||||
MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
|
||||
|
|
@ -333,9 +324,6 @@ RefPtr<MediaDataDecoder::DecodePromise> OpusDataDecoder::Decode(
|
|||
};
|
||||
|
||||
mFrames += frames;
|
||||
mTotalFrames += frames;
|
||||
|
||||
OPUS_DEBUG("Total frames so far: %" PRId64, mTotalFrames);
|
||||
|
||||
if (!frames) {
|
||||
return DecodePromise::CreateAndResolve(DecodedData(), __func__);
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@ class OpusDataDecoder final : public MediaDataDecoder,
|
|||
// will raise an error so we can indicate that the file is invalid.
|
||||
bool mPaddingDiscarded;
|
||||
int64_t mFrames;
|
||||
int64_t mTotalFrames = 0;
|
||||
Maybe<int64_t> mLastFrameTime;
|
||||
AutoTArray<uint8_t, 8> mMappingTable;
|
||||
AudioConfig::ChannelLayout::ChannelMap mChannelMap;
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ RefPtr<MediaDataDecoder::DecodePromise> VorbisDataDecoder::Decode(
|
|||
}
|
||||
}
|
||||
|
||||
auto duration = media::TimeUnit(frames, rate);
|
||||
auto duration = FramesToTimeUnit(frames, rate);
|
||||
if (!duration.IsValid()) {
|
||||
LOG(LogLevel::Warning, ("VorbisDecoder: invalid packet duration"));
|
||||
return DecodePromise::CreateAndReject(
|
||||
|
|
@ -210,7 +210,7 @@ RefPtr<MediaDataDecoder::DecodePromise> VorbisDataDecoder::Decode(
|
|||
RESULT_DETAIL("Overflow converting audio duration")),
|
||||
__func__);
|
||||
}
|
||||
auto total_duration = media::TimeUnit(mFrames, rate);
|
||||
auto total_duration = FramesToTimeUnit(mFrames, rate);
|
||||
if (!total_duration.IsValid()) {
|
||||
LOG(LogLevel::Warning, ("VorbisDecoder: invalid total duration"));
|
||||
return DecodePromise::CreateAndReject(
|
||||
|
|
|
|||
|
|
@ -32,17 +32,17 @@
|
|||
("%s: " arg, __func__, ##__VA_ARGS__))
|
||||
|
||||
using namespace mozilla;
|
||||
using media::TimeUnit;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
mozilla::LazyLogModule sAndroidDecoderModuleLog("AndroidDecoderModule");
|
||||
|
||||
nsCString TranslateMimeType(const nsACString& aMimeType) {
|
||||
const nsCString TranslateMimeType(const nsACString& aMimeType) {
|
||||
if (VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP8)) {
|
||||
static constexpr auto vp8 = "video/x-vnd.on2.vp8"_ns;
|
||||
return vp8;
|
||||
}
|
||||
if (VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP9)) {
|
||||
} else if (VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP9)) {
|
||||
static constexpr auto vp9 = "video/x-vnd.on2.vp9"_ns;
|
||||
return vp9;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ class AndroidDecoderModule : public PlatformDecoderModule {
|
|||
|
||||
extern LazyLogModule sAndroidDecoderModuleLog;
|
||||
|
||||
nsCString TranslateMimeType(const nsACString& aMimeType);
|
||||
const nsCString TranslateMimeType(const nsACString& aMimeType);
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ class RemoteVideoDecoder final : public RemoteDataDecoder {
|
|||
|
||||
class InputInfo {
|
||||
public:
|
||||
InputInfo() = default;
|
||||
InputInfo() {}
|
||||
|
||||
InputInfo(const int64_t aDurationUs, const gfx::IntSize& aImageSize,
|
||||
const gfx::IntSize& aDisplaySize)
|
||||
|
|
@ -91,9 +91,9 @@ class RemoteVideoDecoder final : public RemoteDataDecoder {
|
|||
mImageSize(aImageSize),
|
||||
mDisplaySize(aDisplaySize) {}
|
||||
|
||||
int64_t mDurationUs = {};
|
||||
gfx::IntSize mImageSize = {};
|
||||
gfx::IntSize mDisplaySize = {};
|
||||
int64_t mDurationUs;
|
||||
gfx::IntSize mImageSize;
|
||||
gfx::IntSize mDisplaySize;
|
||||
};
|
||||
|
||||
class CallbacksSupport final : public JavaCallbacksSupport {
|
||||
|
|
@ -109,7 +109,7 @@ class RemoteVideoDecoder final : public RemoteDataDecoder {
|
|||
java::SampleBuffer::Param aBuffer) override {
|
||||
MOZ_ASSERT(!aBuffer, "Video sample should be bufferless");
|
||||
// aSample will be implicitly converted into a GlobalRef.
|
||||
mDecoder->ProcessOutput(aSample);
|
||||
mDecoder->ProcessOutput(std::move(aSample));
|
||||
}
|
||||
|
||||
void HandleOutputFormatChanged(
|
||||
|
|
@ -539,7 +539,7 @@ class RemoteVideoDecoder final : public RemoteDataDecoder {
|
|||
|
||||
const VideoInfo mConfig;
|
||||
java::GeckoSurface::GlobalRef mSurface;
|
||||
AndroidSurfaceTextureHandle mSurfaceHandle{};
|
||||
AndroidSurfaceTextureHandle mSurfaceHandle;
|
||||
// Used to override the SurfaceTexture transform on some devices where the
|
||||
// decoder provides a buggy value.
|
||||
Maybe<gfx::Matrix4x4> mTransformOverride;
|
||||
|
|
@ -561,7 +561,7 @@ class RemoteVideoDecoder final : public RemoteDataDecoder {
|
|||
const Maybe<TrackingId> mTrackingId;
|
||||
// Can be accessed on any thread, but only written during init.
|
||||
// Pre-filled decode info used by the performance recorder.
|
||||
MediaInfoFlag mMediaInfoFlag = {};
|
||||
MediaInfoFlag mMediaInfoFlag;
|
||||
// Only accessed on mThread.
|
||||
// Records decode performance to the profiler.
|
||||
PerformanceRecorderMulti<DecodeStage> mPerformanceRecorder;
|
||||
|
|
@ -660,7 +660,7 @@ class RemoteAudioDecoder final : public RemoteDataDecoder {
|
|||
java::SampleBuffer::Param aBuffer) override {
|
||||
MOZ_ASSERT(aBuffer, "Audio sample should have buffer");
|
||||
// aSample will be implicitly converted into a GlobalRef.
|
||||
mDecoder->ProcessOutput(aSample, aBuffer);
|
||||
mDecoder->ProcessOutput(std::move(aSample), std::move(aBuffer));
|
||||
}
|
||||
|
||||
void HandleOutputFormatChanged(
|
||||
|
|
@ -801,8 +801,8 @@ class RemoteAudioDecoder final : public RemoteDataDecoder {
|
|||
mOutputSampleRate = aSampleRate;
|
||||
}
|
||||
|
||||
int32_t mOutputChannels{};
|
||||
int32_t mOutputSampleRate{};
|
||||
int32_t mOutputChannels;
|
||||
int32_t mOutputSampleRate;
|
||||
Maybe<TimeUnit> mFirstDemuxedSampleTime;
|
||||
};
|
||||
|
||||
|
|
@ -916,7 +916,7 @@ using CryptoInfoResult =
|
|||
Result<java::sdk::MediaCodec::CryptoInfo::LocalRef, nsresult>;
|
||||
|
||||
static CryptoInfoResult GetCryptoInfoFromSample(const MediaRawData* aSample) {
|
||||
const auto& cryptoObj = aSample->mCrypto;
|
||||
auto& cryptoObj = aSample->mCrypto;
|
||||
java::sdk::MediaCodec::CryptoInfo::LocalRef cryptoInfo;
|
||||
|
||||
if (!cryptoObj.IsEncrypted()) {
|
||||
|
|
@ -935,10 +935,10 @@ static CryptoInfoResult GetCryptoInfoFromSample(const MediaRawData* aSample) {
|
|||
cryptoObj.mPlainSizes.Length(), cryptoObj.mEncryptedSizes.Length());
|
||||
|
||||
uint32_t totalSubSamplesSize = 0;
|
||||
for (const auto& size : cryptoObj.mPlainSizes) {
|
||||
for (auto& size : cryptoObj.mPlainSizes) {
|
||||
totalSubSamplesSize += size;
|
||||
}
|
||||
for (const auto& size : cryptoObj.mEncryptedSizes) {
|
||||
for (auto& size : cryptoObj.mEncryptedSizes) {
|
||||
totalSubSamplesSize += size;
|
||||
}
|
||||
|
||||
|
|
@ -982,9 +982,7 @@ static CryptoInfoResult GetCryptoInfoFromSample(const MediaRawData* aSample) {
|
|||
tempIV.AppendElement(0);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(numSubSamples <= INT32_MAX);
|
||||
cryptoInfo->Set(static_cast<int32_t>(numSubSamples),
|
||||
mozilla::jni::IntArray::From(plainSizes),
|
||||
cryptoInfo->Set(numSubSamples, mozilla::jni::IntArray::From(plainSizes),
|
||||
mozilla::jni::IntArray::From(cryptoObj.mEncryptedSizes),
|
||||
mozilla::jni::ByteArray::From(cryptoObj.mKeyId),
|
||||
mozilla::jni::ByteArray::From(tempIV), mode);
|
||||
|
|
@ -1005,9 +1003,7 @@ RefPtr<MediaDataDecoder::DecodePromise> RemoteDataDecoder::Decode(
|
|||
const_cast<uint8_t*>(aSample->Data()), aSample->Size());
|
||||
|
||||
SetState(State::DRAINABLE);
|
||||
MOZ_ASSERT(aSample->Size() <= INT32_MAX);
|
||||
mInputBufferInfo->Set(0, static_cast<int32_t>(aSample->Size()),
|
||||
aSample->mTime.ToMicroseconds(), 0);
|
||||
mInputBufferInfo->Set(0, aSample->Size(), aSample->mTime.ToMicroseconds(), 0);
|
||||
CryptoInfoResult crypto = GetCryptoInfoFromSample(aSample);
|
||||
if (crypto.isErr()) {
|
||||
return DecodePromise::CreateAndReject(
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class RemoteDataDecoder : public MediaDataDecoder,
|
|||
}
|
||||
|
||||
protected:
|
||||
virtual ~RemoteDataDecoder() = default;
|
||||
virtual ~RemoteDataDecoder() {}
|
||||
RemoteDataDecoder(MediaData::Type aType, const nsACString& aMimeType,
|
||||
java::sdk::MediaFormat::Param aFormat,
|
||||
const nsString& aDrmStubId);
|
||||
|
|
|
|||
|
|
@ -41,15 +41,6 @@ AppleATDecoder::AppleATDecoder(const AudioInfo& aConfig)
|
|||
mFormatID = kAudioFormatMPEGLayer3;
|
||||
} else if (mConfig.mMimeType.EqualsLiteral("audio/mp4a-latm")) {
|
||||
mFormatID = kAudioFormatMPEG4AAC;
|
||||
if (aConfig.mCodecSpecificConfig.is<AacCodecSpecificData>()) {
|
||||
const AacCodecSpecificData& aacCodecSpecificData =
|
||||
aConfig.mCodecSpecificConfig.as<AacCodecSpecificData>();
|
||||
mEncoderDelay = aacCodecSpecificData.mEncoderDelayFrames;
|
||||
mTotalMediaFrames = aacCodecSpecificData.mMediaFrameCount;
|
||||
LOG("AppleATDecoder (aac), found encoder delay (%" PRIu32
|
||||
") and total frame count (%" PRIu64 ") in codec-specific side data",
|
||||
mEncoderDelay, mTotalMediaFrames);
|
||||
}
|
||||
} else {
|
||||
mFormatID = 0;
|
||||
}
|
||||
|
|
@ -181,8 +172,8 @@ static OSStatus _PassthroughInputDataCallback(
|
|||
RefPtr<MediaDataDecoder::DecodePromise> AppleATDecoder::Decode(
|
||||
MediaRawData* aSample) {
|
||||
MOZ_ASSERT(mThread->IsOnCurrentThread());
|
||||
LOG("mp4 input sample pts=%s duration=%s %s %llu bytes audio",
|
||||
aSample->mTime.ToString().get(), aSample->GetEndTime().ToString().get(),
|
||||
LOG("mp4 input sample %p %lld us %lld pts%s %llu bytes audio", aSample,
|
||||
aSample->mDuration.ToMicroseconds(), aSample->mTime.ToMicroseconds(),
|
||||
aSample->mKeyframe ? " keyframe" : "",
|
||||
(unsigned long long)aSample->Size());
|
||||
|
||||
|
|
@ -219,9 +210,8 @@ MediaResult AppleATDecoder::DecodeSample(MediaRawData* aSample) {
|
|||
nsTArray<AudioDataValue> outputData;
|
||||
UInt32 channels = mOutputFormat.mChannelsPerFrame;
|
||||
// Pick a multiple of the frame size close to a power of two
|
||||
// for efficient allocation. We're mainly using this decoder to decode AAC,
|
||||
// that has packets of 1024 audio frames.
|
||||
const uint32_t MAX_AUDIO_FRAMES = 1024;
|
||||
// for efficient allocation.
|
||||
const uint32_t MAX_AUDIO_FRAMES = 128;
|
||||
const uint32_t maxDecodedSamples = MAX_AUDIO_FRAMES * channels;
|
||||
|
||||
// Descriptions for _decompressed_ audio packets. ignored.
|
||||
|
|
@ -258,13 +248,12 @@ MediaResult AppleATDecoder::DecodeSample(MediaRawData* aSample) {
|
|||
LOG("Error decoding audio sample: %d\n", static_cast<int>(rv));
|
||||
return MediaResult(
|
||||
NS_ERROR_DOM_MEDIA_DECODE_ERR,
|
||||
RESULT_DETAIL("Error decoding audio sample: %d @ %s",
|
||||
static_cast<int>(rv), aSample->mTime.ToString().get()));
|
||||
RESULT_DETAIL("Error decoding audio sample: %d @ %lld",
|
||||
static_cast<int>(rv), aSample->mTime.ToMicroseconds()));
|
||||
}
|
||||
|
||||
if (numFrames) {
|
||||
AudioDataValue* outputFrames = decoded.get();
|
||||
outputData.AppendElements(outputFrames, numFrames * channels);
|
||||
outputData.AppendElements(decoded.get(), numFrames * channels);
|
||||
}
|
||||
|
||||
if (rv == kNoMoreDataErr) {
|
||||
|
|
@ -278,7 +267,7 @@ MediaResult AppleATDecoder::DecodeSample(MediaRawData* aSample) {
|
|||
|
||||
size_t numFrames = outputData.Length() / channels;
|
||||
int rate = mOutputFormat.mSampleRate;
|
||||
media::TimeUnit duration(numFrames, rate);
|
||||
media::TimeUnit duration = FramesToTimeUnit(numFrames, rate);
|
||||
if (!duration.IsValid()) {
|
||||
NS_WARNING("Invalid count of accumulated audio samples");
|
||||
return MediaResult(
|
||||
|
|
@ -288,9 +277,10 @@ MediaResult AppleATDecoder::DecodeSample(MediaRawData* aSample) {
|
|||
uint64_t(numFrames), rate));
|
||||
}
|
||||
|
||||
LOG("Decoded audio packet [%s, %s] (duration: %s)\n",
|
||||
aSample->mTime.ToString().get(), aSample->GetEndTime().ToString().get(),
|
||||
duration.ToString().get());
|
||||
#ifdef LOG_SAMPLE_DECODE
|
||||
LOG("pushed audio at time %lfs; duration %lfs\n",
|
||||
(double)aSample->mTime / USECS_PER_S, duration.ToSeconds());
|
||||
#endif
|
||||
|
||||
AudioSampleBuffer data(outputData.Elements(), outputData.Length());
|
||||
if (!data.Data()) {
|
||||
|
|
|
|||
|
|
@ -70,8 +70,6 @@ class AppleATDecoder final : public MediaDataDecoder,
|
|||
nsresult GetImplicitAACMagicCookie(const MediaRawData* aSample);
|
||||
nsresult SetupChannelLayout();
|
||||
uint32_t mParsedFramesForAACMagicCookie;
|
||||
uint32_t mEncoderDelay = 0;
|
||||
uint64_t mTotalMediaFrames = 0;
|
||||
bool mErrored;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@
|
|||
|
||||
namespace mozilla {
|
||||
|
||||
using TimeUnit = media::TimeUnit;
|
||||
|
||||
FFmpegAudioDecoder<LIBAV_VER>::FFmpegAudioDecoder(FFmpegLibWrapper* aLib,
|
||||
const AudioInfo& aConfig)
|
||||
: FFmpegDataDecoder(aLib, GetCodecId(aConfig.mMimeType)) {
|
||||
|
|
@ -29,12 +27,6 @@ FFmpegAudioDecoder<LIBAV_VER>::FFmpegAudioDecoder(FFmpegLibWrapper* aLib,
|
|||
// Ffmpeg expects the DecoderConfigDescriptor blob.
|
||||
mExtraData->AppendElements(
|
||||
*aacCodecSpecificData.mDecoderConfigDescriptorBinaryBlob);
|
||||
mEncoderDelay = aacCodecSpecificData.mEncoderDelayFrames;
|
||||
mEncoderPaddingOrTotalFrames = aacCodecSpecificData.mMediaFrameCount;
|
||||
FFMPEG_LOG("FFmpegAudioDecoder (aac), found encoder delay (%" PRIu32
|
||||
") and total frame count (%" PRIu64
|
||||
") in codec-specific side data",
|
||||
mEncoderDelay, TotalFrames());
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -48,11 +40,10 @@ FFmpegAudioDecoder<LIBAV_VER>::FFmpegAudioDecoder(FFmpegLibWrapper* aLib,
|
|||
const Mp3CodecSpecificData& mp3CodecSpecificData =
|
||||
aConfig.mCodecSpecificConfig.as<Mp3CodecSpecificData>();
|
||||
mEncoderDelay = mp3CodecSpecificData.mEncoderDelayFrames;
|
||||
mEncoderPaddingOrTotalFrames = mp3CodecSpecificData.mEncoderPaddingFrames;
|
||||
FFMPEG_LOG("FFmpegAudioDecoder (mp3), found encoder delay (%" PRIu32
|
||||
")"
|
||||
"and padding values (%" PRIu64 ") in codec-specific side-data",
|
||||
mEncoderDelay, Padding());
|
||||
mEncoderPadding = mp3CodecSpecificData.mEncoderPaddingFrames;
|
||||
FFMPEG_LOG("FFmpegAudioDecoder, found encoder delay (%" PRIu32
|
||||
") and padding values (%" PRIu32 ") in extra data",
|
||||
mEncoderDelay, mEncoderPadding);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -239,16 +230,7 @@ static AlignedAudioBuffer CopyAndPackAudio(AVFrame* aFrame,
|
|||
return audio;
|
||||
}
|
||||
|
||||
using ChannelLayout = AudioConfig::ChannelLayout;
|
||||
|
||||
uint64_t FFmpegAudioDecoder<LIBAV_VER>::Padding() const {
|
||||
MOZ_ASSERT(mCodecID == AV_CODEC_ID_MP3);
|
||||
return mEncoderPaddingOrTotalFrames;
|
||||
}
|
||||
uint64_t FFmpegAudioDecoder<LIBAV_VER>::TotalFrames() const {
|
||||
MOZ_ASSERT(mCodecID == AV_CODEC_ID_AAC);
|
||||
return mEncoderPaddingOrTotalFrames;
|
||||
}
|
||||
typedef AudioConfig::ChannelLayout ChannelLayout;
|
||||
|
||||
MediaResult FFmpegAudioDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample,
|
||||
uint8_t* aData, int aSize,
|
||||
|
|
@ -267,13 +249,13 @@ MediaResult FFmpegAudioDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample,
|
|||
}
|
||||
|
||||
if (!PrepareFrame()) {
|
||||
FFMPEG_LOG("FFmpegAudioDecoder: OOM in PrepareFrame");
|
||||
return MediaResult(
|
||||
NS_ERROR_OUT_OF_MEMORY,
|
||||
RESULT_DETAIL("FFmpeg audio decoder failed to allocate frame"));
|
||||
}
|
||||
|
||||
int64_t samplePosition = aSample->mOffset;
|
||||
media::TimeUnit pts = aSample->mTime;
|
||||
|
||||
while (packet.size > 0) {
|
||||
int decoded = false;
|
||||
|
|
@ -338,26 +320,36 @@ MediaResult FFmpegAudioDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample,
|
|||
AlignedAudioBuffer audio =
|
||||
CopyAndPackAudio(mFrame, numChannels, mFrame->nb_samples);
|
||||
if (!audio) {
|
||||
FFMPEG_LOG("FFmpegAudioDecoder: OOM");
|
||||
return MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__);
|
||||
}
|
||||
|
||||
FFMPEG_LOG("Packet decoded: [%s, %s] (%" PRId64 "us, %d frames)",
|
||||
aSample->mTime.ToString().get(),
|
||||
aSample->GetEndTime().ToString().get(),
|
||||
aSample->mDuration.ToMicroseconds(), mFrame->nb_samples);
|
||||
DebugOnly<bool> trimmed = false;
|
||||
if (mEncoderDelay) {
|
||||
trimmed = true;
|
||||
uint32_t toPop = std::min((uint32_t)mFrame->nb_samples, mEncoderDelay);
|
||||
audio.PopFront(toPop * numChannels);
|
||||
mFrame->nb_samples -= toPop;
|
||||
mEncoderDelay -= toPop;
|
||||
}
|
||||
|
||||
media::TimeUnit duration = TimeUnit(mFrame->nb_samples, samplingRate);
|
||||
if (aSample->mEOS && mEncoderPadding) {
|
||||
trimmed = true;
|
||||
uint32_t toTrim =
|
||||
std::min((uint32_t)mFrame->nb_samples, mEncoderPadding);
|
||||
mEncoderPadding -= toTrim;
|
||||
audio.PopBack(toTrim * numChannels);
|
||||
mFrame->nb_samples = audio.Length() / numChannels;
|
||||
}
|
||||
|
||||
media::TimeUnit duration =
|
||||
FramesToTimeUnit(mFrame->nb_samples, samplingRate);
|
||||
if (!duration.IsValid()) {
|
||||
FFMPEG_LOG("FFmpegAudioDecoder: invalid duration");
|
||||
return MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
|
||||
RESULT_DETAIL("Invalid sample duration"));
|
||||
}
|
||||
|
||||
media::TimeUnit pts = aSample->mTime;
|
||||
media::TimeUnit newpts = pts + duration;
|
||||
if (!newpts.IsValid()) {
|
||||
FFMPEG_LOG("FFmpegAudioDecoder: invalid PTS.");
|
||||
return MediaResult(
|
||||
NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
|
||||
RESULT_DETAIL("Invalid count of accumulated audio samples"));
|
||||
|
|
@ -366,7 +358,7 @@ MediaResult FFmpegAudioDecoder<LIBAV_VER>::DoDecode(MediaRawData* aSample,
|
|||
RefPtr<AudioData> data =
|
||||
new AudioData(samplePosition, pts, std::move(audio), numChannels,
|
||||
samplingRate, mCodecContext->channel_layout);
|
||||
MOZ_ASSERT(duration == data->mDuration, "must be equal");
|
||||
MOZ_ASSERT(duration == data->mDuration || trimmed, "must be equal");
|
||||
aResults.AppendElement(std::move(data));
|
||||
|
||||
pts = newpts;
|
||||
|
|
|
|||
|
|
@ -43,21 +43,8 @@ class FFmpegAudioDecoder<LIBAV_VER>
|
|||
private:
|
||||
MediaResult DoDecode(MediaRawData* aSample, uint8_t* aData, int aSize,
|
||||
bool* aGotFrame, DecodedData& aResults) override;
|
||||
// This method is to be called only when decoding mp3, in order to correctly
|
||||
// discard padding frames.
|
||||
uint64_t Padding() const;
|
||||
// This method is to be called only when decoding AAC, in order to correctly
|
||||
// discard padding frames, based on the number of frames decoded and the total
|
||||
// frame count of the media.
|
||||
uint64_t TotalFrames() const;
|
||||
// The number of frames of encoder delay, that need to be discarded at the
|
||||
// beginning of the stream.
|
||||
uint32_t mEncoderDelay = 0;
|
||||
// This holds either the encoder padding (when this decoder decodes mp3), or
|
||||
// the total frame count of the media (when this decoder decodes AAC).
|
||||
// It is best accessed via the `Padding` and `TotalFrames` methods, for
|
||||
// clarity.
|
||||
uint64_t mEncoderPaddingOrTotalFrames = 0;
|
||||
uint32_t mEncoderPadding = 0;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ MediaResult FFmpegDataDecoder<LIBAV_VER>::InitDecoder() {
|
|||
|
||||
AVCodec* codec = FindAVCodec(mLib, mCodecID);
|
||||
if (!codec) {
|
||||
FFMPEG_LOG(" couldn't find ffmpeg decoder for codec id %d", mCodecID);
|
||||
FFMPEG_LOG(" unable to find codec");
|
||||
return MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
|
||||
RESULT_DETAIL("unable to find codec"));
|
||||
}
|
||||
|
|
@ -95,7 +95,7 @@ MediaResult FFmpegDataDecoder<LIBAV_VER>::InitDecoder() {
|
|||
StaticMutexAutoLock mon(sMutex);
|
||||
|
||||
if (!(mCodecContext = mLib->avcodec_alloc_context3(codec))) {
|
||||
FFMPEG_LOG(" couldn't allocate ffmpeg context for codec %s", codec->name);
|
||||
FFMPEG_LOG(" couldn't init ffmpeg context");
|
||||
return MediaResult(NS_ERROR_OUT_OF_MEMORY,
|
||||
RESULT_DETAIL("Couldn't init ffmpeg context"));
|
||||
}
|
||||
|
|
@ -112,8 +112,7 @@ MediaResult FFmpegDataDecoder<LIBAV_VER>::InitDecoder() {
|
|||
InitCodecContext();
|
||||
MediaResult ret = AllocateExtraData();
|
||||
if (NS_FAILED(ret)) {
|
||||
FFMPEG_LOG(" couldn't allocate ffmpeg extra data for codec %s",
|
||||
codec->name);
|
||||
FFMPEG_LOG(" failed to allocate extra data");
|
||||
mLib->av_freep(&mCodecContext);
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -233,11 +232,9 @@ RefPtr<MediaDataDecoder::FlushPromise>
|
|||
FFmpegDataDecoder<LIBAV_VER>::ProcessFlush() {
|
||||
MOZ_ASSERT(mTaskQueue->IsOnCurrentThread());
|
||||
if (mCodecContext) {
|
||||
FFMPEG_LOG("FFmpegDataDecoder: flushing buffers");
|
||||
mLib->avcodec_flush_buffers(mCodecContext);
|
||||
}
|
||||
if (mCodecParser) {
|
||||
FFMPEG_LOG("FFmpegDataDecoder: reinitializing parser");
|
||||
mLib->av_parser_close(mCodecParser);
|
||||
mCodecParser = mLib->av_parser_init(mCodecID);
|
||||
}
|
||||
|
|
@ -249,7 +246,6 @@ void FFmpegDataDecoder<LIBAV_VER>::ProcessShutdown() {
|
|||
StaticMutexAutoLock mon(sMutex);
|
||||
|
||||
if (mCodecContext) {
|
||||
FFMPEG_LOG("FFmpegDataDecoder: shutdown");
|
||||
if (mCodecContext->extradata) {
|
||||
mLib->av_freep(&mCodecContext->extradata);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,6 @@
|
|||
#include "mozilla/Logging.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
#include "nsTArray.h"
|
||||
#include "BufferReader.h"
|
||||
#include "mozilla/ScopeExit.h"
|
||||
|
||||
#define LOG(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
|
||||
|
||||
|
|
@ -39,13 +37,6 @@ WMFAudioMFTManager::WMFAudioMFTManager(const AudioInfo& aConfig)
|
|||
aacCodecSpecificData.mDecoderConfigDescriptorBinaryBlob->Elements();
|
||||
configLength =
|
||||
aacCodecSpecificData.mDecoderConfigDescriptorBinaryBlob->Length();
|
||||
|
||||
mRemainingEncoderDelay = mEncoderDelay =
|
||||
aacCodecSpecificData.mEncoderDelayFrames;
|
||||
mTotalMediaFrames = aacCodecSpecificData.mMediaFrameCount;
|
||||
LOG("AudioMFT decoder: Found AAC decoder delay (%" PRIu32
|
||||
"frames) and total media frames (%" PRIu64 " frames)\n",
|
||||
mEncoderDelay, mTotalMediaFrames);
|
||||
} else {
|
||||
// Gracefully handle failure to cover all codec specific cases above. Once
|
||||
// we're confident there is no fall through from these cases above, we
|
||||
|
|
@ -222,12 +213,7 @@ WMFAudioMFTManager::Output(int64_t aStreamOffset, RefPtr<MediaData>& aOutData) {
|
|||
mFirstFrame = false;
|
||||
}
|
||||
|
||||
LONGLONG hns;
|
||||
hr = sample->GetSampleTime(&hns);
|
||||
if (FAILED(hr)) {
|
||||
return E_FAIL;
|
||||
}
|
||||
TimeUnit pts = TimeUnit::FromHns(hns, mAudioRate);
|
||||
TimeUnit pts = GetSampleTime(sample);
|
||||
NS_ENSURE_TRUE(pts.IsValid(), E_FAIL);
|
||||
|
||||
RefPtr<IMFMediaBuffer> buffer;
|
||||
|
|
@ -238,7 +224,6 @@ WMFAudioMFTManager::Output(int64_t aStreamOffset, RefPtr<MediaData>& aOutData) {
|
|||
// don't need to free it.
|
||||
DWORD maxLength = 0, currentLength = 0;
|
||||
hr = buffer->Lock(&data, &maxLength, ¤tLength);
|
||||
ScopeExit exit([buffer] { buffer->Unlock(); });
|
||||
NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
|
||||
|
||||
// Output is made of floats.
|
||||
|
|
@ -262,16 +247,18 @@ WMFAudioMFTManager::Output(int64_t aStreamOffset, RefPtr<MediaData>& aOutData) {
|
|||
return E_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
float* floatData = reinterpret_cast<float*>(data);
|
||||
PodCopy(audioData.Data(), floatData, numSamples);
|
||||
PodCopy(audioData.Data(), reinterpret_cast<float*>(data), numSamples);
|
||||
|
||||
TimeUnit duration(numFrames, mAudioRate);
|
||||
buffer->Unlock();
|
||||
|
||||
TimeUnit duration = FramesToTimeUnit(numFrames, mAudioRate);
|
||||
NS_ENSURE_TRUE(duration.IsValid(), E_FAIL);
|
||||
|
||||
const bool isAudioRateChangedToHigher = oldAudioRate < mAudioRate;
|
||||
if (IsPartialOutput(duration, isAudioRateChangedToHigher)) {
|
||||
LOG("Encounter a partial frame?! duration shrinks from %s to %s",
|
||||
mLastOutputDuration.ToString().get(), duration.ToString().get());
|
||||
LOG("Encounter a partial frame?! duration shrinks from %" PRId64
|
||||
" to %" PRId64,
|
||||
mLastOutputDuration.ToMicroseconds(), duration.ToMicroseconds());
|
||||
return MF_E_TRANSFORM_NEED_MORE_INPUT;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,10 +58,6 @@ class WMFAudioMFTManager : public MFTManager {
|
|||
media::TimeUnit mLastOutputDuration = media::TimeUnit::Zero();
|
||||
|
||||
bool mFirstFrame = true;
|
||||
|
||||
uint64_t mTotalMediaFrames = 0;
|
||||
uint32_t mEncoderDelay = 0;
|
||||
uint32_t mRemainingEncoderDelay = 0;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
|||
|
|
@ -101,15 +101,15 @@ RefPtr<MediaDataDecoder::DecodePromise> AudioTrimmer::HandleDecodedResult(
|
|||
return DecodePromise::CreateAndReject(std::move(aValue.RejectValue()),
|
||||
__func__);
|
||||
}
|
||||
TimeUnit rawStart = aRaw ? aRaw->mTime : TimeUnit::Zero();
|
||||
TimeUnit rawEnd = aRaw ? aRaw->GetEndTime() : TimeUnit::Zero();
|
||||
int64_t rawStart = aRaw ? aRaw->mTime.ToMicroseconds() : 0;
|
||||
int64_t rawEnd = aRaw ? aRaw->GetEndTime().ToMicroseconds() : 0;
|
||||
MediaDataDecoder::DecodedData results = std::move(aValue.ResolveValue());
|
||||
if (results.IsEmpty()) {
|
||||
// No samples returned, we assume this is due to the latency of the
|
||||
// decoder and that the related decoded sample will be returned during
|
||||
// the next call to Decode().
|
||||
LOGV("No sample returned for sample[%s, %s]", rawStart.ToString().get(),
|
||||
rawEnd.ToString().get());
|
||||
LOG("No sample returned for sample[%" PRId64 ",%" PRId64 "]", rawStart,
|
||||
rawEnd);
|
||||
}
|
||||
for (uint32_t i = 0; i < results.Length();) {
|
||||
const RefPtr<MediaData>& data = results[i];
|
||||
|
|
@ -118,10 +118,10 @@ RefPtr<MediaDataDecoder::DecodePromise> AudioTrimmer::HandleDecodedResult(
|
|||
if (mTrimmers.IsEmpty()) {
|
||||
// mTrimmers being empty can only occurs if the decoder returned more
|
||||
// frames than we pushed in. We can't handle this case, abort trimming.
|
||||
LOG("sample[%s, %s] (decoded[%s, %s] no trimming information)",
|
||||
rawStart.ToString().get(), rawEnd.ToString().get(),
|
||||
sampleInterval.mStart.ToString().get(),
|
||||
sampleInterval.mEnd.ToString().get());
|
||||
LOG("sample[%" PRId64 ",%" PRId64 "] (decoded[%" PRId64 ",%" PRId64
|
||||
"] no trimming information",
|
||||
rawStart, rawEnd, sampleInterval.mStart.ToMicroseconds(),
|
||||
sampleInterval.mEnd.ToMicroseconds());
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -130,28 +130,27 @@ RefPtr<MediaDataDecoder::DecodePromise> AudioTrimmer::HandleDecodedResult(
|
|||
mTrimmers.RemoveElementAt(0);
|
||||
if (!trimmer) {
|
||||
// Those frames didn't need trimming.
|
||||
LOGV("sample[%s, %s] (decoded[%s, %s] no trimming needed",
|
||||
rawStart.ToString().get(), rawEnd.ToString().get(),
|
||||
sampleInterval.mStart.ToString().get(),
|
||||
sampleInterval.mEnd.ToString().get());
|
||||
LOGV("sample[%" PRId64 ",%" PRId64 "] (decoded[%" PRId64 ",%" PRId64
|
||||
"] no trimming needed",
|
||||
rawStart, rawEnd, sampleInterval.mStart.ToMicroseconds(),
|
||||
sampleInterval.mEnd.ToMicroseconds());
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (!trimmer->Intersects(sampleInterval)) {
|
||||
LOGV(
|
||||
"sample[%s, %s] (decoded[%s, %s] would be empty after trimming, "
|
||||
"dropping it",
|
||||
rawStart.ToString().get(), rawEnd.ToString().get(),
|
||||
sampleInterval.mStart.ToString().get(),
|
||||
sampleInterval.mEnd.ToString().get());
|
||||
LOG("sample[%" PRId64 ",%" PRId64 "] (decoded[%" PRId64 ",%" PRId64
|
||||
"] would be empty after trimming, dropping it",
|
||||
rawStart, rawEnd, sampleInterval.mStart.ToMicroseconds(),
|
||||
sampleInterval.mEnd.ToMicroseconds());
|
||||
results.RemoveElementAt(i);
|
||||
continue;
|
||||
}
|
||||
LOGV("Trimming sample[%s,%s] to [%s,%s] (raw was:[%s, %s])",
|
||||
sampleInterval.mStart.ToString().get(),
|
||||
sampleInterval.mEnd.ToString().get(), trimmer->mStart.ToString().get(),
|
||||
trimmer->mEnd.ToString().get(), rawStart.ToString().get(),
|
||||
rawEnd.ToString().get());
|
||||
LOG("Trimming sample[%" PRId64 ",%" PRId64 "] to [%" PRId64 ",%" PRId64
|
||||
"] (raw "
|
||||
"was:[%" PRId64 ",%" PRId64 "])",
|
||||
sampleInterval.mStart.ToMicroseconds(),
|
||||
sampleInterval.mEnd.ToMicroseconds(), trimmer->mStart.ToMicroseconds(),
|
||||
trimmer->mEnd.ToMicroseconds(), rawStart, rawEnd);
|
||||
|
||||
TimeInterval trim({std::max(trimmer->mStart, sampleInterval.mStart),
|
||||
std::min(trimmer->mEnd, sampleInterval.mEnd)});
|
||||
|
|
@ -160,8 +159,9 @@ RefPtr<MediaDataDecoder::DecodePromise> AudioTrimmer::HandleDecodedResult(
|
|||
NS_ASSERTION(ok, "Trimming of audio sample failed");
|
||||
Unused << ok;
|
||||
if (sample->Frames() == 0) {
|
||||
LOGV("sample[%s, %s] is empty after trimming, dropping it",
|
||||
rawStart.ToString().get(), rawEnd.ToString().get());
|
||||
LOG("sample[%" PRId64 ",%" PRId64
|
||||
"] is empty after trimming, dropping it",
|
||||
rawStart, rawEnd);
|
||||
results.RemoveElementAt(i);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -174,7 +174,7 @@ RefPtr<MediaDataDecoder::DecodePromise> AudioTrimmer::DecodeBatch(
|
|||
nsTArray<RefPtr<MediaRawData>>&& aSamples) {
|
||||
MOZ_ASSERT(mThread->IsOnCurrentThread(),
|
||||
"We're not on the thread we were first initialized on");
|
||||
LOGV("DecodeBatch");
|
||||
LOG("DecodeBatch");
|
||||
|
||||
for (auto&& sample : aSamples) {
|
||||
PrepareTrimmers(sample);
|
||||
|
|
@ -201,17 +201,18 @@ void AudioTrimmer::PrepareTrimmers(MediaRawData* aRaw) {
|
|||
// the frame set by the demuxer and mTime and mDuration set to what it
|
||||
// should be after trimming.
|
||||
if (aRaw->mOriginalPresentationWindow) {
|
||||
LOGV("sample[%s, %s] has trimming info ([%s, %s]",
|
||||
aRaw->mOriginalPresentationWindow->mStart.ToString().get(),
|
||||
aRaw->mOriginalPresentationWindow->mEnd.ToString().get(),
|
||||
aRaw->mTime.ToString().get(), aRaw->GetEndTime().ToString().get());
|
||||
LOG("sample[%" PRId64 ",%" PRId64 "] has trimming info ([%" PRId64
|
||||
",%" PRId64 "]",
|
||||
aRaw->mOriginalPresentationWindow->mStart.ToMicroseconds(),
|
||||
aRaw->mOriginalPresentationWindow->mEnd.ToMicroseconds(),
|
||||
aRaw->mTime.ToMicroseconds(), aRaw->GetEndTime().ToMicroseconds());
|
||||
mTrimmers.AppendElement(
|
||||
Some(TimeInterval(aRaw->mTime, aRaw->GetEndTime())));
|
||||
aRaw->mTime = aRaw->mOriginalPresentationWindow->mStart;
|
||||
aRaw->mDuration = aRaw->mOriginalPresentationWindow->Length();
|
||||
} else {
|
||||
LOGV("sample[%s,%s] no trimming information", aRaw->mTime.ToString().get(),
|
||||
aRaw->GetEndTime().ToString().get());
|
||||
LOGV("sample[%" PRId64 ",%" PRId64 "] no trimming information",
|
||||
aRaw->mTime.ToMicroseconds(), aRaw->GetEndTime().ToMicroseconds());
|
||||
mTrimmers.AppendElement(Nothing());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2126,19 +2126,6 @@ var gEMENonMSEFailTests = [
|
|||
},
|
||||
];
|
||||
|
||||
// Test files that are supposed to loop seamlessly when played back.
|
||||
var gSeamlessLoopingTests = [
|
||||
// MP4 files dont't loop seamlessly yet, the seeking logic seeks to 0, not the
|
||||
// actual first packet, resulting in incorrect decoding.
|
||||
// See bug 1817989
|
||||
// { name: "sin-441-1s-44100-fdk_aac.mp4", type: "audio/mp4" },
|
||||
// { name: "sin-441-1s-44100-afconvert.mp4", type: "audio/mp4" },
|
||||
// { name: "sin-441-1s-44100.ogg", type: "audio/vorbis" },
|
||||
// { name: "sin-441-1s-44100.opus", type: "audio/opus" },
|
||||
{ name: "sin-441-1s-44100-lame.mp3", type: "audio/mpeg" },
|
||||
{ name: "sin-441-1s-44100.flac", type: "audio/flac" },
|
||||
];
|
||||
|
||||
// These are files that are used for video decode suspend in
|
||||
// background tabs tests.
|
||||
var gDecodeSuspendTests = [
|
||||
|
|
|
|||
|
|
@ -641,36 +641,21 @@ support-files =
|
|||
seek_support.js
|
||||
seekLies.sjs
|
||||
seek_with_sound.ogg^headers^
|
||||
short-aac-encrypted-audio.mp4
|
||||
short-aac-encrypted-audio.mp4^headers^
|
||||
short-audio-fragmented-cenc-without-pssh.mp4
|
||||
short-audio-fragmented-cenc-without-pssh.mp4^headers^
|
||||
short-cenc.mp4
|
||||
short-video.ogv
|
||||
short.mp4
|
||||
short.mp4.gz
|
||||
short.mp4^headers^
|
||||
# source file generated with:
|
||||
# > sox -V -r 44100 -n -b 16 -c 1 sin-441-1s-44100.wav synth 1 sin 441 vol -5dB
|
||||
# then encoded:
|
||||
# ffmpeg -i sin-441-1s-44100.wav sin-441-1s-44100-libfdk_aac.mp4
|
||||
sin-441-1s-44100-fdk_aac.mp4
|
||||
# afconvert -s 3 -f mp4f -d aac sin-4411-1s-441100.wav
|
||||
sin-441-1s-44100-afconvert.mp4
|
||||
# ffmpeg -i sin-441-1s-44100.wav sin-441-1s-44100-libfdk_lame.mp3
|
||||
sin-441-1s-44100-lame.mp3
|
||||
# ffmpeg -i sin-441-1s-44100.wav sin-441-1s-44100.ogg
|
||||
sin-441-1s-44100.ogg
|
||||
# ffmpeg -i sin-441-1s-44100.wav sin-441-1s-44100.opus
|
||||
sin-441-1s-44100.opus
|
||||
# ffmpeg -i sin-441-1s-44100.wav sin-441-1s-44100.flac
|
||||
sin-441-1s-44100.flac
|
||||
sine.webm
|
||||
sine.webm^headers^
|
||||
sintel-short-clearkey-subsample-encrypted-audio.webm
|
||||
sintel-short-clearkey-subsample-encrypted-audio.webm^headers^
|
||||
sintel-short-clearkey-subsample-encrypted-video.webm
|
||||
sintel-short-clearkey-subsample-encrypted-video.webm^headers^
|
||||
short.mp4
|
||||
short.mp4.gz
|
||||
short.mp4^headers^
|
||||
short-aac-encrypted-audio.mp4
|
||||
short-aac-encrypted-audio.mp4^headers^
|
||||
short-audio-fragmented-cenc-without-pssh.mp4
|
||||
short-audio-fragmented-cenc-without-pssh.mp4^headers^
|
||||
short-video.ogv
|
||||
short-video.ogv^headers^
|
||||
short-vp9-encrypted-video.mp4
|
||||
short-vp9-encrypted-video.mp4^headers^
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,9 +1,9 @@
|
|||
<!DOCTYPE html>
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for seamless loop of HTMLAudioElements</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="text/javascript" src="manifest.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -22,34 +22,19 @@ SimpleTest.waitForExplicitFinish();
|
|||
var DEBUG = true;
|
||||
var LOOPING_COUNT = 0;
|
||||
var MAX_LOOPING_COUNT = 10;
|
||||
// Test files are at 44100Hz, files are one second long, and contain therefore
|
||||
// 100 periods
|
||||
var TONE_FREQUENCY = 441;
|
||||
var TONE_FREQUENCY = 440;
|
||||
|
||||
(async function testSeamlesslooping() {
|
||||
let wavFileURL = {
|
||||
name: URL.createObjectURL(new Blob([createSrcBuffer()], { type: 'audio/wav'
|
||||
})),
|
||||
type: "audio/wav"
|
||||
};
|
||||
info(`- create looping audio element -`);
|
||||
let audio = createAudioElement();
|
||||
|
||||
let testURLs = gSeamlessLoopingTests.splice(0)
|
||||
testURLs.push(wavFileURL);
|
||||
for (let testFile of testURLs) {
|
||||
LOOPING_COUNT = 0;
|
||||
info(`- create looping audio element ${testFile.name}`);
|
||||
let audio = createAudioElement(testFile.name);
|
||||
info(`- start audio and analyze audio wave data to ensure looping audio without any silence or noise -`);
|
||||
await playAudioAndStartAnalyzingWaveData(audio);
|
||||
|
||||
info(`- start audio and analyze audio wave data to ensure looping audio without any silence or noise -`);
|
||||
await playAudioAndStartAnalyzingWaveData(audio);
|
||||
|
||||
info(`- test seamless looping multiples times -`);
|
||||
for (LOOPING_COUNT = 0; LOOPING_COUNT < MAX_LOOPING_COUNT; LOOPING_COUNT++) {
|
||||
await once(audio, "seeked");
|
||||
info(`- the round ${LOOPING_COUNT} of the seamless looping succeeds -`);
|
||||
}
|
||||
window.audio.remove();
|
||||
window.ac.close();
|
||||
info(`- test seamless looping multiples times -`);
|
||||
for (LOOPING_COUNT = 0; LOOPING_COUNT < MAX_LOOPING_COUNT; LOOPING_COUNT++) {
|
||||
await once(audio, "seeked");
|
||||
info(`- the round ${LOOPING_COUNT} of the seamless looping succeeds -`);
|
||||
}
|
||||
|
||||
info(`- end of seamless looping test -`);
|
||||
|
|
@ -111,10 +96,11 @@ function createSrcBuffer() {
|
|||
return buf;
|
||||
}
|
||||
|
||||
function createAudioElement(url) {
|
||||
function createAudioElement() {
|
||||
/* global audio */
|
||||
window.audio = document.createElement("audio");
|
||||
audio.src = url;
|
||||
audio.src = URL.createObjectURL(new Blob([createSrcBuffer()],
|
||||
{ type: 'audio/wav' }));
|
||||
audio.controls = true;
|
||||
audio.loop = true;
|
||||
document.body.appendChild(audio);
|
||||
|
|
@ -181,19 +167,19 @@ function createAudioWaveAnalyser(source) {
|
|||
let fftSize = 2 * buf.length;
|
||||
// first find a peak where we expect one.
|
||||
let binIndexTone = 1 + Math.round(TONE_FREQUENCY * fftSize / ctxSampleRate);
|
||||
ok(buf[binIndexTone] > -35,
|
||||
`Could not find a peak: ${buf[binIndexTone]} db at ${TONE_FREQUENCY}Hz
|
||||
(${source.src})`);
|
||||
ok(buf[binIndexTone] > -25,
|
||||
`Could not find a peak: ${buf[binIndexTone]} db at ${TONE_FREQUENCY}Hz`);
|
||||
|
||||
// check that the energy some octaves higher is very low.
|
||||
let binIndexOutsidePeak = 1 + Math.round(TONE_FREQUENCY * 4 * buf.length / ctxSampleRate);
|
||||
ok(buf[binIndexOutsidePeak] < -84,
|
||||
`Found unexpected high frequency content: ${buf[binIndexOutsidePeak]}db
|
||||
at ${TONE_FREQUENCY * 4}Hz (${source.src})`);
|
||||
ok(buf[binIndexOutsidePeak] < -110,
|
||||
`Found unexpected high frequency content: ${buf[binIndexOutsidePeak]}db at ${TONE_FREQUENCY * 4}Hz`);
|
||||
}
|
||||
|
||||
analyser.notifyAnalysis();
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,52 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
|
||||
rates = [44100, 48000]
|
||||
channels = [1, 2]
|
||||
duration = "0.5"
|
||||
frequency = "1000"
|
||||
volume = "-3dB"
|
||||
name = "half-a-second"
|
||||
formats = {
|
||||
"aac-in-adts": [{"codec": "aac", "extension": "aac"}],
|
||||
"mp3": [{"codec": "libmp3lame", "extension": "mp3"}],
|
||||
"mp4": [
|
||||
{
|
||||
"codec": "libopus",
|
||||
"extension": "mp4",
|
||||
},
|
||||
{"codec": "aac", "extension": "mp4"},
|
||||
],
|
||||
"ogg": [
|
||||
{"codec": "libvorbis", "extension": "ogg"},
|
||||
{"codec": "libopus", "extension": "opus"},
|
||||
],
|
||||
"flac": [
|
||||
{"codec": "flac", "extension": "flac"},
|
||||
],
|
||||
"webm": [
|
||||
{"codec": "libopus", "extension": "webm"},
|
||||
{"codec": "libvorbis", "extension": "webm"},
|
||||
],
|
||||
}
|
||||
|
||||
for rate in rates:
|
||||
for channel_count in channels:
|
||||
wav_filename = "{}-{}ch-{}.wav".format(name, channel_count, rate)
|
||||
wav_command = "sox -V -r {} -n -b 16 -c {} {} synth {} sin {} vol {}".format(
|
||||
rate, channel_count, wav_filename, duration, frequency, volume
|
||||
)
|
||||
print(wav_command)
|
||||
os.system(wav_command)
|
||||
for container, codecs in formats.items():
|
||||
for codec in codecs:
|
||||
encoded_filename = "{}-{}ch-{}-{}.{}".format(
|
||||
name, channel_count, rate, codec["codec"], codec["extension"]
|
||||
)
|
||||
print(encoded_filename)
|
||||
encoded_command = "ffmpeg -y -i {} -acodec {} {}".format(
|
||||
wav_filename, codec["codec"], encoded_filename
|
||||
)
|
||||
print(encoded_command)
|
||||
os.system(encoded_command)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue