gecko-dev/dom/media/VideoUtils.h

555 строки
18 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#ifndef VideoUtils_h
#define VideoUtils_h
#include "MediaInfo.h"
#include "mozilla/Attributes.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/MozPromise.h"
#include "mozilla/ReentrantMonitor.h"
#include "mozilla/RefPtr.h"
#include "mozilla/UniquePtr.h"
#include "nsAutoPtr.h"
#include "nsIThread.h"
#include "nsSize.h"
#include "nsRect.h"
#include "nsThreadUtils.h"
#include "prtime.h"
#include "AudioSampleFormat.h"
#include "TimeUnits.h"
#include "nsITimer.h"
#include "nsCOMPtr.h"
#include "VideoLimits.h"
using mozilla::CheckedInt64;
using mozilla::CheckedUint64;
using mozilla::CheckedInt32;
using mozilla::CheckedUint32;
// This file contains stuff we'd rather put elsewhere, but which is
// dependent on other changes which we don't want to wait for. We plan to
// remove this file in the near future.
// This belongs in xpcom/monitor/Monitor.h, once we've made
// mozilla::Monitor non-reentrant.
namespace mozilla {
class MediaContainerType;
// EME Key System String.
extern const nsLiteralCString kEMEKeySystemClearkey;
extern const nsLiteralCString kEMEKeySystemWidevine;
/**
* ReentrantMonitorConditionallyEnter
*
* Enters the supplied monitor only if the conditional value |aEnter| is true.
* E.g. Used to allow unmonitored read access on the decode thread,
* and monitored access on all other threads.
*/
class MOZ_STACK_CLASS ReentrantMonitorConditionallyEnter
{
public:
ReentrantMonitorConditionallyEnter(bool aEnter,
ReentrantMonitor &aReentrantMonitor) :
mReentrantMonitor(nullptr)
{
MOZ_COUNT_CTOR(ReentrantMonitorConditionallyEnter);
if (aEnter) {
mReentrantMonitor = &aReentrantMonitor;
NS_ASSERTION(mReentrantMonitor, "null monitor");
mReentrantMonitor->Enter();
}
}
~ReentrantMonitorConditionallyEnter(void)
{
if (mReentrantMonitor) {
mReentrantMonitor->Exit();
}
MOZ_COUNT_DTOR(ReentrantMonitorConditionallyEnter);
}
private:
// Restrict to constructor and destructor defined above.
ReentrantMonitorConditionallyEnter();
ReentrantMonitorConditionallyEnter(const ReentrantMonitorConditionallyEnter&);
ReentrantMonitorConditionallyEnter& operator =(const ReentrantMonitorConditionallyEnter&);
static void* operator new(size_t) CPP_THROW_NEW;
static void operator delete(void*);
ReentrantMonitor* mReentrantMonitor;
};
// Shuts down a thread asynchronously.
class ShutdownThreadEvent : public Runnable
{
public:
explicit ShutdownThreadEvent(nsIThread* aThread) : mThread(aThread) {}
~ShutdownThreadEvent() {}
NS_IMETHOD Run() override {
mThread->Shutdown();
mThread = nullptr;
return NS_OK;
}
private:
nsCOMPtr<nsIThread> mThread;
};
class MediaResource;
// Estimates the buffered ranges of a MediaResource using a simple
// (byteOffset/length)*duration method. Probably inaccurate, but won't
// do file I/O, and can be used when we don't have detailed knowledge
// of the byte->time mapping of a resource. aDurationUsecs is the duration
// of the media in microseconds. Estimated buffered ranges are stored in
// aOutBuffered. Ranges are 0-normalized, i.e. in the range of (0,duration].
media::TimeIntervals GetEstimatedBufferedTimeRanges(mozilla::MediaResource* aStream,
int64_t aDurationUsecs);
// Converts from number of audio frames (aFrames) to microseconds, given
// the specified audio rate (aRate).
CheckedInt64 FramesToUsecs(int64_t aFrames, uint32_t aRate);
// Converts from number of audio frames (aFrames) TimeUnit, given
// the specified audio rate (aRate).
media::TimeUnit FramesToTimeUnit(int64_t aFrames, uint32_t aRate);
// Perform aValue * aMul / aDiv, reducing the possibility of overflow due to
// aValue * aMul overflowing.
CheckedInt64 SaferMultDiv(int64_t aValue, uint32_t aMul, uint32_t aDiv);
// Converts from microseconds (aUsecs) to number of audio frames, given the
// specified audio rate (aRate). Stores the result in aOutFrames. Returns
// true if the operation succeeded, or false if there was an integer
// overflow while calulating the conversion.
CheckedInt64 UsecsToFrames(int64_t aUsecs, uint32_t aRate);
// Format TimeUnit as number of frames at given rate.
CheckedInt64 TimeUnitToFrames(const media::TimeUnit& aTime, uint32_t aRate);
// Converts milliseconds to seconds.
#define MS_TO_SECONDS(ms) ((double)(ms) / (PR_MSEC_PER_SEC))
// Converts seconds to milliseconds.
#define SECONDS_TO_MS(s) ((int)((s) * (PR_MSEC_PER_SEC)))
// Converts from seconds to microseconds. Returns failure if the resulting
// integer is too big to fit in an int64_t.
nsresult SecondsToUsecs(double aSeconds, int64_t& aOutUsecs);
// Scales the display rect aDisplay by aspect ratio aAspectRatio.
// Note that aDisplay must be validated by IsValidVideoRegion()
// before being used!
void ScaleDisplayByAspectRatio(nsIntSize& aDisplay, float aAspectRatio);
// Downmix Stereo audio samples to Mono.
// Input are the buffer contains stereo data and the number of frames.
void DownmixStereoToMono(mozilla::AudioDataValue* aBuffer,
uint32_t aFrames);
bool IsVideoContentType(const nsCString& aContentType);
// Returns true if it's safe to use aPicture as the picture to be
// extracted inside a frame of size aFrame, and scaled up to and displayed
// at a size of aDisplay. You should validate the frame, picture, and
// display regions before using them to display video frames.
bool IsValidVideoRegion(const nsIntSize& aFrame, const nsIntRect& aPicture,
const nsIntSize& aDisplay);
// Template to automatically set a variable to a value on scope exit.
// Useful for unsetting flags, etc.
template<typename T>
class AutoSetOnScopeExit {
public:
AutoSetOnScopeExit(T& aVar, T aValue)
: mVar(aVar)
, mValue(aValue)
{}
~AutoSetOnScopeExit() {
mVar = mValue;
}
private:
T& mVar;
const T mValue;
};
class SharedThreadPool;
// The MediaDataDecoder API blocks, with implementations waiting on platform
// decoder tasks. These platform decoder tasks are queued on a separate
// thread pool to ensure they can run when the MediaDataDecoder clients'
// thread pool is blocked. Tasks on the PLATFORM_DECODER thread pool must not
// wait on tasks in the PLAYBACK thread pool.
//
// No new dependencies on this mechanism should be added, as methods are being
// made async supported by MozPromise, making this unnecessary and
// permitting unifying the pool.
enum class MediaThreadType {
PLAYBACK, // MediaDecoderStateMachine and MediaDecoderReader
PLATFORM_DECODER
};
// Returns the thread pool that is shared amongst all decoder state machines
// for decoding streams.
already_AddRefed<SharedThreadPool> GetMediaThreadPool(MediaThreadType aType);
enum H264_PROFILE {
H264_PROFILE_UNKNOWN = 0,
H264_PROFILE_BASE = 0x42,
H264_PROFILE_MAIN = 0x4D,
H264_PROFILE_EXTENDED = 0x58,
H264_PROFILE_HIGH = 0x64,
};
enum H264_LEVEL {
H264_LEVEL_1 = 10,
H264_LEVEL_1_b = 11,
H264_LEVEL_1_1 = 11,
H264_LEVEL_1_2 = 12,
H264_LEVEL_1_3 = 13,
H264_LEVEL_2 = 20,
H264_LEVEL_2_1 = 21,
H264_LEVEL_2_2 = 22,
H264_LEVEL_3 = 30,
H264_LEVEL_3_1 = 31,
H264_LEVEL_3_2 = 32,
H264_LEVEL_4 = 40,
H264_LEVEL_4_1 = 41,
H264_LEVEL_4_2 = 42,
H264_LEVEL_5 = 50,
H264_LEVEL_5_1 = 51,
H264_LEVEL_5_2 = 52
};
// Extracts the H.264/AVC profile and level from an H.264 codecs string.
// H.264 codecs parameters have a type defined as avc1.PPCCLL, where
// PP = profile_idc, CC = constraint_set flags, LL = level_idc.
// See http://blog.pearce.org.nz/2013/11/what-does-h264avc1-codecs-parameters.html
// for more details.
// Returns false on failure.
bool
ExtractH264CodecDetails(const nsAString& aCodecs,
int16_t& aProfile,
int16_t& aLevel);
// Use a cryptographic quality PRNG to generate raw random bytes
// and convert that to a base64 string.
nsresult
GenerateRandomName(nsCString& aOutSalt, uint32_t aLength);
// This version returns a string suitable for use as a file or URL
// path. This is based on code from nsExternalAppHandler::SetUpTempFile.
nsresult
GenerateRandomPathName(nsCString& aOutSalt, uint32_t aLength);
already_AddRefed<TaskQueue>
CreateMediaDecodeTaskQueue(const char* aName);
// Iteratively invokes aWork until aCondition returns true, or aWork returns false.
// Use this rather than a while loop to avoid bogarting the task queue.
template<class Work, class Condition>
RefPtr<GenericPromise> InvokeUntil(Work aWork, Condition aCondition) {
RefPtr<GenericPromise::Private> p = new GenericPromise::Private(__func__);
if (aCondition()) {
p->Resolve(true, __func__);
}
struct Helper {
static void Iteration(const RefPtr<GenericPromise::Private>& aPromise, Work aLocalWork, Condition aLocalCondition) {
if (!aLocalWork()) {
aPromise->Reject(NS_ERROR_FAILURE, __func__);
} else if (aLocalCondition()) {
aPromise->Resolve(true, __func__);
} else {
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableFunction([aPromise, aLocalWork, aLocalCondition] () { Iteration(aPromise, aLocalWork, aLocalCondition); });
AbstractThread::GetCurrent()->Dispatch(r.forget());
}
}
};
Helper::Iteration(p, aWork, aCondition);
return p.forget();
}
// Simple timer to run a runnable after a timeout.
class SimpleTimer : public nsITimerCallback
{
public:
NS_DECL_ISUPPORTS
// Create a new timer to run aTask after aTimeoutMs milliseconds
// on thread aTarget. If aTarget is null, task is run on the main thread.
static already_AddRefed<SimpleTimer> Create(nsIRunnable* aTask,
uint32_t aTimeoutMs,
nsIEventTarget* aTarget = nullptr);
void Cancel();
NS_IMETHOD Notify(nsITimer *timer) override;
private:
virtual ~SimpleTimer() {}
nsresult Init(nsIRunnable* aTask, uint32_t aTimeoutMs, nsIEventTarget* aTarget);
RefPtr<nsIRunnable> mTask;
nsCOMPtr<nsITimer> mTimer;
};
void
LogToBrowserConsole(const nsAString& aMsg);
bool
ParseMIMETypeString(const nsAString& aMIMEType,
nsString& aOutContainerType,
nsTArray<nsString>& aOutCodecs);
bool
ParseCodecsString(const nsAString& aCodecs, nsTArray<nsString>& aOutCodecs);
bool
IsH264CodecString(const nsAString& aCodec);
bool
IsAACCodecString(const nsAString& aCodec);
bool
IsVP8CodecString(const nsAString& aCodec);
bool
IsVP9CodecString(const nsAString& aCodec);
// Try and create a TrackInfo with a given codec MIME type.
UniquePtr<TrackInfo>
CreateTrackInfoWithMIMEType(const nsACString& aCodecMIMEType);
// Try and create a TrackInfo with a given codec MIME type, and optional extra
// parameters from a container type (its MIME type and codecs are ignored).
UniquePtr<TrackInfo>
CreateTrackInfoWithMIMETypeAndContainerTypeExtraParameters(
const nsACString& aCodecMIMEType,
const MediaContainerType& aContainerType);
namespace detail {
// aString should start with aMajor + '/'.
constexpr bool
StartsWithMIMETypeMajor(const char* aString,
const char* aMajor, size_t aMajorRemaining)
{
return (aMajorRemaining == 0 && *aString == '/')
|| (*aString == *aMajor
&& StartsWithMIMETypeMajor(aString + 1,
aMajor + 1, aMajorRemaining - 1));
}
// aString should only contain [a-z0-9\-\.] and a final '\0'.
constexpr bool
EndsWithMIMESubtype(const char* aString, size_t aRemaining)
{
return aRemaining == 0
|| (((*aString >= 'a' && *aString <= 'z')
|| (*aString >= '0' && *aString <= '9')
|| *aString == '-'
|| *aString == '.')
&& EndsWithMIMESubtype(aString + 1, aRemaining - 1));
}
// Simple MIME-type literal string checker with a given (major) type.
// Only accepts "{aMajor}/[a-z0-9\-\.]+".
template <size_t MajorLengthPlus1>
constexpr bool
IsMIMETypeWithMajor(const char* aString, size_t aLength,
const char (&aMajor)[MajorLengthPlus1])
{
return aLength > MajorLengthPlus1 // Major + '/' + at least 1 char
&& StartsWithMIMETypeMajor(aString, aMajor, MajorLengthPlus1 - 1)
&& EndsWithMIMESubtype(aString + MajorLengthPlus1,
aLength - MajorLengthPlus1);
}
} // namespace detail
// Simple MIME-type string checker.
// Only accepts lowercase "{application,audio,video}/[a-z0-9\-\.]+".
// Add more if necessary.
constexpr bool
IsMediaMIMEType(const char* aString, size_t aLength)
{
return detail::IsMIMETypeWithMajor(aString, aLength, "application")
|| detail::IsMIMETypeWithMajor(aString, aLength, "audio")
|| detail::IsMIMETypeWithMajor(aString, aLength, "video");
}
// Simple MIME-type string literal checker.
// Only accepts lowercase "{application,audio,video}/[a-z0-9\-\.]+".
// Add more if necessary.
template <size_t LengthPlus1>
constexpr bool
IsMediaMIMEType(const char (&aString)[LengthPlus1])
{
return IsMediaMIMEType(aString, LengthPlus1 - 1);
}
// Simple MIME-type string checker.
// Only accepts lowercase "{application,audio,video}/[a-z0-9\-\.]+".
// Add more if necessary.
inline bool
IsMediaMIMEType(const nsACString& aString)
{
return IsMediaMIMEType(aString.Data(), aString.Length());
}
enum class StringListRangeEmptyItems
{
// Skip all empty items (empty string will process nothing)
// E.g.: "a,,b" -> ["a", "b"], "" -> nothing
Skip,
// Process all, except if string is empty
// E.g.: "a,,b" -> ["a", "", "b"], "" -> nothing
ProcessEmptyItems,
// Process all, including 1 empty item in an empty string
// E.g.: "a,,b" -> ["a", "", "b"], "" -> [""]
ProcessAll
};
template <typename String,
StringListRangeEmptyItems empties = StringListRangeEmptyItems::Skip>
class StringListRange
{
typedef typename String::char_type CharType;
typedef const CharType* Pointer;
public:
// Iterator into range, trims items and optionally skips empty items.
class Iterator
{
public:
bool operator!=(const Iterator& a) const
{
return mStart != a.mStart || mEnd != a.mEnd;
}
Iterator& operator++()
{
SearchItemAt(mComma + 1);
return *this;
}
// DereferencedType should be 'const nsDependent[C]String' pointing into
// mList (which is 'const ns[C]String&').
typedef decltype(Substring(Pointer(), Pointer())) DereferencedType;
DereferencedType operator*()
{
return Substring(mStart, mEnd);
}
private:
friend class StringListRange;
Iterator(const CharType* aRangeStart, uint32_t aLength)
: mRangeEnd(aRangeStart + aLength)
{
SearchItemAt(aRangeStart);
}
void SearchItemAt(Pointer start)
{
// First, skip leading whitespace.
for (Pointer p = start; ; ++p) {
if (p >= mRangeEnd) {
if (p > mRangeEnd
+ (empties != StringListRangeEmptyItems::Skip ? 1 : 0)) {
p = mRangeEnd
+ (empties != StringListRangeEmptyItems::Skip ? 1 : 0);
}
mStart = mEnd = mComma = p;
return;
}
auto c = *p;
if (c == CharType(',')) {
// Comma -> Empty item -> Skip or process?
if (empties != StringListRangeEmptyItems::Skip) {
mStart = mEnd = mComma = p;
return;
}
} else if (c != CharType(' ')) {
mStart = p;
break;
}
}
// Find comma, recording start of trailing space.
Pointer trailingWhitespace = nullptr;
for (Pointer p = mStart + 1; ; ++p) {
if (p >= mRangeEnd) {
mEnd = trailingWhitespace ? trailingWhitespace : p;
mComma = p;
return;
}
auto c = *p;
if (c == CharType(',')) {
mEnd = trailingWhitespace ? trailingWhitespace : p;
mComma = p;
return;
}
if (c == CharType(' ')) {
// Found a whitespace -> Record as trailing if not first one.
if (!trailingWhitespace) {
trailingWhitespace = p;
}
} else {
// Found a non-whitespace -> Reset trailing whitespace if needed.
if (trailingWhitespace) {
trailingWhitespace = nullptr;
}
}
}
}
const Pointer mRangeEnd;
Pointer mStart;
Pointer mEnd;
Pointer mComma;
};
explicit StringListRange(const String& aList) : mList(aList) {}
Iterator begin() const
{
return Iterator(mList.Data()
+ ((empties == StringListRangeEmptyItems::ProcessEmptyItems
&& mList.Length() == 0) ? 1 : 0),
mList.Length());
}
Iterator end() const
{
return Iterator(mList.Data() + mList.Length()
+ (empties != StringListRangeEmptyItems::Skip ? 1 : 0),
0);
}
private:
const String& mList;
};
template <StringListRangeEmptyItems empties = StringListRangeEmptyItems::Skip,
typename String>
StringListRange<String, empties>
MakeStringListRange(const String& aList)
{
return StringListRange<String, empties>(aList);
}
template <StringListRangeEmptyItems empties = StringListRangeEmptyItems::Skip,
typename ListString, typename ItemString>
static bool
StringListContains(const ListString& aList, const ItemString& aItem)
{
for (const auto& listItem : MakeStringListRange<empties>(aList)) {
if (listItem.Equals(aItem)) {
return true;
}
}
return false;
}
} // end namespace mozilla
#endif