Commit 4213dae8 authored by John Rummell's avatar John Rummell Committed by Commit Bot

Update H264Accelerator to handle encrypted streams.

H264Decoder will now call H264Accelerator::SetStream() for each buffer
so that the accelerator can check for encrypted streams. It will be
called from Decode() so that the accelerator can return kTryAgain
if it determines that additional data is needed to handle the stream
(e.g. decryption key is needed). Default implementation of SetStream()
does nothing, and returns kNotSupported which allows Decode() to
continue.

The decoder will also call H264Accelerator::ParseSliceHeader() before
calling the same method on H264Parser. This allows the accelerator to
handle the slice header if it is encrypted. Default implementation
returns kNotSupported which forces the decoder to call the H264Parser
method.

BUG=837455
TEST=new media_unittests pass

Cq-Include-Trybots: luci.chromium.try:android_optional_gpu_tests_rel;luci.chromium.try:linux_optional_gpu_tests_rel;luci.chromium.try:mac_optional_gpu_tests_rel;luci.chromium.try:win_optional_gpu_tests_rel
Change-Id: I6d31b57f85b366ca9518904923b00300341afe98
Reviewed-on: https://chromium-review.googlesource.com/c/1166220
Commit-Queue: John Rummell <jrummell@chromium.org>
Reviewed-by: default avatarRintaro Kuroiwa <rkuroiwa@chromium.org>
Cr-Commit-Position: refs/heads/master@{#601775}
parent 01cfef7b
...@@ -21,6 +21,18 @@ H264Decoder::H264Accelerator::H264Accelerator() = default; ...@@ -21,6 +21,18 @@ H264Decoder::H264Accelerator::H264Accelerator() = default;
H264Decoder::H264Accelerator::~H264Accelerator() = default; H264Decoder::H264Accelerator::~H264Accelerator() = default;
H264Decoder::H264Accelerator::Status H264Decoder::H264Accelerator::SetStream(
base::span<const uint8_t> stream,
const DecryptConfig* decrypt_config) {
return H264Decoder::H264Accelerator::Status::kNotSupported;
}
H264Decoder::H264Accelerator::Status
H264Decoder::H264Accelerator::ParseSliceHeader(const H264NALU& slice_nalu,
H264SliceHeader* slice_header) {
return H264Decoder::H264Accelerator::Status::kNotSupported;
}
H264Decoder::H264Decoder(std::unique_ptr<H264Accelerator> accelerator, H264Decoder::H264Decoder(std::unique_ptr<H264Accelerator> accelerator,
const VideoColorSpace& container_color_space) const VideoColorSpace& container_color_space)
: state_(kNeedStreamMetadata), : state_(kNeedStreamMetadata),
...@@ -1204,18 +1216,19 @@ H264Decoder::H264Accelerator::Status H264Decoder::ProcessCurrentSlice() { ...@@ -1204,18 +1216,19 @@ H264Decoder::H264Accelerator::Status H264Decoder::ProcessCurrentSlice() {
return H264Decoder::kDecodeError; \ return H264Decoder::kDecodeError; \
} while (0) } while (0)
#define CHECK_ACCELERATOR_RESULT(func) \ #define CHECK_ACCELERATOR_RESULT(func) \
do { \ do { \
H264Accelerator::Status result = (func); \ H264Accelerator::Status result = (func); \
switch (result) { \ switch (result) { \
case H264Accelerator::Status::kOk: \ case H264Accelerator::Status::kOk: \
break; \ break; \
case H264Accelerator::Status::kTryAgain: \ case H264Accelerator::Status::kTryAgain: \
DVLOG(1) << #func " needs to try again"; \ DVLOG(1) << #func " needs to try again"; \
return H264Decoder::kTryAgain; \ return H264Decoder::kTryAgain; \
case H264Accelerator::Status::kFail: \ case H264Accelerator::Status::kFail: \
SET_ERROR_AND_RETURN(); \ case H264Accelerator::Status::kNotSupported: \
} \ SET_ERROR_AND_RETURN(); \
} \
} while (0) } while (0)
void H264Decoder::SetStream(int32_t id, void H264Decoder::SetStream(int32_t id,
...@@ -1228,6 +1241,9 @@ void H264Decoder::SetStream(int32_t id, ...@@ -1228,6 +1241,9 @@ void H264Decoder::SetStream(int32_t id,
DVLOG(4) << "New input stream id: " << id << " at: " << (void*)ptr DVLOG(4) << "New input stream id: " << id << " at: " << (void*)ptr
<< " size: " << size; << " size: " << size;
stream_id_ = id; stream_id_ = id;
current_stream_ = ptr;
current_stream_size_ = size;
current_stream_has_been_changed_ = true;
if (decrypt_config) { if (decrypt_config) {
parser_.SetEncryptedStream(ptr, size, decrypt_config->subsamples()); parser_.SetEncryptedStream(ptr, size, decrypt_config->subsamples());
current_decrypt_config_ = decrypt_config->Clone(); current_decrypt_config_ = decrypt_config->Clone();
...@@ -1243,6 +1259,30 @@ H264Decoder::DecodeResult H264Decoder::Decode() { ...@@ -1243,6 +1259,30 @@ H264Decoder::DecodeResult H264Decoder::Decode() {
return kDecodeError; return kDecodeError;
} }
if (current_stream_has_been_changed_) {
// Calling H264Accelerator::SetStream() here instead of when the stream is
// originally set in case the accelerator needs to return kTryAgain.
H264Accelerator::Status result = accelerator_->SetStream(
base::span<const uint8_t>(current_stream_, current_stream_size_),
current_decrypt_config_.get());
switch (result) {
case H264Accelerator::Status::kOk:
case H264Accelerator::Status::kNotSupported:
// kNotSupported means the accelerator can't handle this stream,
// so everything will be done through the parser.
break;
case H264Accelerator::Status::kTryAgain:
DVLOG(1) << "SetStream() needs to try again";
return H264Decoder::kTryAgain;
case H264Accelerator::Status::kFail:
SET_ERROR_AND_RETURN();
}
// Reset the flag so that this is only called again next time SetStream()
// is called.
current_stream_has_been_changed_ = false;
}
while (1) { while (1) {
H264Parser::Result par_res; H264Parser::Result par_res;
...@@ -1283,10 +1323,28 @@ H264Decoder::DecodeResult H264Decoder::Decode() { ...@@ -1283,10 +1323,28 @@ H264Decoder::DecodeResult H264Decoder::Decode() {
if (!curr_slice_hdr_) { if (!curr_slice_hdr_) {
curr_slice_hdr_.reset(new H264SliceHeader()); curr_slice_hdr_.reset(new H264SliceHeader());
par_res = // If the accelerator handles the slice header, let it handle it.
parser_.ParseSliceHeader(*curr_nalu_, curr_slice_hdr_.get()); // If not, use the parser.
if (par_res != H264Parser::kOk) H264Accelerator::Status result = accelerator_->ParseSliceHeader(
SET_ERROR_AND_RETURN(); *curr_nalu_, curr_slice_hdr_.get());
switch (result) {
case H264Accelerator::Status::kOk:
break;
case H264Accelerator::Status::kTryAgain:
DVLOG(1) << "ParseSliceHeader() needs to try again";
// reset |curr_slice_hdr_| so ParseSliceHeader() is tried again.
curr_slice_hdr_.reset();
return H264Decoder::kTryAgain;
case H264Accelerator::Status::kNotSupported:
// Let the parser try to handle it.
par_res =
parser_.ParseSliceHeader(*curr_nalu_, curr_slice_hdr_.get());
if (par_res == H264Parser::kOk)
break;
FALLTHROUGH;
case H264Accelerator::Status::kFail:
SET_ERROR_AND_RETURN();
}
state_ = kTryPreprocessCurrentSlice; state_ = kTryPreprocessCurrentSlice;
} }
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include "base/containers/span.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "media/base/limits.h" #include "media/base/limits.h"
...@@ -43,11 +44,19 @@ class MEDIA_GPU_EXPORT H264Decoder : public AcceleratedVideoDecoder { ...@@ -43,11 +44,19 @@ class MEDIA_GPU_EXPORT H264Decoder : public AcceleratedVideoDecoder {
// is called again, it will attempt to resume processing of the stream // is called again, it will attempt to resume processing of the stream
// by calling the same method again. // by calling the same method again.
enum class Status { enum class Status {
kOk, // Operation completed successfully. // Operation completed successfully.
kFail, // Operation failed. kOk,
kTryAgain, // Operation failed because some external data is missing.
// Retry the same operation later, once the data has been // Operation failed.
// provided. kFail,
// Operation failed because some external data is missing. Retry the same
// operation later, once the data has been provided.
kTryAgain,
// Operation is not supported. Used by SetStream() and ParseSliceHeader()
// to indicate that the Accelerator can not handle this operation.
kNotSupported,
}; };
H264Accelerator(); H264Accelerator();
...@@ -121,6 +130,26 @@ class MEDIA_GPU_EXPORT H264Decoder : public AcceleratedVideoDecoder { ...@@ -121,6 +130,26 @@ class MEDIA_GPU_EXPORT H264Decoder : public AcceleratedVideoDecoder {
// any cached parameters/slices that have not been committed yet. // any cached parameters/slices that have not been committed yet.
virtual void Reset() = 0; virtual void Reset() = 0;
// Notifies the accelerator whenever there is a new stream to process.
// |stream| is the data in annex B format, which may include SPS and PPS
// NALUs when there is a configuration change. The first frame must contain
// the SPS and PPS NALUs. SPS and PPS NALUs may not be encrypted.
// |decrypt_config| is the config for decrypting the stream. The accelerator
// should use |decrypt_config| to keep track of the parts of |stream| that
// are encrypted. If kTryAgain is returned, the decoder will retry this call
// later. This method has a default implementation that returns
// kNotSupported.
virtual Status SetStream(base::span<const uint8_t> stream,
const DecryptConfig* decrypt_config);
// Parse a slice header, returning it in |*slice_header|. |slice_nalu| must
// be a slice NALU. On success, this populates |*slice_header|. If the
// Accelerator doesn't handle this slice header, then it should return
// kNotSupported. This method has a default implementation that returns
// kNotSupported.
virtual Status ParseSliceHeader(const H264NALU& slice_nalu,
H264SliceHeader* slice_header);
private: private:
DISALLOW_COPY_AND_ASSIGN(H264Accelerator); DISALLOW_COPY_AND_ASSIGN(H264Accelerator);
}; };
...@@ -281,9 +310,17 @@ class MEDIA_GPU_EXPORT H264Decoder : public AcceleratedVideoDecoder { ...@@ -281,9 +310,17 @@ class MEDIA_GPU_EXPORT H264Decoder : public AcceleratedVideoDecoder {
// Parser in use. // Parser in use.
H264Parser parser_; H264Parser parser_;
// Most recent call to SetStream().
const uint8_t* current_stream_ = nullptr;
size_t current_stream_size_ = 0;
// Decrypting config for the most recent data passed to SetStream(). // Decrypting config for the most recent data passed to SetStream().
std::unique_ptr<DecryptConfig> current_decrypt_config_; std::unique_ptr<DecryptConfig> current_decrypt_config_;
// Keep track of when SetStream() is called so that
// H264Accelerator::SetStream() can be called.
bool current_stream_has_been_changed_ = false;
// DPB in use. // DPB in use.
H264DPB dpb_; H264DPB dpb_;
......
...@@ -5,11 +5,13 @@ ...@@ -5,11 +5,13 @@
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <string.h>
#include <cstring>
#include <memory> #include <memory>
#include <string> #include <string>
#include "base/command_line.h" #include "base/command_line.h"
#include "base/containers/queue.h" #include "base/containers/queue.h"
#include "base/containers/span.h"
#include "base/files/file_util.h" #include "base/files/file_util.h"
#include "base/logging.h" #include "base/logging.h"
#include "media/base/test_data_util.h" #include "media/base/test_data_util.h"
...@@ -28,6 +30,7 @@ using ::testing::MatcherInterface; ...@@ -28,6 +30,7 @@ using ::testing::MatcherInterface;
using ::testing::MatchResultListener; using ::testing::MatchResultListener;
using ::testing::Mock; using ::testing::Mock;
using ::testing::Return; using ::testing::Return;
using ::testing::WithArg;
namespace media { namespace media {
namespace { namespace {
...@@ -59,6 +62,46 @@ MATCHER(SubsampleSizeMatches, "Verify subsample sizes match buffer size") { ...@@ -59,6 +62,46 @@ MATCHER(SubsampleSizeMatches, "Verify subsample sizes match buffer size") {
return subsample_total_size == buffer_size; return subsample_total_size == buffer_size;
} }
// Given a H264NALU (arg0), compute the slice header and store a copy in
// both |arg1| and |slice_header|. This assumes that the NALU comes from
// kBaselineFrame0.
ACTION_P(ComputeSliceHeader, slice_header) {
const H264NALU& slice_nalu = arg0;
// |arg1| and |slice_header| are H264SliceHeader*.
// Ideally we could just parse |slice_nalu|, but the parser needs additional
// data (like SPS and PPS entries) which we don't have. So this simulates
// parsing of |slice_nalu| by simply setting the appropriate fields
// Zero out |slice_header| so there is no need to set a lot of default values.
std::memset(slice_header, 0, sizeof(H264SliceHeader));
// Extract the values directly from the H264NALU provided.
slice_header->idr_pic_flag = (slice_nalu.nal_unit_type == 5);
slice_header->nal_ref_idc = slice_nalu.nal_ref_idc;
slice_header->nalu_data = slice_nalu.data;
slice_header->nalu_size = slice_nalu.size;
// Don't want to duplicate all the work of H264Parser.ParseSliceHeader(),
// so the following were determined by looking at the slice header after
// H264_Parser.ParseSliceHeader() was called on kBaselineFrame0.
slice_header->header_bit_size = 0x24;
slice_header->slice_type = 7;
slice_header->slice_qp_delta = 8;
slice_header->dec_ref_pic_marking_bit_size = 2u;
// Now that we have created our local copy of the slice header, copy it into
// |arg1| and return success.
std::memcpy(arg1, slice_header, sizeof(H264SliceHeader));
return H264Decoder::H264Accelerator::Status::kOk;
}
// Compare 2 H264SliceHeader objects for equality.
MATCHER_P(SliceHeaderMatches, slice_header, "Verify H264SliceHeader objects") {
// Rather than match pointers, the contents must be the same.
return std::memcmp(arg, slice_header, sizeof(H264SliceHeader)) == 0;
}
class MockH264Accelerator : public H264Decoder::H264Accelerator { class MockH264Accelerator : public H264Decoder::H264Accelerator {
public: public:
MockH264Accelerator() = default; MockH264Accelerator() = default;
...@@ -83,6 +126,12 @@ class MockH264Accelerator : public H264Decoder::H264Accelerator { ...@@ -83,6 +126,12 @@ class MockH264Accelerator : public H264Decoder::H264Accelerator {
size_t size, size_t size,
const std::vector<SubsampleEntry>& subsamples)); const std::vector<SubsampleEntry>& subsamples));
MOCK_METHOD1(OutputPicture, bool(const scoped_refptr<H264Picture>& pic)); MOCK_METHOD1(OutputPicture, bool(const scoped_refptr<H264Picture>& pic));
MOCK_METHOD2(SetStream,
Status(base::span<const uint8_t> stream,
const DecryptConfig* decrypt_config));
MOCK_METHOD2(ParseSliceHeader,
Status(const H264NALU& slice_nalu,
H264SliceHeader* slice_header));
void Reset() override {} void Reset() override {}
}; };
...@@ -130,6 +179,12 @@ void H264DecoderTest::SetUp() { ...@@ -130,6 +179,12 @@ void H264DecoderTest::SetUp() {
ON_CALL(*accelerator_, SubmitSlice(_, _, _, _, _, _, _, _)) ON_CALL(*accelerator_, SubmitSlice(_, _, _, _, _, _, _, _))
.With(Args<6, 7>(SubsampleSizeMatches())) .With(Args<6, 7>(SubsampleSizeMatches()))
.WillByDefault(Return(H264Decoder::H264Accelerator::Status::kOk)); .WillByDefault(Return(H264Decoder::H264Accelerator::Status::kOk));
ON_CALL(*accelerator_, SetStream(_, _))
.WillByDefault(
Return(H264Decoder::H264Accelerator::Status::kNotSupported));
ON_CALL(*accelerator_, ParseSliceHeader(_, _))
.WillByDefault(
Return(H264Decoder::H264Accelerator::Status::kNotSupported));
} }
void H264DecoderTest::SetInputFrameFiles( void H264DecoderTest::SetInputFrameFiles(
...@@ -522,5 +577,66 @@ TEST_F(H264DecoderTest, SubmitDecodeRetry) { ...@@ -522,5 +577,66 @@ TEST_F(H264DecoderTest, SubmitDecodeRetry) {
ASSERT_TRUE(decoder_->Flush()); ASSERT_TRUE(decoder_->Flush());
} }
TEST_F(H264DecoderTest, SetStreamRetry) {
SetInputFrameFiles({kBaselineFrame0});
EXPECT_CALL(*accelerator_, SetStream(_, _))
.WillOnce(Return(H264Decoder::H264Accelerator::Status::kTryAgain))
.WillOnce(Return(H264Decoder::H264Accelerator::Status::kOk));
ASSERT_EQ(AcceleratedVideoDecoder::kTryAgain, Decode());
ASSERT_EQ(AcceleratedVideoDecoder::kAllocateNewSurfaces, Decode());
EXPECT_EQ(gfx::Size(320, 192), decoder_->GetPicSize());
EXPECT_LE(9u, decoder_->GetRequiredNumOfPictures());
{
InSequence sequence;
EXPECT_CALL(*accelerator_, ParseSliceHeader(_, _));
EXPECT_CALL(*accelerator_, CreateH264Picture());
EXPECT_CALL(*accelerator_, SubmitFrameMetadata(_, _, _, _, _, _, _));
EXPECT_CALL(*accelerator_, SubmitSlice(_, _, _, _, _, _, _, _));
}
ASSERT_EQ(AcceleratedVideoDecoder::kRanOutOfStreamData, Decode());
{
InSequence sequence;
EXPECT_CALL(*accelerator_, SubmitDecode(WithPoc(0)));
EXPECT_CALL(*accelerator_, OutputPicture(WithPoc(0)));
}
ASSERT_TRUE(decoder_->Flush());
}
TEST_F(H264DecoderTest, ParseSliceHeaderRetry) {
SetInputFrameFiles({kBaselineFrame0});
ASSERT_EQ(AcceleratedVideoDecoder::kAllocateNewSurfaces, Decode());
EXPECT_EQ(gfx::Size(320, 192), decoder_->GetPicSize());
EXPECT_LE(9u, decoder_->GetRequiredNumOfPictures());
EXPECT_CALL(*accelerator_, ParseSliceHeader(_, _))
.WillOnce(Return(H264Decoder::H264Accelerator::Status::kTryAgain));
ASSERT_EQ(AcceleratedVideoDecoder::kTryAgain, Decode());
H264SliceHeader slice_header = {};
{
InSequence sequence;
EXPECT_CALL(*accelerator_, ParseSliceHeader(_, _))
.WillOnce(ComputeSliceHeader(&slice_header));
EXPECT_CALL(*accelerator_, CreateH264Picture());
EXPECT_CALL(*accelerator_, SubmitFrameMetadata(_, _, _, _, _, _, _));
EXPECT_CALL(*accelerator_, SubmitSlice(_, SliceHeaderMatches(&slice_header),
_, _, _, _, _, _));
}
ASSERT_EQ(AcceleratedVideoDecoder::kRanOutOfStreamData, Decode());
{
InSequence sequence;
EXPECT_CALL(*accelerator_, SubmitDecode(WithPoc(0)));
EXPECT_CALL(*accelerator_, OutputPicture(WithPoc(0)));
}
ASSERT_TRUE(decoder_->Flush());
}
} // namespace } // namespace
} // namespace media } // namespace media
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