gecko/content/media/VideoUtils.cpp
Chris Pearce 223e397746 Bug 778077 - Implement HTMLMediaElement.fastSeek(time). r=cajbir
Implement HTMLMediaElement.fastSeek(), basically by changing all the
MediaDecoderReader::Seek() overrides to not call
MediaDecoderReader::DecodeToTarget(), and have MediaDecoderReader::DecodeSeek()
call DecodeToTarget() if we're doing an accurate (non-fast) seek.

Update gizmo.mp4 to have a keyframe every second, instead of only 1 keyframe at
the start of stream. This makes the unit test I added more useful for mp4...

I pushed most of the seek target clamping logic in MediaDecoder up into
HTMLMediaElement, so that we're clamping in fewer places. Note
MediaDecoderStateMachine::Seek() still sanity checks the seek target.

We have to update the currentTime/MediaDecoder playback position after a seek
completes now, rather than assuming the seek always got it exactly right.

Removed those pesky assertions about seek target lying in the first frame after
seek, since actually sometimes the media doesn't have samples for all streams
after a seek (either due to the media being encoded like that, or because of a
bug in the platform's decoder, not entirely sure).

Green: https://tbpl.mozilla.org/?tree=Try&rev=b028258565e2
* * *
Bug 778077 - Fix up MediaOMXReader fastseek to ensure audio stream stays in sync with video stream. r=cajbir
2014-04-01 16:39:04 +13:00

194 lines
7.1 KiB
C++

/* 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 "VideoUtils.h"
#include "MediaResource.h"
#include "mozilla/dom/TimeRanges.h"
#include "nsMathUtils.h"
#include "nsSize.h"
#include "VorbisUtils.h"
#include "ImageContainer.h"
#include <stdint.h>
namespace mozilla {
using layers::PlanarYCbCrImage;
// Converts from number of audio frames to microseconds, given the specified
// audio rate.
CheckedInt64 FramesToUsecs(int64_t aFrames, uint32_t aRate) {
return (CheckedInt64(aFrames) * 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) {
return (CheckedInt64(aUsecs) * aRate) / USECS_PER_S;
}
nsresult SecondsToUsecs(double aSeconds, int64_t& aOutUsecs) {
if (aSeconds * double(USECS_PER_S) > INT64_MAX) {
return NS_ERROR_FAILURE;
}
aOutUsecs = int64_t(aSeconds * double(USECS_PER_S));
return NS_OK;
}
static int32_t ConditionDimension(float aValue)
{
// This will exclude NaNs and too-big values.
if (aValue > 1.0 && aValue <= INT32_MAX)
return int32_t(NS_round(aValue));
return 0;
}
void ScaleDisplayByAspectRatio(nsIntSize& aDisplay, float aAspectRatio)
{
if (aAspectRatio > 1.0) {
// Increase the intrinsic width
aDisplay.width = ConditionDimension(aAspectRatio * aDisplay.width);
} else {
// Increase the intrinsic height
aDisplay.height = ConditionDimension(aDisplay.height / aAspectRatio);
}
}
static int64_t BytesToTime(int64_t offset, int64_t length, int64_t durationUs) {
NS_ASSERTION(length > 0, "Must have positive length");
double r = double(offset) / double(length);
if (r > 1.0)
r = 1.0;
return int64_t(double(durationUs) * r);
}
void GetEstimatedBufferedTimeRanges(mozilla::MediaResource* aStream,
int64_t aDurationUsecs,
mozilla::dom::TimeRanges* aOutBuffered)
{
// Nothing to cache if the media takes 0us to play.
if (aDurationUsecs <= 0 || !aStream || !aOutBuffered)
return;
// Special case completely cached files. This also handles local files.
if (aStream->IsDataCachedToEndOfResource(0)) {
aOutBuffered->Add(0, double(aDurationUsecs) / USECS_PER_S);
return;
}
int64_t totalBytes = aStream->GetLength();
// If we can't determine the total size, pretend that we have nothing
// buffered. This will put us in a state of eternally-low-on-undecoded-data
// which is not great, but about the best we can do.
if (totalBytes <= 0)
return;
int64_t startOffset = aStream->GetNextCachedData(0);
while (startOffset >= 0) {
int64_t endOffset = aStream->GetCachedDataEnd(startOffset);
// Bytes [startOffset..endOffset] are cached.
NS_ASSERTION(startOffset >= 0, "Integer underflow in GetBuffered");
NS_ASSERTION(endOffset >= 0, "Integer underflow in GetBuffered");
int64_t startUs = BytesToTime(startOffset, totalBytes, aDurationUsecs);
int64_t endUs = BytesToTime(endOffset, totalBytes, aDurationUsecs);
if (startUs != endUs) {
aOutBuffered->Add(double(startUs) / USECS_PER_S,
double(endUs) / USECS_PER_S);
}
startOffset = aStream->GetNextCachedData(endOffset);
}
return;
}
int DownmixAudioToStereo(mozilla::AudioDataValue* buffer,
int channels, uint32_t frames)
{
int outChannels;
outChannels = 2;
#ifdef MOZ_SAMPLE_TYPE_FLOAT32
// Downmix matrix. Per-row normalization 1 for rows 3,4 and 2 for rows 5-8.
static const float dmatrix[6][8][2]= {
/*3*/{{0.5858f,0},{0.4142f,0.4142f},{0, 0.5858f}},
/*4*/{{0.4226f,0},{0, 0.4226f},{0.366f,0.2114f},{0.2114f,0.366f}},
/*5*/{{0.6510f,0},{0.4600f,0.4600f},{0, 0.6510f},{0.5636f,0.3254f},{0.3254f,0.5636f}},
/*6*/{{0.5290f,0},{0.3741f,0.3741f},{0, 0.5290f},{0.4582f,0.2645f},{0.2645f,0.4582f},{0.3741f,0.3741f}},
/*7*/{{0.4553f,0},{0.3220f,0.3220f},{0, 0.4553f},{0.3943f,0.2277f},{0.2277f,0.3943f},{0.2788f,0.2788f},{0.3220f,0.3220f}},
/*8*/{{0.3886f,0},{0.2748f,0.2748f},{0, 0.3886f},{0.3366f,0.1943f},{0.1943f,0.3366f},{0.3366f,0.1943f},{0.1943f,0.3366f},{0.2748f,0.2748f}},
};
// Re-write the buffer with downmixed data
for (uint32_t i = 0; i < frames; i++) {
float sampL = 0.0;
float sampR = 0.0;
for (int j = 0; j < channels; j++) {
sampL+=buffer[i*channels+j]*dmatrix[channels-3][j][0];
sampR+=buffer[i*channels+j]*dmatrix[channels-3][j][1];
}
buffer[i*outChannels]=sampL;
buffer[i*outChannels+1]=sampR;
}
#else
// Downmix matrix. Per-row normalization 1 for rows 3,4 and 2 for rows 5-8.
// Coefficients in Q14.
static const int16_t dmatrix[6][8][2]= {
/*3*/{{9598, 0},{6786,6786},{0, 9598}},
/*4*/{{6925, 0},{0, 6925},{5997,3462},{3462,5997}},
/*5*/{{10663,0},{7540,7540},{0, 10663},{9234,5331},{5331,9234}},
/*6*/{{8668, 0},{6129,6129},{0, 8668},{7507,4335},{4335,7507},{6129,6129}},
/*7*/{{7459, 0},{5275,5275},{0, 7459},{6460,3731},{3731,6460},{4568,4568},{5275,5275}},
/*8*/{{6368, 0},{4502,4502},{0, 6368},{5514,3184},{3184,5514},{5514,3184},{3184,5514},{4502,4502}}
};
// Re-write the buffer with downmixed data
for (uint32_t i = 0; i < frames; i++) {
int32_t sampL = 0;
int32_t sampR = 0;
for (int j = 0; j < channels; j++) {
sampL+=buffer[i*channels+j]*dmatrix[channels-3][j][0];
sampR+=buffer[i*channels+j]*dmatrix[channels-3][j][1];
}
sampL = (sampL + 8192)>>14;
buffer[i*outChannels] = static_cast<mozilla::AudioDataValue>(MOZ_CLIP_TO_15(sampL));
sampR = (sampR + 8192)>>14;
buffer[i*outChannels+1] = static_cast<mozilla::AudioDataValue>(MOZ_CLIP_TO_15(sampR));
}
#endif
return outChannels;
}
bool
IsVideoContentType(const nsCString& aContentType)
{
NS_NAMED_LITERAL_CSTRING(video, "video");
if (FindInReadable(video, aContentType)) {
return true;
}
return false;
}
bool
IsValidVideoRegion(const nsIntSize& aFrame, const nsIntRect& aPicture,
const nsIntSize& aDisplay)
{
return
aFrame.width <= PlanarYCbCrImage::MAX_DIMENSION &&
aFrame.height <= PlanarYCbCrImage::MAX_DIMENSION &&
aFrame.width * aFrame.height <= MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
aFrame.width * aFrame.height != 0 &&
aPicture.width <= PlanarYCbCrImage::MAX_DIMENSION &&
aPicture.x < PlanarYCbCrImage::MAX_DIMENSION &&
aPicture.x + aPicture.width < PlanarYCbCrImage::MAX_DIMENSION &&
aPicture.height <= PlanarYCbCrImage::MAX_DIMENSION &&
aPicture.y < PlanarYCbCrImage::MAX_DIMENSION &&
aPicture.y + aPicture.height < PlanarYCbCrImage::MAX_DIMENSION &&
aPicture.width * aPicture.height <= MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
aPicture.width * aPicture.height != 0 &&
aDisplay.width <= PlanarYCbCrImage::MAX_DIMENSION &&
aDisplay.height <= PlanarYCbCrImage::MAX_DIMENSION &&
aDisplay.width * aDisplay.height <= MAX_VIDEO_WIDTH * MAX_VIDEO_HEIGHT &&
aDisplay.width * aDisplay.height != 0;
}
} // end namespace mozilla