Commit b85903de authored by Eugene Zemtsov's avatar Eugene Zemtsov Committed by Commit Bot

[webcodecs] OpenH264-based video encoder in media

1. media::OpenH264VideoEncoder hidden behind the build flag
2. webcodecs/VideoEncoder using it when accelerated implementation
   is not available.
3. webtests exercising it

TBR=ssilken

Using TBR here, because neither Sergey nor Erik are apparently
Chromium committers and can't give an official +1.
(even though they're OpenH264 owners).

Sergey already left LGTM in the CL comments.

Bug: 1127073
Change-Id: Ida865f5fb8973999a75e15f488fc9df1d9506b31
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2425293
Commit-Queue: Eugene Zemtsov <eugene@chromium.org>
Reviewed-by: default avatarDan Sanders <sandersd@chromium.org>
Cr-Commit-Position: refs/heads/master@{#810876}
parent 2c8b578a
...@@ -37,6 +37,7 @@ buildflag_header("media_buildflags") { ...@@ -37,6 +37,7 @@ buildflag_header("media_buildflags") {
"ENABLE_MEDIA_DRM_STORAGE=$enable_media_drm_storage", "ENABLE_MEDIA_DRM_STORAGE=$enable_media_drm_storage",
"ENABLE_MEDIA_REMOTING=$enable_media_remoting", "ENABLE_MEDIA_REMOTING=$enable_media_remoting",
"ENABLE_MEDIA_REMOTING_RPC=$enable_media_remoting_rpc", "ENABLE_MEDIA_REMOTING_RPC=$enable_media_remoting_rpc",
"ENABLE_OPENH264=$media_use_openh264",
"ENABLE_PLATFORM_MPEG_H_AUDIO=$enable_platform_mpeg_h_audio", "ENABLE_PLATFORM_MPEG_H_AUDIO=$enable_platform_mpeg_h_audio",
"ENABLE_MSE_MPEG2TS_STREAM_PARSER=$enable_mse_mpeg2ts_stream_parser", "ENABLE_MSE_MPEG2TS_STREAM_PARSER=$enable_mse_mpeg2ts_stream_parser",
"USE_CHROMEOS_MEDIA_ACCELERATION=$use_vaapi||$use_v4l2_codec", "USE_CHROMEOS_MEDIA_ACCELERATION=$use_vaapi||$use_v4l2_codec",
......
...@@ -18,6 +18,7 @@ include_rules = [ ...@@ -18,6 +18,7 @@ include_rules = [
"+third_party/libgav1", "+third_party/libgav1",
"+third_party/libvpx", "+third_party/libvpx",
"+third_party/libyuv", "+third_party/libyuv",
"+third_party/openh264/src/codec/api/svc",
"+third_party/opus", "+third_party/opus",
"+third_party/skia", "+third_party/skia",
"+ui/base/ui_base_features.h", "+ui/base/ui_base_features.h",
......
...@@ -26,10 +26,10 @@ struct MEDIA_EXPORT VideoEncoderOutput { ...@@ -26,10 +26,10 @@ struct MEDIA_EXPORT VideoEncoderOutput {
// Feel free take this buffer out and use underlying memory as is without // Feel free take this buffer out and use underlying memory as is without
// copying. // copying.
std::unique_ptr<uint8_t[]> data; std::unique_ptr<uint8_t[]> data;
size_t size; size_t size = 0;
base::TimeDelta timestamp; base::TimeDelta timestamp;
bool key_frame; bool key_frame = false;
}; };
class MEDIA_EXPORT VideoEncoder { class MEDIA_EXPORT VideoEncoder {
......
...@@ -58,6 +58,13 @@ declare_args() { ...@@ -58,6 +58,13 @@ declare_args() {
media_use_libvpx = false media_use_libvpx = false
} }
# Enable usage of libvpx within the media library. Used for software based
# encoding of H264 content.
media_use_openh264 = true
if (is_ios || is_android || !proprietary_codecs) {
media_use_openh264 = false
}
# Override to dynamically link the cras (ChromeOS audio) library. # Override to dynamically link the cras (ChromeOS audio) library.
use_cras = is_chromeos_device use_cras = is_chromeos_device
......
...@@ -76,6 +76,14 @@ source_set("video") { ...@@ -76,6 +76,14 @@ source_set("video") {
] ]
deps += [ "//third_party/libvpx" ] deps += [ "//third_party/libvpx" ]
} }
if (proprietary_codecs && media_use_openh264) {
sources += [
"openh264_video_encoder.cc",
"openh264_video_encoder.h",
]
deps += [ "//third_party/openh264:encoder" ]
}
} }
# Note: This is a roll-up only target; do not expand the visibility. DEPS should # Note: This is a roll-up only target; do not expand the visibility. DEPS should
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/video/openh264_video_encoder.h"
#include <algorithm>
#include <limits>
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/system/sys_info.h"
#include "base/time/time.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/video_frame.h"
namespace media {
namespace {
Status SetUpOpenH264Params(const VideoEncoder::Options& options,
SEncParamExt* params) {
params->bEnableFrameSkip = false;
params->iPaddingFlag = 0;
params->iComplexityMode = MEDIUM_COMPLEXITY;
params->iUsageType = CAMERA_VIDEO_REAL_TIME;
params->bEnableDenoise = false;
// Set to 1 due to https://crbug.com/583348
params->iMultipleThreadIdc = 1;
params->fMaxFrameRate = options.framerate;
params->iPicHeight = options.height;
params->iPicWidth = options.width;
if (options.keyframe_interval.has_value())
params->uiIntraPeriod = options.keyframe_interval.value();
if (options.bitrate.has_value()) {
params->iRCMode = RC_BITRATE_MODE;
params->iTargetBitrate = int{std::min(
options.bitrate.value(), uint64_t{std::numeric_limits<int>::max()})};
} else {
params->iRCMode = RC_OFF_MODE;
}
params->iSpatialLayerNum = 1;
params->sSpatialLayers[0].fFrameRate = params->fMaxFrameRate;
params->sSpatialLayers[0].iMaxSpatialBitrate = params->iTargetBitrate;
params->sSpatialLayers[0].iSpatialBitrate = params->iTargetBitrate;
params->sSpatialLayers[0].iVideoHeight = params->iPicHeight;
params->sSpatialLayers[0].iVideoWidth = params->iPicWidth;
params->sSpatialLayers[0].sSliceArgument.uiSliceMode = SM_SINGLE_SLICE;
return Status();
}
} // namespace
OpenH264VideoEncoder::ISVCEncoderDeleter::ISVCEncoderDeleter() = default;
OpenH264VideoEncoder::ISVCEncoderDeleter::ISVCEncoderDeleter(
const ISVCEncoderDeleter&) = default;
void OpenH264VideoEncoder::ISVCEncoderDeleter::operator()(ISVCEncoder* codec) {
if (codec) {
if (initialized_) {
auto result = codec->Uninitialize();
DCHECK_EQ(cmResultSuccess, result);
}
WelsDestroySVCEncoder(codec);
}
}
void OpenH264VideoEncoder::ISVCEncoderDeleter::MarkInitialized() {
initialized_ = true;
}
OpenH264VideoEncoder::OpenH264VideoEncoder() : codec_() {}
OpenH264VideoEncoder::~OpenH264VideoEncoder() = default;
void OpenH264VideoEncoder::Initialize(VideoCodecProfile profile,
const Options& options,
OutputCB output_cb,
StatusCB done_cb) {
done_cb = media::BindToCurrentLoop(std::move(done_cb));
if (codec_) {
std::move(done_cb).Run(StatusCode::kEncoderInitializeTwice);
return;
}
profile_ = profile;
ISVCEncoder* raw_codec = nullptr;
if (WelsCreateSVCEncoder(&raw_codec) != 0) {
auto status = Status(StatusCode::kEncoderInitializationError,
"Failed to create OpenH264 encoder.");
std::move(done_cb).Run(status);
return;
}
svc_encoder_unique_ptr codec(raw_codec, ISVCEncoderDeleter());
raw_codec = nullptr;
Status status;
SEncParamExt params = {};
if (int err = codec->GetDefaultParams(&params)) {
status = Status(StatusCode::kEncoderInitializationError,
"Failed to get default params.")
.WithData("error", err);
std::move(done_cb).Run(status);
return;
}
status = SetUpOpenH264Params(options, &params);
if (!status.is_ok()) {
std::move(done_cb).Run(status);
return;
}
if (int err = codec->InitializeExt(&params)) {
status = Status(StatusCode::kEncoderInitializationError,
"Failed to initialize OpenH264 encoder.")
.WithData("error", err);
std::move(done_cb).Run(status);
return;
}
codec.get_deleter().MarkInitialized();
int video_format = EVideoFormatType::videoFormatI420;
if (int err = codec->SetOption(ENCODER_OPTION_DATAFORMAT, &video_format)) {
status = Status(StatusCode::kEncoderInitializationError,
"Failed to set data format for OpenH264 encoder")
.WithData("error", err);
std::move(done_cb).Run(status);
return;
}
options_ = options;
output_cb_ = media::BindToCurrentLoop(std::move(output_cb));
codec_ = std::move(codec);
std::move(done_cb).Run(Status());
}
void OpenH264VideoEncoder::Encode(scoped_refptr<VideoFrame> frame,
bool key_frame,
StatusCB done_cb) {
Status status;
done_cb = media::BindToCurrentLoop(std::move(done_cb));
if (!codec_) {
std::move(done_cb).Run(StatusCode::kEncoderInitializeNeverCompleted);
return;
}
if (!frame) {
std::move(done_cb).Run(Status(StatusCode::kEncoderFailedEncode,
"No frame provided for encoding."));
return;
}
if (!frame->IsMappable() || frame->format() != media::PIXEL_FORMAT_I420) {
status =
Status(StatusCode::kEncoderFailedEncode, "Unexpected frame format.")
.WithData("IsMappable", frame->IsMappable())
.WithData("format", frame->format());
std::move(done_cb).Run(std::move(status));
return;
}
SSourcePicture picture = {};
picture.iPicWidth = frame->visible_rect().width();
picture.iPicHeight = frame->visible_rect().height();
picture.iColorFormat = EVideoFormatType::videoFormatI420;
picture.uiTimeStamp = frame->timestamp().InMilliseconds();
picture.pData[0] = frame->visible_data(VideoFrame::kYPlane);
picture.pData[1] = frame->visible_data(VideoFrame::kUPlane);
picture.pData[2] = frame->visible_data(VideoFrame::kVPlane);
picture.iStride[0] = frame->stride(VideoFrame::kYPlane);
picture.iStride[1] = frame->stride(VideoFrame::kUPlane);
picture.iStride[2] = frame->stride(VideoFrame::kVPlane);
if (key_frame) {
if (int err = codec_->ForceIntraFrame(true)) {
std::move(done_cb).Run(
Status(StatusCode::kEncoderFailedEncode, "Can't make keyframe.")
.WithData("error", err));
return;
}
}
SFrameBSInfo frame_info = {};
if (int err = codec_->EncodeFrame(&picture, &frame_info)) {
std::move(done_cb).Run(Status(StatusCode::kEncoderFailedEncode,
"Failed to encode using OpenH264.")
.WithData("error", err));
return;
}
VideoEncoderOutput result;
result.key_frame = (frame_info.eFrameType == videoFrameTypeIDR);
result.timestamp = frame->timestamp();
DCHECK_GT(frame_info.iFrameSizeInBytes, 0);
size_t total_chunk_size = frame_info.iFrameSizeInBytes;
conversion_buffer_.resize(total_chunk_size);
size_t written_size = 0;
for (int layer_idx = 0; layer_idx < frame_info.iLayerNum; ++layer_idx) {
SLayerBSInfo& layer_info = frame_info.sLayerInfo[layer_idx];
size_t layer_len = 0;
for (int nal_idx = 0; nal_idx < layer_info.iNalCount; ++nal_idx)
layer_len += layer_info.pNalLengthInByte[nal_idx];
if (written_size + layer_len > total_chunk_size) {
std::move(done_cb).Run(Status(StatusCode::kEncoderFailedEncode,
"Inconsistent size of the encoded frame."));
return;
}
memcpy(conversion_buffer_.data() + written_size, layer_info.pBsBuf,
layer_len);
written_size += layer_len;
}
DCHECK_EQ(written_size, total_chunk_size);
size_t converted_output_size = 0;
bool config_changed = false;
result.data.reset(new uint8_t[total_chunk_size]);
status = h264_converter_.ConvertChunk(
conversion_buffer_,
base::span<uint8_t>(result.data.get(), total_chunk_size), &config_changed,
&converted_output_size);
if (!status.is_ok()) {
std::move(done_cb).Run(std::move(status).AddHere(FROM_HERE));
return;
}
result.size = converted_output_size;
base::Optional<CodecDescription> desc;
if (config_changed) {
const auto& config = h264_converter_.GetCurrentConfig();
desc = CodecDescription();
if (!config.Serialize(desc.value())) {
std::move(done_cb).Run(Status(StatusCode::kEncoderFailedEncode,
"Failed to serialize AVC decoder config"));
return;
}
}
output_cb_.Run(std::move(result), std::move(desc));
std::move(done_cb).Run(Status());
}
void OpenH264VideoEncoder::ChangeOptions(const Options& options,
StatusCB done_cb) {
done_cb = media::BindToCurrentLoop(std::move(done_cb));
if (!codec_) {
std::move(done_cb).Run(StatusCode::kEncoderInitializeNeverCompleted);
return;
}
// TODO(eugene): Not implemented yet.
std::move(done_cb).Run(Status());
}
void OpenH264VideoEncoder::Flush(StatusCB done_cb) {
done_cb = media::BindToCurrentLoop(std::move(done_cb));
if (!codec_) {
std::move(done_cb).Run(StatusCode::kEncoderInitializeNeverCompleted);
return;
}
// Nothing to do really.
std::move(done_cb).Run(Status());
}
} // namespace media
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MEDIA_VIDEO_OPENH264_VIDEO_ENCODER_H_
#define MEDIA_VIDEO_OPENH264_VIDEO_ENCODER_H_
#include <memory>
#include <vector>
#include "base/callback_forward.h"
#include "media/base/media_export.h"
#include "media/base/video_encoder.h"
#include "media/formats/mp4/h264_annex_b_to_avc_bitstream_converter.h"
#include "third_party/openh264/src/codec/api/svc/codec_api.h"
#include "ui/gfx/geometry/size.h"
namespace media {
class MEDIA_EXPORT OpenH264VideoEncoder : public VideoEncoder {
public:
OpenH264VideoEncoder();
~OpenH264VideoEncoder() override;
// VideoDecoder implementation.
void Initialize(VideoCodecProfile profile,
const Options& options,
OutputCB output_cb,
StatusCB done_cb) override;
void Encode(scoped_refptr<VideoFrame> frame,
bool key_frame,
StatusCB done_cb) override;
void ChangeOptions(const Options& options, StatusCB done_cb) override;
void Flush(StatusCB done_cb) override;
private:
void DrainOutputs();
class ISVCEncoderDeleter {
public:
ISVCEncoderDeleter();
ISVCEncoderDeleter(const ISVCEncoderDeleter&);
void operator()(ISVCEncoder* coder);
void MarkInitialized();
private:
bool initialized_ = false;
};
using svc_encoder_unique_ptr =
std::unique_ptr<ISVCEncoder, ISVCEncoderDeleter>;
svc_encoder_unique_ptr codec_;
VideoCodecProfile profile_ = VIDEO_CODEC_PROFILE_UNKNOWN;
Options options_;
OutputCB output_cb_;
std::vector<uint8_t> conversion_buffer_;
H264AnnexBToAvcBitstreamConverter h264_converter_;
};
} // namespace media
#endif // MEDIA_VIDEO_OPENH264_VIDEO_ENCODER_H_
\ No newline at end of file
...@@ -10,10 +10,14 @@ ...@@ -10,10 +10,14 @@
#include "base/callback.h" #include "base/callback.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/macros.h" #include "base/macros.h"
#include "build/build_config.h"
#include "media/base/mime_util.h" #include "media/base/mime_util.h"
#include "media/base/video_codecs.h" #include "media/base/video_codecs.h"
#include "media/base/video_color_space.h" #include "media/base/video_color_space.h"
#include "media/base/video_encoder.h" #include "media/base/video_encoder.h"
#if BUILDFLAG(ENABLE_OPENH264)
#include "media/video/openh264_video_encoder.h"
#endif
#if BUILDFLAG(ENABLE_LIBVPX) #if BUILDFLAG(ENABLE_LIBVPX)
#include "media/video/vpx_video_encoder.h" #include "media/video/vpx_video_encoder.h"
#endif #endif
...@@ -47,6 +51,11 @@ namespace blink { ...@@ -47,6 +51,11 @@ namespace blink {
namespace { namespace {
std::unique_ptr<media::VideoEncoder> CreateAcceleratedVideoEncoder() { std::unique_ptr<media::VideoEncoder> CreateAcceleratedVideoEncoder() {
#if defined(OS_MAC) || defined(OS_LINUX)
// TODO(https://crbug.com/1110279) Flush() is not implemented on MacOS'
// accelerated video encoder, so we can't use it yet.
return nullptr;
#else
auto* gpu_factories = Platform::Current()->GetGpuFactories(); auto* gpu_factories = Platform::Current()->GetGpuFactories();
if (!gpu_factories || !gpu_factories->IsGpuVideoAcceleratorEnabled()) if (!gpu_factories || !gpu_factories->IsGpuVideoAcceleratorEnabled())
return nullptr; return nullptr;
...@@ -56,6 +65,7 @@ std::unique_ptr<media::VideoEncoder> CreateAcceleratedVideoEncoder() { ...@@ -56,6 +65,7 @@ std::unique_ptr<media::VideoEncoder> CreateAcceleratedVideoEncoder() {
media::AsyncDestroyVideoEncoder<media::VideoEncodeAcceleratorAdapter>>( media::AsyncDestroyVideoEncoder<media::VideoEncodeAcceleratorAdapter>>(
std::make_unique<media::VideoEncodeAcceleratorAdapter>( std::make_unique<media::VideoEncodeAcceleratorAdapter>(
gpu_factories, std::move(task_runner))); gpu_factories, std::move(task_runner)));
#endif // defined(OS_MAC) || defined(OS_LINUX)
} }
std::unique_ptr<media::VideoEncoder> CreateVpxVideoEncoder() { std::unique_ptr<media::VideoEncoder> CreateVpxVideoEncoder() {
...@@ -66,6 +76,14 @@ std::unique_ptr<media::VideoEncoder> CreateVpxVideoEncoder() { ...@@ -66,6 +76,14 @@ std::unique_ptr<media::VideoEncoder> CreateVpxVideoEncoder() {
#endif // BUILDFLAG(ENABLE_LIBVPX) #endif // BUILDFLAG(ENABLE_LIBVPX)
} }
std::unique_ptr<media::VideoEncoder> CreateOpenH264VideoEncoder() {
#if BUILDFLAG(ENABLE_OPENH264)
return std::make_unique<media::OpenH264VideoEncoder>();
#else
return nullptr;
#endif // BUILDFLAG(ENABLE_OPENH264)
}
scoped_refptr<media::VideoFrame> ConvertToI420Frame( scoped_refptr<media::VideoFrame> ConvertToI420Frame(
scoped_refptr<media::VideoFrame> frame) { scoped_refptr<media::VideoFrame> frame) {
DCHECK_EQ(frame->storage_type(), DCHECK_EQ(frame->storage_type(),
...@@ -220,11 +238,6 @@ bool VideoEncoder::VerifyCodecSupport(ParsedConfig* config, ...@@ -220,11 +238,6 @@ bool VideoEncoder::VerifyCodecSupport(ParsedConfig* config,
break; break;
case media::kCodecH264: case media::kCodecH264:
if (config->acc_pref == AccelerationPreference::kDeny) {
exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
"Software h264 is not supported yet");
return false;
}
break; break;
default: default:
...@@ -236,6 +249,49 @@ bool VideoEncoder::VerifyCodecSupport(ParsedConfig* config, ...@@ -236,6 +249,49 @@ bool VideoEncoder::VerifyCodecSupport(ParsedConfig* config,
return true; return true;
} }
std::unique_ptr<media::VideoEncoder> VideoEncoder::CreateMediaVideoEncoder(
const ParsedConfig& config) {
// TODO(https://crbug.com/1119636): Implement / call a proper method for
// detecting support of encoder configs.
switch (config.acc_pref) {
case AccelerationPreference::kRequire:
return CreateAcceleratedVideoEncoder();
case AccelerationPreference::kAllow: {
switch (config.codec) {
case media::kCodecVP8:
case media::kCodecVP9:
/* No acceleration supported yet. */
return CreateVpxVideoEncoder();
case media::kCodecH264:
if (auto result = CreateAcceleratedVideoEncoder())
return result;
return CreateOpenH264VideoEncoder();
default:
return nullptr;
}
}
case AccelerationPreference::kDeny: {
switch (config.codec) {
case media::kCodecVP8:
case media::kCodecVP9:
return CreateVpxVideoEncoder();
case media::kCodecH264:
return CreateOpenH264VideoEncoder();
default:
return nullptr;
}
}
default:
NOTREACHED();
return nullptr;
}
}
void VideoEncoder::configure(const VideoEncoderConfig* config, void VideoEncoder::configure(const VideoEncoderConfig* config,
ExceptionState& exception_state) { ExceptionState& exception_state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
...@@ -462,26 +518,11 @@ void VideoEncoder::ProcessConfigure(Request* request) { ...@@ -462,26 +518,11 @@ void VideoEncoder::ProcessConfigure(Request* request) {
DCHECK(active_config_); DCHECK(active_config_);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
switch (active_config_->codec) { media_encoder_ = CreateMediaVideoEncoder(*active_config_);
case media::kCodecVP8:
case media::kCodecVP9:
media_encoder_ = CreateVpxVideoEncoder();
break;
case media::kCodecH264:
media_encoder_ = CreateAcceleratedVideoEncoder();
break;
default:
// This should already have been caught in ParseConfig() and
// VerifyCodecSupport().
NOTREACHED();
break;
}
if (!media_encoder_) { if (!media_encoder_) {
// CreateAcceleratedVideoEncoder() can return a nullptr. HandleError(DOMExceptionCode::kOperationError,
HandleError(DOMExceptionCode::kOperationError, "Encoder creation error."); "Encoder creation error. Most likely unsupported "
"codec/acceleration requirement combination.");
return; return;
} }
......
...@@ -111,6 +111,8 @@ class MODULES_EXPORT VideoEncoder final : public ScriptWrappable { ...@@ -111,6 +111,8 @@ class MODULES_EXPORT VideoEncoder final : public ScriptWrappable {
std::unique_ptr<ParsedConfig> ParseConfig(const VideoEncoderConfig*, std::unique_ptr<ParsedConfig> ParseConfig(const VideoEncoderConfig*,
ExceptionState&); ExceptionState&);
bool VerifyCodecSupport(ParsedConfig*, ExceptionState&); bool VerifyCodecSupport(ParsedConfig*, ExceptionState&);
std::unique_ptr<media::VideoEncoder> CreateMediaVideoEncoder(
const ParsedConfig& config);
std::unique_ptr<ParsedConfig> active_config_; std::unique_ptr<ParsedConfig> active_config_;
std::unique_ptr<media::VideoEncoder> media_encoder_; std::unique_ptr<media::VideoEncoder> media_encoder_;
......
...@@ -12,8 +12,17 @@ ...@@ -12,8 +12,17 @@
'use strict'; 'use strict';
async function waitTillLoaded(img) {
if (img.complete)
return Promise.resolve();
return new Promise((resolve, reject) => {
img.onload = () => resolve()
});
}
async function generateBitmap(width, height, text) { async function generateBitmap(width, height, text) {
let img = document.getElementById('frame_image'); let img = document.getElementById('frame_image');
await waitTillLoaded(img);
let cnv = document.createElement("canvas"); let cnv = document.createElement("canvas");
cnv.height = height; cnv.height = height;
cnv.width = width; cnv.width = width;
...@@ -35,13 +44,14 @@ ...@@ -35,13 +44,14 @@
}); });
}; };
async function encode_decode_test(codec) { async function encode_decode_test(codec, acc) {
const w = 120; const w = 120;
const h = 60; const h = 60;
let next_ts = 0 let next_ts = 0
let frames_to_encode = 16; let frames_to_encode = 16;
let frames_encoded = 0; let frames_encoded = 0;
let frames_decoded = 0; let frames_decoded = 0;
let errors = 0;
let decoder = new VideoDecoder({ let decoder = new VideoDecoder({
output(frame) { output(frame) {
...@@ -51,25 +61,33 @@ ...@@ -51,25 +61,33 @@
frames_decoded++; frames_decoded++;
frame.destroy(); frame.destroy();
}, },
error(e) { console.log(e.message); } error(e) {
errors++;
console.log(e.message);
}
}); });
const encoder_init = { const encoder_init = {
output(chunk, config) { output(chunk, config) {
frames_encoded++; var data = new Uint8Array(chunk.data);
if (decoder.state != "configured") if (decoder.state != "configured" || config.description) {
decoder.configure(config); decoder.configure(config);
}
decoder.decode(chunk); decoder.decode(chunk);
frames_encoded++;
}, },
error(e) { console.log(e.message); } error(e) {
errors++;
console.log(e.message);
}
}; };
const encoder_config = { const encoder_config = {
codec: codec, codec: codec,
acceleration: "allow", acceleration: acc,
width: w, width: w,
height: h, height: h,
bitrate: 10e6, bitrate: 5000000,
framerate: 30, framerate: 30,
}; };
...@@ -88,6 +106,7 @@ ...@@ -88,6 +106,7 @@
decoder.close(); decoder.close();
assert_equals(frames_encoded, frames_to_encode); assert_equals(frames_encoded, frames_to_encode);
assert_equals(frames_decoded, frames_to_encode); assert_equals(frames_decoded, frames_to_encode);
assert_equals(errors, 0);
} }
async function encode_test(codec, acc) { async function encode_test(codec, acc) {
...@@ -96,32 +115,36 @@ ...@@ -96,32 +115,36 @@
let next_ts = 0 let next_ts = 0
let frames_to_encode = 16; let frames_to_encode = 16;
let frames_processed = 0; let frames_processed = 0;
let errors = 0;
let process_video_chunk = function (chunk, config) { let process_video_chunk = function (chunk, config) {
assert_greater_than_equal(chunk.timestamp, next_ts++); assert_greater_than_equal(chunk.timestamp, next_ts++);
var data = new Uint8Array(chunk.data);
let type = (chunk.timestamp % 5 == 0) ? "key" : "delta"; let type = (chunk.timestamp % 5 == 0) ? "key" : "delta";
assert_equals(chunk.type, type); assert_equals(chunk.type, type);
var data = new Uint8Array(chunk.data);
assert_greater_than_equal(data.length, 0); assert_greater_than_equal(data.length, 0);
if (config) { if (config) {
assert_equals(config.codec, codec); assert_equals(config.codec, codec);
assert_equals(config.codedWidth, w); assert_equals(config.codedWidth, w);
assert_equals(config.codedHeight, h); assert_equals(config.codedHeight, h);
let data = new Uint8Array(config.description); let data = new Uint8Array(config.description);
console.log("extra data len: " + data.length);
} }
frames_processed++; frames_processed++;
}; };
const init = { const init = {
output: process_video_chunk, output: process_video_chunk,
error: (e) => { console.log(e.message); }, error: (e) => {
error++;
console.log(e.message);
},
}; };
const params = { const params = {
codec: codec, codec: codec,
acceleration: acc, acceleration: acc,
width: w, width: w,
height: h, height: h,
bitrate: 10e6, bitrate: 5000000,
framerate: 30, framerate: 30,
}; };
let encoder = new VideoEncoder(init); let encoder = new VideoEncoder(init);
...@@ -135,29 +158,35 @@ ...@@ -135,29 +158,35 @@
await encoder.flush(); await encoder.flush();
encoder.close(); encoder.close();
assert_equals(frames_processed, frames_to_encode); assert_equals(frames_processed, frames_to_encode);
assert_equals(errors, 0);
} }
promise_test(encode_test.bind(null, "vp09.00.10.08", "allow"), promise_test(encode_test.bind(null, "vp09.00.10.08", "allow"),
"encoding vp9 profile0"); "encoding vp9 profile0");
promise_test(encode_test.bind(null, "vp09.02.10.10", "allow"), promise_test(encode_test.bind(null, "vp09.02.10.10", "allow"),
"encoding vp9 profile2"); "encoding vp9 profile2");
promise_test(encode_decode_test.bind(null, "vp09.02.10.10", "allow"),
"encoding and decoding vp9 profile2");
promise_test(encode_test.bind(null, "vp8", "allow"), promise_test(encode_test.bind(null, "vp8", "allow"),
"encoding vp8"); "encoding vp8");
promise_test(encode_decode_test.bind(null, "vp8"), promise_test(encode_decode_test.bind(null, "vp8", "allow"),
"encoding and decoding vp8"); "encoding and decoding vp8");
promise_test(encode_decode_test.bind(null, "vp09.02.10.10"), promise_test(encode_test.bind(null, "avc1.42001E", "allow"),
"encoding and decoding vp09.02.10.10"); "encoding avc1.42001E");
promise_test(encode_decode_test.bind(null, "avc1.42001E", "allow"),
"encoding and decoding avc1.42001E");
/* Uncomment this for manual testing, before we have GPU tests for that */ /* Uncomment this for manual testing, before we have GPU tests for that */
// promise_test(encode_test.bind(null, "avc1.42001E", "require"), // promise_test(encode_test.bind(null, "avc1.42001E", "require"),
// "encoding avc1.42001E"); // "encoding avc1.42001E");
// promise_test(encode_decode_test.bind(null, "avc1.42001E"), // promise_test(encode_decode_test.bind(null, "avc1.42001E", "require"),
// "encoding and decoding avc1.42001E"); // "encoding and decoding avc1.42001E");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment