mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 04:41:11 +00:00
Bug 1921623 - Adding rescaling support to WMFMediaDataEncoder r=chunmin
This patch adds rescaling support to WMFMediaDataEncoder. It fixes distorted Images when rescaling. Furthermore, wpt tests are added to find similar errors on other platforms. Includes revisions by chunmin Differential Revision: https://phabricator.services.mozilla.com/D225445
This commit is contained in:
parent
1f35efdb47
commit
0ab86a4008
@ -14,6 +14,7 @@
|
||||
#include "YCbCrUtils.h"
|
||||
#include "libyuv/convert.h"
|
||||
#include "libyuv/convert_from_argb.h"
|
||||
#include "libyuv/scale_argb.h"
|
||||
#include "mozilla/PodOperations.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "mozilla/Result.h"
|
||||
@ -161,8 +162,14 @@ nsresult ConvertToI420(Image* aImage, uint8_t* aDestY, int aDestStrideY,
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t CeilingOfHalf(int32_t aValue) {
|
||||
MOZ_ASSERT(aValue >= 0);
|
||||
return aValue / 2 + (aValue % 2);
|
||||
}
|
||||
|
||||
nsresult ConvertToNV12(layers::Image* aImage, uint8_t* aDestY, int aDestStrideY,
|
||||
uint8_t* aDestUV, int aDestStrideUV) {
|
||||
uint8_t* aDestUV, int aDestStrideUV,
|
||||
gfx::IntSize aDestSize) {
|
||||
if (!aImage->IsValid()) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
@ -180,10 +187,70 @@ nsresult ConvertToNV12(layers::Image* aImage, uint8_t* aDestY, int aDestStrideY,
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
PlanarYCbCrData i420Source = *data;
|
||||
gfx::AlignedArray<uint8_t> scaledI420Buffer;
|
||||
|
||||
if (aDestSize != aImage->GetSize()) {
|
||||
const int32_t halfWidth = CeilingOfHalf(aDestSize.width);
|
||||
const uint32_t halfHeight = CeilingOfHalf(aDestSize.height);
|
||||
|
||||
CheckedInt<size_t> ySize(aDestSize.width);
|
||||
ySize *= aDestSize.height;
|
||||
|
||||
CheckedInt<size_t> uSize(halfWidth);
|
||||
uSize *= halfHeight;
|
||||
|
||||
CheckedInt<size_t> vSize(uSize);
|
||||
|
||||
CheckedInt<size_t> i420Size = ySize + uSize + vSize;
|
||||
if (!i420Size.isValid()) {
|
||||
NS_WARNING("ConvertToNV12: Destination size is too large");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
scaledI420Buffer.Realloc(i420Size.value());
|
||||
if (!scaledI420Buffer) {
|
||||
NS_WARNING(
|
||||
"ConvertToNV12: Failed to allocate buffer for rescaled I420 image");
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// Y plane
|
||||
i420Source.mYChannel = scaledI420Buffer;
|
||||
i420Source.mYStride = aDestSize.width;
|
||||
i420Source.mYSkip = 0;
|
||||
|
||||
// Cb plane (aka U)
|
||||
i420Source.mCbChannel = i420Source.mYChannel + ySize.value();
|
||||
i420Source.mCbSkip = 0;
|
||||
|
||||
// Cr plane (aka V)
|
||||
i420Source.mCrChannel = i420Source.mCbChannel + uSize.value();
|
||||
i420Source.mCrSkip = 0;
|
||||
|
||||
i420Source.mCbCrStride = halfWidth;
|
||||
i420Source.mChromaSubsampling =
|
||||
gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT;
|
||||
i420Source.mPictureRect = {0, 0, aDestSize.width, aDestSize.height};
|
||||
|
||||
nsresult rv = MapRv(libyuv::I420Scale(
|
||||
data->mYChannel, data->mYStride, data->mCbChannel, data->mCbCrStride,
|
||||
data->mCrChannel, data->mCbCrStride, aImage->GetSize().width,
|
||||
aImage->GetSize().height, i420Source.mYChannel, i420Source.mYStride,
|
||||
i420Source.mCbChannel, i420Source.mCbCrStride, i420Source.mCrChannel,
|
||||
i420Source.mCbCrStride, i420Source.mPictureRect.width,
|
||||
i420Source.mPictureRect.height, libyuv::FilterMode::kFilterBox));
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("ConvertToNV12: I420Scale failed");
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
return MapRv(libyuv::I420ToNV12(
|
||||
data->mYChannel, data->mYStride, data->mCbChannel, data->mCbCrStride,
|
||||
data->mCrChannel, data->mCbCrStride, aDestY, aDestStrideY, aDestUV,
|
||||
aDestStrideUV, aImage->GetSize().width, aImage->GetSize().height));
|
||||
i420Source.mYChannel, i420Source.mYStride, i420Source.mCbChannel,
|
||||
i420Source.mCbCrStride, i420Source.mCrChannel, i420Source.mCbCrStride,
|
||||
aDestY, aDestStrideY, aDestUV, aDestStrideUV, aDestSize.width,
|
||||
aDestSize.height));
|
||||
}
|
||||
|
||||
RefPtr<SourceSurface> surf = GetSourceSurface(aImage);
|
||||
@ -207,10 +274,51 @@ nsresult ConvertToNV12(layers::Image* aImage, uint8_t* aDestY, int aDestStrideY,
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
return MapRv(
|
||||
libyuv::ARGBToNV12(static_cast<uint8_t*>(map.GetData()), map.GetStride(),
|
||||
aDestY, aDestStrideY, aDestUV, aDestStrideUV,
|
||||
aImage->GetSize().width, aImage->GetSize().height));
|
||||
struct RgbSource {
|
||||
uint8_t* mBuffer;
|
||||
int32_t mStride;
|
||||
} rgbSource = {map.GetData(), map.GetStride()};
|
||||
|
||||
gfx::AlignedArray<uint8_t> scaledRGB32Buffer;
|
||||
|
||||
if (aDestSize != aImage->GetSize()) {
|
||||
CheckedInt<int> rgbaStride(aDestSize.width);
|
||||
rgbaStride *= 4;
|
||||
if (!rgbaStride.isValid()) {
|
||||
NS_WARNING("ConvertToNV12: Destination width is too large");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
CheckedInt<size_t> rgbSize(rgbaStride.value());
|
||||
rgbSize *= aDestSize.height;
|
||||
if (!rgbSize.isValid()) {
|
||||
NS_WARNING("ConvertToNV12: Destination size is too large");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
scaledRGB32Buffer.Realloc(rgbSize.value());
|
||||
if (!scaledRGB32Buffer) {
|
||||
NS_WARNING(
|
||||
"ConvertToNV12: Failed to allocate buffer for rescaled RGB32 image");
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
nsresult rv = MapRv(libyuv::ARGBScale(
|
||||
map.GetData(), map.GetStride(), aImage->GetSize().width,
|
||||
aImage->GetSize().height, scaledRGB32Buffer, rgbaStride.value(),
|
||||
aDestSize.width, aDestSize.height, libyuv::FilterMode::kFilterBox));
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("ConvertToNV12: ARGBScale failed");
|
||||
return rv;
|
||||
}
|
||||
|
||||
rgbSource.mBuffer = scaledRGB32Buffer;
|
||||
rgbSource.mStride = rgbaStride.value();
|
||||
}
|
||||
|
||||
return MapRv(libyuv::ARGBToNV12(rgbSource.mBuffer, rgbSource.mStride, aDestY,
|
||||
aDestStrideY, aDestUV, aDestStrideUV,
|
||||
aDestSize.width, aDestSize.height));
|
||||
}
|
||||
|
||||
static bool IsRGBX(const SurfaceFormat& aFormat) {
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "mozilla/AlreadyAddRefed.h"
|
||||
#include "nsError.h"
|
||||
#include "mozilla/gfx/Point.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@ -36,7 +37,8 @@ nsresult ConvertToI420(layers::Image* aImage, uint8_t* aDestY, int aDestStrideY,
|
||||
* Converts aImage to an NV12 image and writes it to the given buffers.
|
||||
*/
|
||||
nsresult ConvertToNV12(layers::Image* aImage, uint8_t* aDestY, int aDestStrideY,
|
||||
uint8_t* aDestUV, int aDestStrideUV);
|
||||
uint8_t* aDestUV, int aDestStrideUV,
|
||||
gfx::IntSize aDestSize);
|
||||
|
||||
/**
|
||||
* Converts aImage into an RGBA image in a specified format and writes it to the
|
||||
|
@ -210,56 +210,38 @@ already_AddRefed<IMFSample> WMFMediaDataEncoder::ConvertToNV12InputSample(
|
||||
AssertOnTaskQueue();
|
||||
MOZ_ASSERT(mEncoder);
|
||||
|
||||
struct NV12Info {
|
||||
int32_t mYStride = 0;
|
||||
int32_t mUVStride = 0;
|
||||
size_t mYLength = 0;
|
||||
size_t mBufferLength = 0;
|
||||
} info;
|
||||
size_t mBufferLength = 0;
|
||||
|
||||
if (const layers::PlanarYCbCrImage* image =
|
||||
aData->mImage->AsPlanarYCbCrImage()) {
|
||||
// Assume this is I420. If it's not, the whole process fails in
|
||||
// ConvertToNV12 below.
|
||||
const layers::PlanarYCbCrData* yuv = image->GetData();
|
||||
info.mYStride = yuv->mYStride;
|
||||
info.mUVStride = yuv->mCbCrStride * 2;
|
||||
info.mYLength = info.mYStride * yuv->YDataSize().height;
|
||||
info.mBufferLength =
|
||||
info.mYLength + (info.mUVStride * yuv->CbCrDataSize().height);
|
||||
} else {
|
||||
info.mYStride = aData->mImage->GetSize().width;
|
||||
info.mUVStride = info.mYStride;
|
||||
const int32_t ySrtride = mConfig.mSize.width;
|
||||
const int32_t uvStride = ySrtride;
|
||||
|
||||
const int32_t yHeight = aData->mImage->GetSize().height;
|
||||
const int32_t uvHeight = yHeight / 2;
|
||||
const int32_t yHeight = mConfig.mSize.height;
|
||||
const int32_t uvHeight = yHeight / 2 + (yHeight % 2);
|
||||
|
||||
CheckedInt<size_t> yLength(info.mYStride);
|
||||
yLength *= yHeight;
|
||||
if (!yLength.isValid()) {
|
||||
WMF_ENC_LOGE("yLength overflows");
|
||||
return nullptr;
|
||||
}
|
||||
info.mYLength = yLength.value();
|
||||
|
||||
CheckedInt<size_t> uvLength(info.mUVStride);
|
||||
uvLength *= uvHeight;
|
||||
if (!uvLength.isValid()) {
|
||||
WMF_ENC_LOGE("uvLength overflows");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CheckedInt<size_t> length(yLength);
|
||||
length += uvLength;
|
||||
if (!length.isValid()) {
|
||||
WMF_ENC_LOGE("length overflows");
|
||||
return nullptr;
|
||||
}
|
||||
info.mBufferLength = length.value();
|
||||
CheckedInt<size_t> yLength(ySrtride);
|
||||
yLength *= yHeight;
|
||||
if (!yLength.isValid()) {
|
||||
WMF_ENC_LOGE("dest yLength overflows");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CheckedInt<size_t> uvLength(uvStride);
|
||||
uvLength *= uvHeight;
|
||||
if (!uvLength.isValid()) {
|
||||
WMF_ENC_LOGE("dest uvLength overflows");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CheckedInt<size_t> length(yLength);
|
||||
length += uvLength;
|
||||
if (!length.isValid()) {
|
||||
WMF_ENC_LOGE("dest length overflows");
|
||||
return nullptr;
|
||||
}
|
||||
mBufferLength = length.value();
|
||||
|
||||
RefPtr<IMFSample> input;
|
||||
HRESULT hr = mEncoder->CreateInputSample(&input, info.mBufferLength);
|
||||
HRESULT hr = mEncoder->CreateInputSample(&input, mBufferLength);
|
||||
if (FAILED(hr)) {
|
||||
_com_error error(hr);
|
||||
WMF_ENC_LOGE("CreateInputSample: error = 0x%lX, %ls", hr,
|
||||
@ -276,7 +258,7 @@ already_AddRefed<IMFSample> WMFMediaDataEncoder::ConvertToNV12InputSample(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
hr = buffer->SetCurrentLength(info.mBufferLength);
|
||||
hr = buffer->SetCurrentLength(mBufferLength);
|
||||
if (FAILED(hr)) {
|
||||
_com_error error(hr);
|
||||
WMF_ENC_LOGE("SetCurrentLength: error = 0x%lX, %ls", hr,
|
||||
@ -292,9 +274,9 @@ already_AddRefed<IMFSample> WMFMediaDataEncoder::ConvertToNV12InputSample(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsresult rv =
|
||||
ConvertToNV12(aData->mImage, lockBuffer.Data(), info.mYStride,
|
||||
lockBuffer.Data() + info.mYLength, info.mUVStride);
|
||||
nsresult rv = ConvertToNV12(aData->mImage, lockBuffer.Data(), ySrtride,
|
||||
lockBuffer.Data() + yLength.value(), uvStride,
|
||||
mConfig.mSize);
|
||||
if (NS_FAILED(rv)) {
|
||||
WMF_ENC_LOGE("Failed to convert to NV12");
|
||||
return nullptr;
|
||||
|
@ -0,0 +1,378 @@
|
||||
[video-encoder-rescaling.https.any.html?h264_annexb]
|
||||
disabled:
|
||||
if (os == "android") or (version == "Ubuntu 18.04"): not implemented
|
||||
[Scaling Image in Encoding from 64 x 64 to 128 x 128 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 64 to 128 x 128 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 128 to 128 x 128 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 128 to 128 x 128 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 128 to 64 x 64 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 128 to 64 x 64 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 32 x 32 to 96 x 96 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 32 x 32 to 96 x 96 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 192 x 192 to 64 x 64 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 192 x 192 to 64 x 64 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 32 to 128 x 64 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 32 to 128 x 64 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 256 to 64 x 128 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 256 to 64 x 128 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 64 to 128 x 192 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 64 to 128 x 192 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 192 to 64 x 64 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 192 to 64 x 64 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
|
||||
[video-encoder-rescaling.https.any.html?vp9_p0]
|
||||
disabled:
|
||||
if (os == "android") or (bits == 32): not implemented
|
||||
[Scaling Image in Encoding from 64 x 64 to 128 x 128 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 64 to 128 x 128 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 128 to 128 x 128 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 128 to 128 x 128 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 128 to 64 x 64 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 128 to 64 x 64 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 32 x 32 to 96 x 96 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 32 x 32 to 96 x 96 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 192 x 192 to 64 x 64 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 192 x 192 to 64 x 64 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 32 to 128 x 64 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 32 to 128 x 64 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 256 to 64 x 128 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 256 to 64 x 128 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 64 to 128 x 192 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 64 to 128 x 192 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 192 to 64 x 64 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 192 to 64 x 64 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
|
||||
[video-encoder-rescaling.https.any.html?av1]
|
||||
disabled:
|
||||
if (os == "android") or (bits == 32): not implemented
|
||||
[Scaling Image in Encoding from 64 x 64 to 128 x 128 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 64 to 128 x 128 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 128 to 128 x 128 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 128 to 128 x 128 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 128 to 64 x 64 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 128 to 64 x 64 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 32 x 32 to 96 x 96 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 32 x 32 to 96 x 96 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 192 x 192 to 64 x 64 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 192 x 192 to 64 x 64 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 32 to 128 x 64 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 32 to 128 x 64 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 256 to 64 x 128 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 256 to 64 x 128 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 64 to 128 x 192 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 64 to 128 x 192 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 192 to 64 x 64 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 192 to 64 x 64 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
|
||||
[video-encoder-rescaling.https.any.html?h264_avc]
|
||||
disabled:
|
||||
if (os == "android") or (version == "Ubuntu 18.04"): not implemented
|
||||
[Scaling Image in Encoding from 64 x 64 to 128 x 128 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 64 to 128 x 128 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 128 to 128 x 128 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 128 to 128 x 128 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 128 to 64 x 64 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 128 to 64 x 64 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 32 x 32 to 96 x 96 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 32 x 32 to 96 x 96 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 192 x 192 to 64 x 64 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 192 x 192 to 64 x 64 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 32 to 128 x 64 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 32 to 128 x 64 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 256 to 64 x 128 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 256 to 64 x 128 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 64 to 128 x 192 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 64 to 128 x 192 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 192 to 64 x 64 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 192 to 64 x 64 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
|
||||
[video-encoder-rescaling.https.any.html?vp8]
|
||||
disabled:
|
||||
if (os == "android") or (bits == 32): not implemented
|
||||
[Scaling Image in Encoding from 64 x 64 to 128 x 128 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 64 to 128 x 128 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 128 to 128 x 128 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 128 to 128 x 128 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 128 to 64 x 64 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 128 to 64 x 64 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 32 x 32 to 96 x 96 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 32 x 32 to 96 x 96 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 192 x 192 to 64 x 64 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 192 x 192 to 64 x 64 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 32 to 128 x 64 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 32 to 128 x 64 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 256 to 64 x 128 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 256 to 64 x 128 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 64 to 128 x 192 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 64 x 64 to 128 x 192 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 192 to 64 x 64 Format: RGBX]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
||||
|
||||
[Scaling Image in Encoding from 128 x 192 to 64 x 64 Format: I420]
|
||||
expected:
|
||||
if os == "mac": FAIL
|
@ -0,0 +1,240 @@
|
||||
// META: global=window,dedicatedworker
|
||||
// META: variant=?av1
|
||||
// META: variant=?vp8
|
||||
// META: variant=?vp9_p0
|
||||
// META: variant=?h264_avc
|
||||
// META: variant=?h264_annexb
|
||||
|
||||
let BASECONFIG = null;
|
||||
promise_setup(async () => {
|
||||
const config = {
|
||||
'?av1': { codec: 'av01.0.04M.08' },
|
||||
'?vp8': { codec: 'vp8' },
|
||||
'?vp9_p0': { codec: 'vp09.00.10.08' },
|
||||
'?h264_avc': { codec: 'avc1.42001E', avc: { format: 'avc' } },
|
||||
'?h264_annexb': { codec: 'avc1.42001E', avc: { format: 'annexb' } },
|
||||
}[location.search];
|
||||
BASECONFIG = config;
|
||||
BASECONFIG.framerate = 30;
|
||||
BASECONFIG.bitrate = 3000000;
|
||||
});
|
||||
|
||||
function scaleFrame(oneFrame, scaleSize) {
|
||||
const { w: width, h: height } = scaleSize;
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let encodedResult;
|
||||
const encoder = new VideoEncoder({
|
||||
output: (chunk, metadata) => {
|
||||
encodedResult = { chunk, metadata };
|
||||
},
|
||||
error: (error) => {
|
||||
reject(error);
|
||||
},
|
||||
});
|
||||
|
||||
const encoderConfig = {
|
||||
...BASECONFIG,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
encoder.configure(encoderConfig);
|
||||
|
||||
encoder.encode(oneFrame);
|
||||
await encoder.flush();
|
||||
|
||||
let decodedResult;
|
||||
const decoder = new VideoDecoder({
|
||||
output(frame) {
|
||||
decodedResult = frame;
|
||||
},
|
||||
error: (error) => {
|
||||
reject(error);
|
||||
},
|
||||
});
|
||||
|
||||
decoder.configure(encodedResult.metadata.decoderConfig);
|
||||
decoder.decode(encodedResult.chunk);
|
||||
await decoder.flush();
|
||||
|
||||
encoder.close();
|
||||
decoder.close();
|
||||
|
||||
resolve(decodedResult);
|
||||
});
|
||||
}
|
||||
|
||||
// This function determines which quadrant of a rectangle (width * height)
|
||||
// a point (x, y) falls into, and returns the corresponding color for that
|
||||
// quadrant. The rectangle is divided into four quadrants:
|
||||
// < w >
|
||||
// ^ +--------+--------+
|
||||
// | (0, 0) | (1, 0) |
|
||||
// h +--------+--------+
|
||||
// | (0, 1) | (1, 1) |
|
||||
// v +--------+--------+
|
||||
//
|
||||
// The colors array must contain at least four colors, each corresponding
|
||||
// to one of the quadrants:
|
||||
// - colors[0] : top-left (0, 0)
|
||||
// - colors[1] : top-right (1, 0)
|
||||
// - colors[2] : bottom-left (0, 1)
|
||||
// - colors[3] : bottom-right (1, 1)
|
||||
function getColor(x, y, width, height, colors, channel) {
|
||||
// Determine which quadrant (x, y) belongs to.
|
||||
const xIndex = x * 2 >= width ? 1 : 0;
|
||||
const yIndex = y * 2 >= height ? 1 : 0;
|
||||
|
||||
const index = yIndex * 2 + xIndex;
|
||||
return colors[index][channel];
|
||||
}
|
||||
|
||||
|
||||
// All channel paramaters are arrays with the index being the channel
|
||||
// channelOffset: The offset for each channel in allocated data array.
|
||||
// channelWidth: The width of ecah channel in pixels
|
||||
// channelPlaneWidths: the width of the channel used to calculate the image's memory size.
|
||||
// For interleaved data, only the first width is set to the width of the full data in bytes; see RGBX for an example.
|
||||
// channelStrides: The stride (in bytes) for each channel.
|
||||
// channelSteps: The step size in bytes to move from one pixel to the next horizontally within the same row
|
||||
// channelHeights: The height (in bytes) for each channel.
|
||||
// channelFourColor: The four colors encoded in the color format of the channels
|
||||
//
|
||||
function createImageData({ channelOffsets, channelWidths, channelPlaneWidths, channelStrides, channelSteps, channelHeights, channelFourColors }) {
|
||||
let memSize = 0;
|
||||
for (let chan = 0; chan < 3; chan++) {
|
||||
memSize += channelHeights[chan] * channelPlaneWidths[chan];
|
||||
}
|
||||
let data = new Uint8Array(memSize);
|
||||
for (let chan = 0; chan < 3; chan++) {
|
||||
for (let y = 0; y < channelHeights[chan]; y++) {
|
||||
for (let x = 0; x < channelWidths[chan]; x++) {
|
||||
data[channelOffsets[chan] + Math.floor(channelStrides[chan] * y) + Math.floor(channelSteps[chan] * x)] =
|
||||
getColor(x, y, channelWidths[chan], channelHeights[chan], channelFourColors, chan);
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function testImageData(data, { channelOffsets, channelWidths, channelStrides, channelSteps, channelHeights, channelFourColors }) {
|
||||
let err = 0.;
|
||||
for (let chan = 0; chan < 3; chan++) {
|
||||
for (let y = 0; y < channelHeights[chan]; y++) {
|
||||
for (let x = 0; x < channelWidths[chan]; x++) {
|
||||
const curdata = data[channelOffsets[chan] + Math.floor(channelStrides[chan] * y) + Math.floor(channelSteps[chan] * x)];
|
||||
const diff = curdata - getColor(x, y, channelWidths[chan], channelHeights[chan], channelFourColors, chan);
|
||||
err += Math.abs(diff);
|
||||
}
|
||||
}
|
||||
}
|
||||
return err / data.length / 3 / 255 * 4;
|
||||
}
|
||||
|
||||
function rgb2yuv(rgb) {
|
||||
let y = rgb[0] * .299000 + rgb[1] * .587000 + rgb[2] * .114000
|
||||
let u = rgb[0] * -.168736 + rgb[1] * -.331264 + rgb[2] * .500000 + 128
|
||||
let v = rgb[0] * .500000 + rgb[1] * -.418688 + rgb[2] * -.081312 + 128
|
||||
|
||||
y = Math.floor(y);
|
||||
u = Math.floor(u);
|
||||
v = Math.floor(v);
|
||||
return [
|
||||
y, u, v
|
||||
]
|
||||
}
|
||||
|
||||
function createChannelParameters(channelParams, x, y) {
|
||||
return {
|
||||
channelOffsets: channelParams.channelOffsetsConstant.map(
|
||||
(cont, index) => cont + channelParams.channelOffsetsSize[index] *
|
||||
x * y),
|
||||
channelWidths: channelParams.channelWidths.map((width) => Math.floor(width * x)),
|
||||
channelPlaneWidths: channelParams.channelPlaneWidths.map((width) => Math.floor(width * x)),
|
||||
channelStrides: channelParams.channelStrides.map((width) => Math.floor(width * x)),
|
||||
channelSteps: channelParams.channelSteps.map((height) => height),
|
||||
channelHeights: channelParams.channelHeights.map((height) => Math.floor(height * y)),
|
||||
channelFourColors: channelParams.channelFourColors
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const scaleTests = [
|
||||
{ from: { w: 64, h: 64 }, to: { w: 128, h: 128 } }, // Factor 2
|
||||
{ from: { w: 128, h: 128 }, to: { w: 128, h: 128 } }, // Factor 1
|
||||
{ from: { w: 128, h: 128 }, to: { w: 64, h: 64 } }, // Factor 0.5
|
||||
{ from: { w: 32, h: 32 }, to: { w: 96, h: 96 } }, // Factor 3
|
||||
{ from: { w: 192, h: 192 }, to: { w: 64, h: 64 } }, // Factor 1/3
|
||||
{ from: { w: 64, h: 32 }, to: { w: 128, h: 64 } }, // Factor 2
|
||||
{ from: { w: 128, h: 256 }, to: { w: 64, h: 128 } }, // Factor 0.5
|
||||
{ from: { w: 64, h: 64 }, to: { w: 128, h: 192 } }, // Factor 2 (w) and 3 (h)
|
||||
{ from: { w: 128, h: 192 }, to: { w: 64, h: 64 } }, // Factor 0.5 (w) and 1/3 (h)
|
||||
]
|
||||
const fourColors = [[255, 255, 0], [255, 0, 0], [0, 255, 0], [0, 0, 255]];
|
||||
const pixelFormatChannelParameters = [
|
||||
{ // RGBX
|
||||
channelOffsetsConstant: [0, 1, 2],
|
||||
channelOffsetsSize: [0, 0, 0],
|
||||
channelPlaneWidths: [4, 0, 0], // only used for allocation
|
||||
channelWidths: [1, 1, 1],
|
||||
channelStrides: [4, 4, 4], // scaled by width
|
||||
channelSteps: [4, 4, 4],
|
||||
channelHeights: [1, 1, 1], // scaled by height
|
||||
channelFourColors: fourColors.map((col) => col), // just clone,
|
||||
format: 'RGBX'
|
||||
},
|
||||
{ // I420
|
||||
channelOffsetsConstant: [0, 0, 0],
|
||||
channelOffsetsSize: [0, 1, 1.25],
|
||||
channelPlaneWidths: [1, 0.5, 0.5],
|
||||
channelWidths: [1, 0.5, 0.5],
|
||||
channelStrides: [1, 0.5, 0.5], // scaled by width
|
||||
channelSteps: [1, 1, 1],
|
||||
channelHeights: [1, 0.5, 0.5], // scaled by height
|
||||
channelFourColors: fourColors.map((col) => rgb2yuv(col)), // just clone
|
||||
format: 'I420'
|
||||
}
|
||||
]
|
||||
|
||||
for (const scale of scaleTests) {
|
||||
for (const channelParams of pixelFormatChannelParameters) {
|
||||
promise_test(async t => {
|
||||
const inputChannelParameters = createChannelParameters(channelParams, scale.from.w, scale.from.h);
|
||||
const inputData = createImageData(inputChannelParameters);
|
||||
const inputFrame = new VideoFrame(inputData, {
|
||||
timestamp: 0,
|
||||
displayWidth: scale.from.w,
|
||||
displayHeight: scale.from.h,
|
||||
codedWidth: scale.from.w,
|
||||
codedHeight: scale.from.h,
|
||||
format: channelParams.format
|
||||
});
|
||||
const outputFrame = await scaleFrame(inputFrame, scale.to);
|
||||
const outputArrayBuffer = new Uint8Array(outputFrame.allocationSize({ format: 'RGBX' }));
|
||||
const layout = await outputFrame.copyTo(outputArrayBuffer, { format: 'RGBX' });
|
||||
const stride = layout[0].stride
|
||||
const offset = layout[0].offset
|
||||
|
||||
const error = testImageData(outputArrayBuffer, {
|
||||
channelOffsets: [offset, offset + 1, offset + 2],
|
||||
channelWidths: [outputFrame.codedWidth, outputFrame.codedWidth, outputFrame.codedWidth],
|
||||
channelStrides: [stride, stride, stride],
|
||||
channelSteps: [4, 4, 4],
|
||||
channelHeights: [outputFrame.codedHeight, outputFrame.codedHeight, outputFrame.codedHeight],
|
||||
channelFourColors: fourColors.map((col) => col)
|
||||
});
|
||||
outputFrame.close();
|
||||
assert_approx_equals(error, 0, 0.05, 'Scaled Image differs too much! Scaling from '
|
||||
+ scale.from.w + ' x ' + scale.from.h
|
||||
+ ' to '
|
||||
+ scale.to.w + ' x ' + scale.to.h
|
||||
+ ' Format:' +
|
||||
channelParams.format
|
||||
);
|
||||
}, 'Scaling Image in Encoding from '
|
||||
+ scale.from.w + ' x ' + scale.from.h
|
||||
+ ' to '
|
||||
+ scale.to.w + ' x ' + scale.to.h
|
||||
+ ' Format: ' +
|
||||
channelParams.format);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user