Commit eec72499 authored by Dan Sanders's avatar Dan Sanders Committed by Commit Bot

Reland "[webcodecs] Implement Audio/VideoDecoder error callback."

This reverts commit 51b274e8.

Bug: 1045247
Change-Id: I4f22c1e340d6c2224eaccb29e41b2605efe0d47a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2368493
Commit-Queue: Dan Sanders <sandersd@chromium.org>
Commit-Queue: Chrome Cunningham <chcunningham@chromium.org>
Auto-Submit: Dan Sanders <sandersd@chromium.org>
Reviewed-by: default avatarChrome Cunningham <chcunningham@chromium.org>
Cr-Commit-Position: refs/heads/master@{#800406}
parent de4b404f
...@@ -58,7 +58,6 @@ source_set("unit_tests") { ...@@ -58,7 +58,6 @@ source_set("unit_tests") {
testonly = true testonly = true
sources = [ sources = [
"audio_decoder_broker_test.cc", "audio_decoder_broker_test.cc",
"audio_decoder_test.cc",
"decoder_selector_test.cc", "decoder_selector_test.cc",
"encoded_video_chunk_test.cc", "encoded_video_chunk_test.cc",
"image_decoder_external_test.cc", "image_decoder_external_test.cc",
......
...@@ -56,5 +56,5 @@ ...@@ -56,5 +56,5 @@
// decode requests are aborted. // decode requests are aborted.
// //
// Not recoverable: make a new AudioDecoder if needed. // Not recoverable: make a new AudioDecoder if needed.
void close(); [RaisesException] void close();
}; };
// 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 "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_audio_decoder_init.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/modules/webcodecs/audio_decoder.h"
namespace blink {
TEST(AudioDecoderTest, Construction) {
V8TestingScope v8_scope;
auto* init = MakeGarbageCollected<AudioDecoderInit>();
auto* decoder = MakeGarbageCollected<AudioDecoder>(
v8_scope.GetScriptState(), init, v8_scope.GetExceptionState());
EXPECT_EQ(decoder->decodeQueueSize(), 0);
}
} // namespace blink
...@@ -43,10 +43,10 @@ DecoderTemplate<Traits>::DecoderTemplate(ScriptState* script_state, ...@@ -43,10 +43,10 @@ DecoderTemplate<Traits>::DecoderTemplate(ScriptState* script_state,
ExceptionState& exception_state) ExceptionState& exception_state)
: script_state_(script_state) { : script_state_(script_state) {
DVLOG(1) << __func__; DVLOG(1) << __func__;
// TODO(crbug.com/1070871): Use fooOr(nullptr). DCHECK(init->hasOutput());
// TODO(sandersd): Is it an error to not provide all callbacks? DCHECK(init->hasError());
output_cb_ = init->hasOutput() ? init->output() : nullptr; output_cb_ = init->output();
error_cb_ = init->hasError() ? init->error() : nullptr; error_cb_ = init->error();
} }
template <typename Traits> template <typename Traits>
...@@ -63,6 +63,11 @@ template <typename Traits> ...@@ -63,6 +63,11 @@ template <typename Traits>
void DecoderTemplate<Traits>::configure(const ConfigType* config, void DecoderTemplate<Traits>::configure(const ConfigType* config,
ExceptionState& exception_state) { ExceptionState& exception_state) {
DVLOG(1) << __func__; DVLOG(1) << __func__;
if (is_closed_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"Cannot configure a closed codec.");
return;
}
auto media_config = std::make_unique<MediaConfigType>(); auto media_config = std::make_unique<MediaConfigType>();
String console_message; String console_message;
...@@ -90,8 +95,15 @@ void DecoderTemplate<Traits>::configure(const ConfigType* config, ...@@ -90,8 +95,15 @@ void DecoderTemplate<Traits>::configure(const ConfigType* config,
} }
template <typename Traits> template <typename Traits>
void DecoderTemplate<Traits>::decode(const InputType* chunk, ExceptionState&) { void DecoderTemplate<Traits>::decode(const InputType* chunk,
ExceptionState& exception_state) {
DVLOG(3) << __func__; DVLOG(3) << __func__;
if (is_closed_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"Cannot decode with a closed codec.");
return;
}
Request* request = MakeGarbageCollected<Request>(); Request* request = MakeGarbageCollected<Request>();
request->type = Request::Type::kDecode; request->type = Request::Type::kDecode;
request->chunk = chunk; request->chunk = chunk;
...@@ -101,8 +113,14 @@ void DecoderTemplate<Traits>::decode(const InputType* chunk, ExceptionState&) { ...@@ -101,8 +113,14 @@ void DecoderTemplate<Traits>::decode(const InputType* chunk, ExceptionState&) {
} }
template <typename Traits> template <typename Traits>
ScriptPromise DecoderTemplate<Traits>::flush(ExceptionState&) { ScriptPromise DecoderTemplate<Traits>::flush(ExceptionState& exception_state) {
DVLOG(3) << __func__; DVLOG(3) << __func__;
if (is_closed_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"Cannot flush a closed codec.");
return ScriptPromise();
}
Request* request = MakeGarbageCollected<Request>(); Request* request = MakeGarbageCollected<Request>();
request->type = Request::Type::kFlush; request->type = Request::Type::kFlush;
ScriptPromiseResolver* resolver = ScriptPromiseResolver* resolver =
...@@ -114,8 +132,14 @@ ScriptPromise DecoderTemplate<Traits>::flush(ExceptionState&) { ...@@ -114,8 +132,14 @@ ScriptPromise DecoderTemplate<Traits>::flush(ExceptionState&) {
} }
template <typename Traits> template <typename Traits>
void DecoderTemplate<Traits>::reset(ExceptionState&) { void DecoderTemplate<Traits>::reset(ExceptionState& exception_state) {
DVLOG(3) << __func__; DVLOG(3) << __func__;
if (is_closed_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"Cannot reset a closed codec.");
return;
}
Request* request = MakeGarbageCollected<Request>(); Request* request = MakeGarbageCollected<Request>();
request->type = Request::Type::kReset; request->type = Request::Type::kReset;
requests_.push_back(request); requests_.push_back(request);
...@@ -124,14 +148,20 @@ void DecoderTemplate<Traits>::reset(ExceptionState&) { ...@@ -124,14 +148,20 @@ void DecoderTemplate<Traits>::reset(ExceptionState&) {
} }
template <typename Traits> template <typename Traits>
void DecoderTemplate<Traits>::close() { void DecoderTemplate<Traits>::close(ExceptionState& exception_state) {
// TODO(chcunningham): Implement. DVLOG(3) << __func__;
if (is_closed_) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"Codec is already closed.");
return;
}
Shutdown(false);
} }
template <typename Traits> template <typename Traits>
void DecoderTemplate<Traits>::ProcessRequests() { void DecoderTemplate<Traits>::ProcessRequests() {
DVLOG(3) << __func__; DVLOG(3) << __func__;
// TODO(sandersd): Re-entrancy checker. DCHECK(!is_closed_);
while (!pending_request_ && !requests_.IsEmpty()) { while (!pending_request_ && !requests_.IsEmpty()) {
Request* request = requests_.front(); Request* request = requests_.front();
switch (request->type) { switch (request->type) {
...@@ -159,36 +189,34 @@ void DecoderTemplate<Traits>::ProcessRequests() { ...@@ -159,36 +189,34 @@ void DecoderTemplate<Traits>::ProcessRequests() {
template <typename Traits> template <typename Traits>
bool DecoderTemplate<Traits>::ProcessConfigureRequest(Request* request) { bool DecoderTemplate<Traits>::ProcessConfigureRequest(Request* request) {
DVLOG(3) << __func__; DVLOG(3) << __func__;
DCHECK(!is_closed_);
DCHECK(!pending_request_); DCHECK(!pending_request_);
DCHECK_EQ(request->type, Request::Type::kConfigure); DCHECK_EQ(request->type, Request::Type::kConfigure);
DCHECK(request->media_config); DCHECK(request->media_config);
// TODO(sandersd): If we require configure() after reset() and there is a // TODO(sandersd): Record this configuration as pending but don't apply it
// pending reset, then we could drop this request. // until there is a decode request.
// TODO(sandersd): If the next request is also a configure(), they can be
// merged. It's not trivial to detect that situation.
// TODO(sandersd): Elide this request if the configuration is unchanged.
if (!decoder_) { if (!decoder_) {
media_log_ = std::make_unique<media::NullMediaLog>(); media_log_ = std::make_unique<media::NullMediaLog>();
decoder_ = Traits::CreateDecoder(*ExecutionContext::From(script_state_), decoder_ = Traits::CreateDecoder(*ExecutionContext::From(script_state_),
media_log_.get()); media_log_.get());
if (!decoder_) { if (!decoder_) {
// TODO(sandersd): This is a bit awkward because |request| is still in the
// queue.
HandleError(); HandleError();
return false; return false;
} }
// Processing continues in OnInitializeDone(). // Processing continues in OnInitializeDone().
// TODO(sandersd): OnInitializeDone() may be called reentrantly, in which // Note: OnInitializeDone() must not call ProcessRequests() reentrantly,
// case it must not call ProcessRequests(). // which can happen if InitializeDecoder() calls it synchronously.
pending_request_ = request; pending_request_ = request;
initializing_sync_ = true;
Traits::InitializeDecoder( Traits::InitializeDecoder(
*decoder_, *pending_request_->media_config, *decoder_, *pending_request_->media_config,
WTF::Bind(&DecoderTemplate::OnInitializeDone, WrapWeakPersistent(this)), WTF::Bind(&DecoderTemplate::OnInitializeDone, WrapWeakPersistent(this)),
WTF::BindRepeating(&DecoderTemplate::OnOutput, WTF::BindRepeating(&DecoderTemplate::OnOutput,
WrapWeakPersistent(this))); WrapWeakPersistent(this)));
initializing_sync_ = false;
return true; return true;
} }
...@@ -213,6 +241,7 @@ bool DecoderTemplate<Traits>::ProcessConfigureRequest(Request* request) { ...@@ -213,6 +241,7 @@ bool DecoderTemplate<Traits>::ProcessConfigureRequest(Request* request) {
template <typename Traits> template <typename Traits>
bool DecoderTemplate<Traits>::ProcessDecodeRequest(Request* request) { bool DecoderTemplate<Traits>::ProcessDecodeRequest(Request* request) {
DVLOG(3) << __func__; DVLOG(3) << __func__;
DCHECK(!is_closed_);
DCHECK(!pending_request_); DCHECK(!pending_request_);
DCHECK_EQ(request->type, Request::Type::kDecode); DCHECK_EQ(request->type, Request::Type::kDecode);
DCHECK(request->chunk); DCHECK(request->chunk);
...@@ -221,8 +250,8 @@ bool DecoderTemplate<Traits>::ProcessDecodeRequest(Request* request) { ...@@ -221,8 +250,8 @@ bool DecoderTemplate<Traits>::ProcessDecodeRequest(Request* request) {
// TODO(sandersd): If a reset has been requested, complete immediately. // TODO(sandersd): If a reset has been requested, complete immediately.
if (!decoder_) { if (!decoder_) {
// TODO(sandersd): Emit an error? HandleError();
return true; return false;
} }
if (pending_decodes_.size() + 1 > if (pending_decodes_.size() + 1 >
...@@ -231,7 +260,15 @@ bool DecoderTemplate<Traits>::ProcessDecodeRequest(Request* request) { ...@@ -231,7 +260,15 @@ bool DecoderTemplate<Traits>::ProcessDecodeRequest(Request* request) {
return false; return false;
} }
scoped_refptr<media::DecoderBuffer> decoder_buffer =
Traits::MakeDecoderBuffer(*request->chunk);
if (decoder_buffer->data_size() == 0) {
HandleError();
return false;
}
// Submit for decoding. // Submit for decoding.
//
// |pending_decode_id_| must not be zero because it is used as a key in a // |pending_decode_id_| must not be zero because it is used as a key in a
// HeapHashMap (pending_decodes_). // HeapHashMap (pending_decodes_).
while (++pending_decode_id_ == 0 || while (++pending_decode_id_ == 0 ||
...@@ -239,7 +276,7 @@ bool DecoderTemplate<Traits>::ProcessDecodeRequest(Request* request) { ...@@ -239,7 +276,7 @@ bool DecoderTemplate<Traits>::ProcessDecodeRequest(Request* request) {
; ;
pending_decodes_.Set(pending_decode_id_, request); pending_decodes_.Set(pending_decode_id_, request);
--requested_decodes_; --requested_decodes_;
decoder_->Decode(std::move(Traits::MakeDecoderBuffer(*request->chunk)), decoder_->Decode(std::move(decoder_buffer),
WTF::Bind(&DecoderTemplate::OnDecodeDone, WTF::Bind(&DecoderTemplate::OnDecodeDone,
WrapWeakPersistent(this), pending_decode_id_)); WrapWeakPersistent(this), pending_decode_id_));
return true; return true;
...@@ -248,6 +285,7 @@ bool DecoderTemplate<Traits>::ProcessDecodeRequest(Request* request) { ...@@ -248,6 +285,7 @@ bool DecoderTemplate<Traits>::ProcessDecodeRequest(Request* request) {
template <typename Traits> template <typename Traits>
bool DecoderTemplate<Traits>::ProcessFlushRequest(Request* request) { bool DecoderTemplate<Traits>::ProcessFlushRequest(Request* request) {
DVLOG(3) << __func__; DVLOG(3) << __func__;
DCHECK(!is_closed_);
DCHECK(!pending_request_); DCHECK(!pending_request_);
DCHECK_EQ(request->type, Request::Type::kFlush); DCHECK_EQ(request->type, Request::Type::kFlush);
...@@ -277,6 +315,7 @@ bool DecoderTemplate<Traits>::ProcessFlushRequest(Request* request) { ...@@ -277,6 +315,7 @@ bool DecoderTemplate<Traits>::ProcessFlushRequest(Request* request) {
template <typename Traits> template <typename Traits>
bool DecoderTemplate<Traits>::ProcessResetRequest(Request* request) { bool DecoderTemplate<Traits>::ProcessResetRequest(Request* request) {
DVLOG(3) << __func__; DVLOG(3) << __func__;
DCHECK(!is_closed_);
DCHECK(!pending_request_); DCHECK(!pending_request_);
DCHECK_EQ(request->type, Request::Type::kReset); DCHECK_EQ(request->type, Request::Type::kReset);
DCHECK_GT(requested_resets_, 0); DCHECK_GT(requested_resets_, 0);
...@@ -291,15 +330,58 @@ bool DecoderTemplate<Traits>::ProcessResetRequest(Request* request) { ...@@ -291,15 +330,58 @@ bool DecoderTemplate<Traits>::ProcessResetRequest(Request* request) {
template <typename Traits> template <typename Traits>
void DecoderTemplate<Traits>::HandleError() { void DecoderTemplate<Traits>::HandleError() {
// TODO(sandersd): Reject outstanding requests. We can stop rejeting at a DVLOG(1) << __func__;
// decode(keyframe), reset(), or configure(), but maybe we should reject if (is_closed_)
// everything already queued (an implicit reset). return;
NOTIMPLEMENTED();
Shutdown(true);
}
template <typename Traits>
void DecoderTemplate<Traits>::Shutdown(bool is_error) {
DVLOG(3) << __func__;
DCHECK(!is_closed_);
// Store the error callback so that we can use it after clearing state.
V8WebCodecsErrorCallback* error_cb = error_cb_.Get();
// Prevent any new public API calls during teardown.
// This should make it safe to call into JS synchronously.
is_closed_ = true;
// Prevent any late callbacks running.
output_cb_.Release();
error_cb_.Release();
// Clear decoding and JS-visible queue state.
decoder_.reset();
pending_decodes_.clear();
requested_decodes_ = 0;
requested_resets_ = 0;
// Fire the error callback if necessary.
// TODO(sandersd): Create a DOMException to report.
if (is_error)
error_cb->InvokeAndReportException(nullptr, nullptr);
// Clear any pending requests, rejecting all promises.
if (pending_request_ && pending_request_->resolver)
pending_request_.Release()->resolver.Release()->Reject();
while (!requests_.IsEmpty()) {
Request* request = requests_.front();
if (request->resolver)
request->resolver.Release()->Reject();
requests_.pop_front();
}
} }
template <typename Traits> template <typename Traits>
void DecoderTemplate<Traits>::OnConfigureFlushDone(media::DecodeStatus status) { void DecoderTemplate<Traits>::OnConfigureFlushDone(media::DecodeStatus status) {
DVLOG(3) << __func__; DVLOG(3) << __func__;
if (is_closed_)
return;
DCHECK(pending_request_); DCHECK(pending_request_);
DCHECK_EQ(pending_request_->type, Request::Type::kConfigure); DCHECK_EQ(pending_request_->type, Request::Type::kConfigure);
...@@ -318,6 +400,9 @@ void DecoderTemplate<Traits>::OnConfigureFlushDone(media::DecodeStatus status) { ...@@ -318,6 +400,9 @@ void DecoderTemplate<Traits>::OnConfigureFlushDone(media::DecodeStatus status) {
template <typename Traits> template <typename Traits>
void DecoderTemplate<Traits>::OnInitializeDone(media::Status status) { void DecoderTemplate<Traits>::OnInitializeDone(media::Status status) {
DVLOG(3) << __func__; DVLOG(3) << __func__;
if (is_closed_)
return;
DCHECK(pending_request_); DCHECK(pending_request_);
DCHECK_EQ(pending_request_->type, Request::Type::kConfigure); DCHECK_EQ(pending_request_->type, Request::Type::kConfigure);
...@@ -329,6 +414,8 @@ void DecoderTemplate<Traits>::OnInitializeDone(media::Status status) { ...@@ -329,6 +414,8 @@ void DecoderTemplate<Traits>::OnInitializeDone(media::Status status) {
} }
pending_request_.Release(); pending_request_.Release();
if (!initializing_sync_)
ProcessRequests(); ProcessRequests();
} }
...@@ -336,14 +423,16 @@ template <typename Traits> ...@@ -336,14 +423,16 @@ template <typename Traits>
void DecoderTemplate<Traits>::OnDecodeDone(uint32_t id, void DecoderTemplate<Traits>::OnDecodeDone(uint32_t id,
media::DecodeStatus status) { media::DecodeStatus status) {
DVLOG(3) << __func__; DVLOG(3) << __func__;
DCHECK(pending_decodes_.Contains(id)); if (is_closed_)
return;
if (status != media::DecodeStatus::OK) { if (status != media::DecodeStatus::OK &&
// TODO(sandersd): Handle ABORTED. status != media::DecodeStatus::ABORTED) {
HandleError(); HandleError();
return; return;
} }
DCHECK(pending_decodes_.Contains(id));
auto it = pending_decodes_.find(id); auto it = pending_decodes_.find(id);
pending_decodes_.erase(it); pending_decodes_.erase(it);
ProcessRequests(); ProcessRequests();
...@@ -352,6 +441,9 @@ void DecoderTemplate<Traits>::OnDecodeDone(uint32_t id, ...@@ -352,6 +441,9 @@ void DecoderTemplate<Traits>::OnDecodeDone(uint32_t id,
template <typename Traits> template <typename Traits>
void DecoderTemplate<Traits>::OnFlushDone(media::DecodeStatus status) { void DecoderTemplate<Traits>::OnFlushDone(media::DecodeStatus status) {
DVLOG(3) << __func__; DVLOG(3) << __func__;
if (is_closed_)
return;
DCHECK(pending_request_); DCHECK(pending_request_);
DCHECK_EQ(pending_request_->type, Request::Type::kFlush); DCHECK_EQ(pending_request_->type, Request::Type::kFlush);
...@@ -367,6 +459,9 @@ void DecoderTemplate<Traits>::OnFlushDone(media::DecodeStatus status) { ...@@ -367,6 +459,9 @@ void DecoderTemplate<Traits>::OnFlushDone(media::DecodeStatus status) {
template <typename Traits> template <typename Traits>
void DecoderTemplate<Traits>::OnResetDone() { void DecoderTemplate<Traits>::OnResetDone() {
DVLOG(3) << __func__; DVLOG(3) << __func__;
if (is_closed_)
return;
DCHECK(pending_request_); DCHECK(pending_request_);
DCHECK_EQ(pending_request_->type, Request::Type::kReset); DCHECK_EQ(pending_request_->type, Request::Type::kReset);
...@@ -377,6 +472,8 @@ void DecoderTemplate<Traits>::OnResetDone() { ...@@ -377,6 +472,8 @@ void DecoderTemplate<Traits>::OnResetDone() {
template <typename Traits> template <typename Traits>
void DecoderTemplate<Traits>::OnOutput(scoped_refptr<MediaOutputType> output) { void DecoderTemplate<Traits>::OnOutput(scoped_refptr<MediaOutputType> output) {
DVLOG(3) << __func__; DVLOG(3) << __func__;
if (is_closed_)
return;
output_cb_->InvokeAndReportException( output_cb_->InvokeAndReportException(
nullptr, MakeGarbageCollected<OutputType>(output)); nullptr, MakeGarbageCollected<OutputType>(output));
} }
......
...@@ -43,7 +43,7 @@ class MODULES_EXPORT DecoderTemplate : public ScriptWrappable { ...@@ -43,7 +43,7 @@ class MODULES_EXPORT DecoderTemplate : public ScriptWrappable {
void decode(const InputType*, ExceptionState&); void decode(const InputType*, ExceptionState&);
ScriptPromise flush(ExceptionState&); ScriptPromise flush(ExceptionState&);
void reset(ExceptionState&); void reset(ExceptionState&);
void close(); void close(ExceptionState&);
// GarbageCollected override. // GarbageCollected override.
void Trace(Visitor*) const override; void Trace(Visitor*) const override;
...@@ -77,6 +77,7 @@ class MODULES_EXPORT DecoderTemplate : public ScriptWrappable { ...@@ -77,6 +77,7 @@ class MODULES_EXPORT DecoderTemplate : public ScriptWrappable {
bool ProcessFlushRequest(Request* request); bool ProcessFlushRequest(Request* request);
bool ProcessResetRequest(Request* request); bool ProcessResetRequest(Request* request);
void HandleError(); void HandleError();
void Shutdown(bool is_error);
// Called by |decoder_|. // Called by |decoder_|.
void OnInitializeDone(media::Status status); void OnInitializeDone(media::Status status);
...@@ -103,6 +104,8 @@ class MODULES_EXPORT DecoderTemplate : public ScriptWrappable { ...@@ -103,6 +104,8 @@ class MODULES_EXPORT DecoderTemplate : public ScriptWrappable {
// TODO(sandersd): Store the last config, flush, and reset so that // TODO(sandersd): Store the last config, flush, and reset so that
// duplicates can be elided. // duplicates can be elided.
std::unique_ptr<MediaDecoderType> decoder_; std::unique_ptr<MediaDecoderType> decoder_;
bool initializing_sync_ = false;
bool is_closed_ = false;
// TODO(sandersd): Can this just be a HashSet by ptr comparison? // TODO(sandersd): Can this just be a HashSet by ptr comparison?
uint32_t pending_decode_id_ = 0; uint32_t pending_decode_id_ = 0;
......
...@@ -71,20 +71,30 @@ CodecConfigEval VideoDecoderTraits::CreateMediaConfig( ...@@ -71,20 +71,30 @@ CodecConfigEval VideoDecoderTraits::CreateMediaConfig(
return CodecConfigEval::kUnsupported; return CodecConfigEval::kUnsupported;
} }
// TODO(sandersd): Can we allow shared ArrayBuffers?
std::vector<uint8_t> extra_data; std::vector<uint8_t> extra_data;
if (config.hasDescription()) { if (config.hasDescription()) {
DOMArrayBuffer* buffer;
if (config.description().IsArrayBuffer()) { if (config.description().IsArrayBuffer()) {
buffer = config.description().GetAsArrayBuffer(); DOMArrayBuffer* buffer = config.description().GetAsArrayBuffer();
} else {
// TODO(sandersd): Can IsNull() be true?
DCHECK(config.description().IsArrayBufferView());
buffer = config.description().GetAsArrayBufferView()->buffer();
}
// TODO(sandersd): Is it possible to not have Data()?
uint8_t* start = static_cast<uint8_t*>(buffer->Data()); uint8_t* start = static_cast<uint8_t*>(buffer->Data());
size_t size = buffer->ByteLengthAsSizeT(); size_t size = buffer->ByteLengthAsSizeT();
extra_data.assign(start, start + size); extra_data.assign(start, start + size);
} else {
DCHECK(config.description().IsArrayBufferView());
DOMArrayBufferView* view =
config.description().GetAsArrayBufferView().Get();
uint8_t* start = static_cast<uint8_t*>(view->BaseAddress());
size_t size = view->byteLengthAsSizeT();
extra_data.assign(start, start + size);
}
}
// If we allow empty |extra_data| here, FFmpegVideoDecoder will expect an
// Annex B formatted stream.
if (codec == media::kCodecH264 && extra_data.empty()) {
*out_console_message =
"H.264 configuration for must include an avcC description.";
return CodecConfigEval::kInvalid;
} }
// TODO(sandersd): Either remove sizes from VideoDecoderConfig (replace with // TODO(sandersd): Either remove sizes from VideoDecoderConfig (replace with
......
...@@ -67,4 +67,10 @@ ...@@ -67,4 +67,10 @@
// resolved but before it is fulfilled. In that case the flush() promise will // resolved but before it is fulfilled. In that case the flush() promise will
// be fulfilled successfully even though reset() was called. // be fulfilled successfully even though reset() was called.
[RaisesException] void reset(); [RaisesException] void reset();
// Immediately shut down the decoder and free its resources. All pending
// decode requests are aborted.
//
// Not recoverable: make a new VideoDecoder if needed.
[RaisesException] void close();
}; };
...@@ -91,7 +91,7 @@ DEFINE_TEXT_PROTO_FUZZER( ...@@ -91,7 +91,7 @@ DEFINE_TEXT_PROTO_FUZZER(
video_decoder->reset(IGNORE_EXCEPTION_FOR_TESTING); video_decoder->reset(IGNORE_EXCEPTION_FOR_TESTING);
break; break;
case wc_fuzzer::VideoDecoderApiInvocation::kClose: case wc_fuzzer::VideoDecoderApiInvocation::kClose:
video_decoder->close(); video_decoder->close(IGNORE_EXCEPTION_FOR_TESTING);
break; break;
case wc_fuzzer::VideoDecoderApiInvocation::API_NOT_SET: case wc_fuzzer::VideoDecoderApiInvocation::API_NOT_SET:
break; break;
......
...@@ -4,6 +4,46 @@ ...@@ -4,6 +4,46 @@
<script src="/resources/testharness.js"></script> <script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script> <script src="/resources/testharnessreport.js"></script>
<script> <script>
'use strict';
// TODO(sandersd): Move metadata into a helper library.
// TODO(sandersd): Add H.264 idecode test once there is an API to query for
// supported codecs.
let h264 = {
buffer: fetch('h264.mp4').then(r => r.arrayBuffer()),
codec: "avc1.64000c",
description: {offset: 7229, size: 46},
frames: [{offset: 48, size: 4007},
{offset: 4055, size: 926},
{offset: 4981, size: 241},
{offset: 5222, size: 97},
{offset: 5319, size: 98},
{offset: 5417, size: 624},
{offset: 6041, size: 185},
{offset: 6226, size: 94},
{offset: 6320, size: 109},
{offset: 6429, size: 281}]
};
let vp9 = {
buffer: fetch('vp9.mp4').then(r => r.arrayBuffer()),
// TODO(sandersd): Verify that the file is actually level 1.
codec: "vp09.00.10.08",
frames: [{offset: 44, size: 3315},
{offset: 3359, size: 203},
{offset: 3562, size: 245},
{offset: 3807, size: 172},
{offset: 3979, size: 312},
{offset: 4291, size: 170},
{offset: 4461, size: 195},
{offset: 4656, size: 181},
{offset: 4837, size: 356},
{offset: 5193, size: 159}]
};
function view(buffer, {offset, size}) {
return new Uint8Array(buffer, offset, size);
}
// Calls done after giving async output/error callbacks a final chance to run. // Calls done after giving async output/error callbacks a final chance to run.
async function asyncDone(test) { async function asyncDone(test) {
...@@ -54,5 +94,133 @@ async_test(async (t) => { ...@@ -54,5 +94,133 @@ async_test(async (t) => {
asyncDone(t); asyncDone(t);
}, 'Test VideoDecoder.configure() codec validity'); }, 'Test VideoDecoder.configure() codec validity');
promise_test(t => vp9.buffer.then(buffer => {
let numOutputs = 0;
let decoder = new VideoDecoder({
output(frame) {
t.step(() => {
assert_equals(++numOutputs, 1, "outputs");
assert_equals(frame.cropWidth, 320, "cropWidth");
assert_equals(frame.cropHeight, 240, "cropHeight");
assert_equals(frame.timestamp, 0, "timestamp");
frame.destroy();
});
},
error(e) {
t.step(() => {
// TODO(sandersd): Change to 'throw e' once e is defined.
throw "decode error";
});
}
});
decoder.configure({codec: vp9.codec});
decoder.decode(new EncodedVideoChunk('key', 0, view(buffer, vp9.frames[0])));
return decoder.flush().then(() => {
assert_equals(numOutputs, 1, "outputs");
});
}), 'Decode VP9');
promise_test(t => {
let decoder = new VideoDecoder({
output(frame) {
t.step(() => {
throw "unexpected output";
});
},
error(e) {
t.step(() => {
throw "unexpected error";
});
}
});
decoder.close();
let fakeChunk = new EncodedVideoChunk('key', 0, Uint8Array.of(0));
assert_throws_dom("InvalidStateError",
() => decoder.configure({codec: vp9.codec}),
"configure");
assert_throws_dom("InvalidStateError",
() => decoder.decode(fakeChunk),
"reset");
assert_throws_dom("InvalidStateError",
() => decoder.reset(),
"reset");
assert_throws_dom("InvalidStateError",
() => decoder.close(),
"close");
return promise_rejects_dom(t, 'InvalidStateError', decoder.flush(), 'flush');
}, 'Closed decoder');
promise_test(t => {
let numErrors = 0;
let decoder = new VideoDecoder({
output(frame) {
t.step(() => {
throw "unexpected output";
});
},
error(e) {
numErrors++;
}
});
let fakeChunk = new EncodedVideoChunk('key', 0, Uint8Array.of(0));
decoder.decode(fakeChunk);
return decoder.flush().then(
() => { throw "flush succeeded unexpectedly"; },
() => { assert_equals(numErrors, 1, "errors"); });
}, 'Decode without configure');
promise_test(t => {
let numErrors = 0;
let decoder = new VideoDecoder({
output(frame) {
t.step(() => {
throw "unexpected output";
});
},
error(e) {
numErrors++;
}
});
decoder.configure({codec: vp9.codec});
let fakeChunk = new EncodedVideoChunk('key', 0, Uint8Array.of(0));
decoder.decode(fakeChunk);
return decoder.flush().then(
() => { throw "flush succeeded unexpectedly"; },
() => { assert_equals(numErrors, 1, "errors"); });
}, 'Decode corrupt VP9 frame');
promise_test(t => {
let numErrors = 0;
let decoder = new VideoDecoder({
output(frame) {
t.step(() => {
throw "unexpected output";
});
},
error(e) {
numErrors++;
}
});
decoder.configure({codec: vp9.codec});
let fakeChunk = new EncodedVideoChunk('key', 0, Uint8Array.of());
decoder.decode(fakeChunk);
return decoder.flush().then(
() => { throw "flush succeeded unexpectedly"; },
() => { assert_equals(numErrors, 1, "errors"); });
}, 'Decode empty VP9 frame');
</script> </script>
</html> </html>
...@@ -8778,6 +8778,7 @@ interface ValidityState ...@@ -8778,6 +8778,7 @@ interface ValidityState
interface VideoDecoder interface VideoDecoder
attribute @@toStringTag attribute @@toStringTag
getter decodeQueueSize getter decodeQueueSize
method close
method configure method configure
method constructor method constructor
method decode method decode
......
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