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,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();
uint8_t* start = static_cast<uint8_t*>(buffer->Data());
size_t size = buffer->ByteLengthAsSizeT();
extra_data.assign(start, start + size);
} else { } else {
// TODO(sandersd): Can IsNull() be true?
DCHECK(config.description().IsArrayBufferView()); DCHECK(config.description().IsArrayBufferView());
buffer = config.description().GetAsArrayBufferView()->buffer(); 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);
} }
// TODO(sandersd): Is it possible to not have Data()? }
uint8_t* start = static_cast<uint8_t*>(buffer->Data());
size_t size = buffer->ByteLengthAsSizeT(); // If we allow empty |extra_data| here, FFmpegVideoDecoder will expect an
extra_data.assign(start, start + size); // 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