Merge mozilla-central to autoland.

This commit is contained in:
Cosmin Sabou 2023-05-19 14:56:17 +03:00
commit 3979c05ca1
166 changed files with 1169 additions and 3387 deletions

View file

@ -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
View file

@ -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",

View file

@ -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();
}

View file

@ -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++) {

View file

@ -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;

View file

@ -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

View file

@ -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;
}

View file

@ -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;

View file

@ -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.

View file

@ -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");
}

View file

@ -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);

View file

@ -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;

View file

@ -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) {

View file

@ -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();

View file

@ -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!

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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) {

View file

@ -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);

View file

@ -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:

View file

@ -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.");

View file

@ -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());

View file

@ -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.

View file

@ -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;

View file

@ -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;

View file

@ -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());

View file

@ -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)

View file

@ -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);
}

View file

@ -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",

View file

@ -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);

View file

@ -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;
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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

View file

@ -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) {

View file

@ -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;

View file

@ -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();
}

View file

@ -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;

View file

@ -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

View file

@ -13,7 +13,6 @@
namespace mozilla {
nsCString DumpTimeRanges(const media::TimeIntervals& aRanges);
nsCString DumpTimeRangesRaw(const media::TimeIntervals& aRanges);
} // namespace mozilla

View file

@ -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)

View file

@ -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);

View file

@ -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);
}

View file

@ -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();

View file

@ -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 {

View file

@ -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());

View file

@ -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]

View file

@ -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>

View file

@ -311,7 +311,6 @@ UNIFIED_SOURCES += [
"QueueObject.cpp",
"ReaderProxy.cpp",
"SeekJob.cpp",
"TimeUnits.cpp",
"Tracing.cpp",
"VideoFrameContainer.cpp",
"VideoPlaybackQuality.cpp",

View file

@ -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) {

View file

@ -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.

View file

@ -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();

View file

@ -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

View file

@ -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

View file

@ -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;
};

View file

@ -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);

View file

@ -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);
}

View file

@ -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;

View file

@ -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();

View file

@ -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;

View file

@ -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);

View file

@ -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)));
}

View file

@ -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;

View file

@ -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

View file

@ -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__);

View file

@ -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;

View file

@ -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(

View file

@ -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;
}

View file

@ -53,7 +53,7 @@ class AndroidDecoderModule : public PlatformDecoderModule {
extern LazyLogModule sAndroidDecoderModuleLog;
nsCString TranslateMimeType(const nsACString& aMimeType);
const nsCString TranslateMimeType(const nsACString& aMimeType);
} // namespace mozilla

View file

@ -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(

View file

@ -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);

View file

@ -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()) {

View file

@ -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;
};

View file

@ -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;

View file

@ -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

View file

@ -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);
}

View file

@ -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, &currentLength);
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;
}

View file

@ -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

View file

@ -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());
}
}

View file

@ -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 = [

View file

@ -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.

View file

@ -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.

View file

@ -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)

Some files were not shown because too many files have changed in this diff Show more