From 522d4bf852c484579de0591879a1d006abf08959 Mon Sep 17 00:00:00 2001 From: John Koleszar Date: Tue, 5 Mar 2013 12:23:34 -0800 Subject: [PATCH] Add 'superframe' index A 'superframe' is a group of frames that share the same PTS, but have a defined decoding order. This commit adds the ability to append an index to such a group of frames, allowing for random access to the constituent frames. This could be useful for frame-level parallelism or partial decoding in a multilayer scenario. Decoding the stream serially without such an index should work as a fallback, and VP9/TestSuperframeIndexIsOptional verifies that. Change-Id: Idff83b7560e1a7077d8fb067bfbc45b567e78b1c --- test/encode_test_driver.cc | 1 + test/encode_test_driver.h | 6 +++ test/superframe_test.cc | 100 +++++++++++++++++++++++++++++++++++++ test/test.mk | 1 + vp9/encoder/vp9_onyx_if.c | 2 +- vp9/vp9_cx_iface.c | 52 ++++++++++++++++++- vp9/vp9_dx_iface.c | 67 +++++++++++++++++++++++++ 7 files changed, 227 insertions(+), 2 deletions(-) create mode 100644 test/superframe_test.cc diff --git a/test/encode_test_driver.cc b/test/encode_test_driver.cc index 3265ff939..75921aa02 100644 --- a/test/encode_test_driver.cc +++ b/test/encode_test_driver.cc @@ -169,6 +169,7 @@ void EncoderTest::RunLoop(VideoSource *video) { bool has_cxdata = false; bool has_dxdata = false; while (const vpx_codec_cx_pkt_t *pkt = iter.Next()) { + pkt = MutateEncoderOutputHook(pkt); again = true; switch (pkt->kind) { case VPX_CODEC_CX_FRAME_PKT: diff --git a/test/encode_test_driver.h b/test/encode_test_driver.h index 35082a7fd..5a37816ab 100644 --- a/test/encode_test_driver.h +++ b/test/encode_test_driver.h @@ -203,6 +203,12 @@ class EncoderTest { virtual void DecompressedFrameHook(const vpx_image_t& img, vpx_codec_pts_t pts) {} + // Hook that can modify the encoder's output data + virtual const vpx_codec_cx_pkt_t * MutateEncoderOutputHook( + const vpx_codec_cx_pkt_t *pkt) { + return pkt; + } + bool abort_; vpx_codec_enc_cfg_t cfg_; unsigned int passes_; diff --git a/test/superframe_test.cc b/test/superframe_test.cc new file mode 100644 index 000000000..39190ea78 --- /dev/null +++ b/test/superframe_test.cc @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2012 The WebM project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#include +#include "third_party/googletest/src/include/gtest/gtest.h" +#include "test/codec_factory.h" +#include "test/encode_test_driver.h" +#include "test/i420_video_source.h" +#include "test/util.h" + +namespace { + +class SuperframeTest : public ::libvpx_test::EncoderTest, + public ::libvpx_test::CodecTestWithParam { + protected: + SuperframeTest() : EncoderTest(GET_PARAM(0)), modified_buf_(NULL), + last_sf_pts_(0) {} + + virtual void SetUp() { + InitializeConfig(); + SetMode(GET_PARAM(1)); + sf_count_ = 0; + sf_count_max_ = INT_MAX; + } + + virtual void TearDown() { + delete modified_buf_; + } + + virtual bool Continue() const { + return !HasFatalFailure() && !abort_; + } + + virtual void PreEncodeFrameHook(libvpx_test::VideoSource *video, + libvpx_test::Encoder *encoder) { + if (video->frame() == 1) { + encoder->Control(VP8E_SET_ENABLEAUTOALTREF, 1); + } + } + + virtual const vpx_codec_cx_pkt_t * MutateEncoderOutputHook( + const vpx_codec_cx_pkt_t *pkt) { + if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) + return pkt; + + const uint8_t *buffer = reinterpret_cast(pkt->data.frame.buf); + const uint8_t marker = buffer[pkt->data.frame.sz - 1]; + const int frames = (marker & 0x7) + 1; + const int mag = ((marker >> 3) & 3) + 1; + const unsigned int index_sz = 2 + mag * frames; + if ((marker & 0xf0) == 0xc0 && + pkt->data.frame.sz >= index_sz && + buffer[pkt->data.frame.sz - index_sz] == marker) { + // frame is a superframe. strip off the index. + if (modified_buf_) + delete modified_buf_; + modified_buf_ = new uint8_t[pkt->data.frame.sz - index_sz]; + memcpy(modified_buf_, pkt->data.frame.buf, + pkt->data.frame.sz - index_sz); + modified_pkt_ = *pkt; + modified_pkt_.data.frame.buf = modified_buf_; + modified_pkt_.data.frame.sz -= index_sz; + + sf_count_++; + last_sf_pts_ = pkt->data.frame.pts; + return &modified_pkt_; + } + + // Make sure we do a few frames after the last SF + abort_ |= sf_count_ > sf_count_max_ && + pkt->data.frame.pts - last_sf_pts_ >= 5; + return pkt; + } + + int sf_count_; + int sf_count_max_; + vpx_codec_cx_pkt_t modified_pkt_; + uint8_t *modified_buf_; + vpx_codec_pts_t last_sf_pts_; +}; + +TEST_P(SuperframeTest, TestSuperframeIndexIsOptional) { + sf_count_max_ = 0; // early exit on successful test. + cfg_.g_lag_in_frames = 25; + + ::libvpx_test::I420VideoSource video("hantro_collage_w352h288.yuv", 352, 288, + 30, 1, 0, 40); + ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); + EXPECT_EQ(sf_count_, 1); +} + +VP9_INSTANTIATE_TEST_CASE(SuperframeTest, ::testing::Values( + ::libvpx_test::kTwoPassGood)); +} // namespace diff --git a/test/test.mk b/test/test.mk index f7a146267..37e4ee793 100644 --- a/test/test.mk +++ b/test/test.mk @@ -68,6 +68,7 @@ LIBVPX_TEST_SRCS-yes += vp9_boolcoder_test.cc # IDCT test currently depends on FDCT function LIBVPX_TEST_SRCS-yes += idct8x8_test.cc +LIBVPX_TEST_SRCS-yes += superframe_test.cc LIBVPX_TEST_SRCS-yes += tile_independence_test.cc endif diff --git a/vp9/encoder/vp9_onyx_if.c b/vp9/encoder/vp9_onyx_if.c index 6335827cf..1d9e7a4e9 100644 --- a/vp9/encoder/vp9_onyx_if.c +++ b/vp9/encoder/vp9_onyx_if.c @@ -4009,7 +4009,7 @@ int vp9_get_preview_raw_frame(VP9_PTR comp, YV12_BUFFER_CONFIG *dest, vp9_ppflags_t *flags) { VP9_COMP *cpi = (VP9_COMP *) comp; - if (cpi->refresh_alt_ref_frame) + if (!cpi->common.show_frame) return -1; else { int ret; diff --git a/vp9/vp9_cx_iface.c b/vp9/vp9_cx_iface.c index db7a2fd11..724f4185f 100644 --- a/vp9/vp9_cx_iface.c +++ b/vp9/vp9_cx_iface.c @@ -77,6 +77,9 @@ struct vpx_codec_alg_priv { unsigned int cx_data_sz; unsigned char *pending_cx_data; unsigned int pending_cx_data_sz; + int pending_frame_count; + uint32_t pending_frame_sizes[8]; + uint32_t pending_frame_magnitude; vpx_image_t preview_img; vp8_postproc_cfg_t preview_ppcfg; vpx_codec_pkt_list_decl(64) pkt_list; // changed to accomendate the maximum number of lagged frames allowed @@ -579,6 +582,45 @@ static void pick_quickcompress_mode(vpx_codec_alg_priv_t *ctx, } +static void write_superframe_index(vpx_codec_alg_priv_t *ctx) { + uint8_t marker = 0xc0; + int mag, mask, index_sz; + + assert(ctx->pending_frame_count); + assert(ctx->pending_frame_count <= 8); + + /* Add the number of frames to the marker byte */ + marker |= ctx->pending_frame_count - 1; + + /* Choose the magnitude */ + for (mag = 0, mask = 0xff; mag < 4; mag++) { + if (ctx->pending_frame_magnitude < mask) + break; + mask <<= 8; + mask |= 0xff; + } + marker |= mag << 3; + + /* Write the index */ + index_sz = 2 + (mag + 1) * ctx->pending_frame_count; + if (ctx->pending_cx_data_sz + index_sz < ctx->cx_data_sz) { + uint8_t *x = ctx->pending_cx_data + ctx->pending_cx_data_sz; + int i, j; + + *x++ = marker; + for (i = 0; i < ctx->pending_frame_count; i++) { + int this_sz = ctx->pending_frame_sizes[i]; + + for (j = 0; j <= mag; j++) { + *x++ = this_sz & 0xff; + this_sz >>= 8; + } + } + *x++ = marker; + ctx->pending_cx_data_sz += index_sz; + } +} + static vpx_codec_err_t vp8e_encode(vpx_codec_alg_priv_t *ctx, const vpx_image_t *img, vpx_codec_pts_t pts, @@ -712,6 +754,8 @@ static vpx_codec_err_t vp8e_encode(vpx_codec_alg_priv_t *ctx, if (!ctx->pending_cx_data) ctx->pending_cx_data = cx_data; ctx->pending_cx_data_sz += size; + ctx->pending_frame_sizes[ctx->pending_frame_count++] = size; + ctx->pending_frame_magnitude |= size; cx_data += size; cx_data_sz -= size; continue; @@ -771,10 +815,16 @@ static vpx_codec_err_t vp8e_encode(vpx_codec_alg_priv_t *ctx, else*/ { if (ctx->pending_cx_data) { + ctx->pending_frame_sizes[ctx->pending_frame_count++] = size; + ctx->pending_frame_magnitude |= size; + ctx->pending_cx_data_sz += size; + write_superframe_index(ctx); pkt.data.frame.buf = ctx->pending_cx_data; - pkt.data.frame.sz = ctx->pending_cx_data_sz + size; + pkt.data.frame.sz = ctx->pending_cx_data_sz; ctx->pending_cx_data = NULL; ctx->pending_cx_data_sz = 0; + ctx->pending_frame_count = 0; + ctx->pending_frame_magnitude = 0; } else { pkt.data.frame.buf = cx_data; pkt.data.frame.sz = size; diff --git a/vp9/vp9_dx_iface.c b/vp9/vp9_dx_iface.c index f2b80e1fb..1bda1703f 100644 --- a/vp9/vp9_dx_iface.c +++ b/vp9/vp9_dx_iface.c @@ -425,6 +425,39 @@ static vpx_codec_err_t decode_one(vpx_codec_alg_priv_t *ctx, return res; } +static void parse_superframe_index(const uint8_t *data, + size_t data_sz, + uint32_t sizes[8], + int *count) { + uint8_t marker; + + assert(data_sz); + marker = data[data_sz - 1]; + *count = 0; + + if ((marker & 0xf0) == 0xc0) { + const int frames = (marker & 0x7) + 1; + const int mag = ((marker >> 3) & 3) + 1; + const int index_sz = 2 + mag * frames; + + if (data_sz >= index_sz && data[data_sz - index_sz] == marker) { + // found a valid superframe index + int i, j; + const uint8_t *x = data + data_sz - index_sz + 1; + + for (i = 0; i < frames; i++) { + int this_sz = 0; + + for (j = 0; j < mag; j++) + this_sz |= (*x++) << (j * 8); + sizes[i] = this_sz; + } + + *count = frames; + } + } +} + static vpx_codec_err_t vp9_decode(vpx_codec_alg_priv_t *ctx, const uint8_t *data, unsigned int data_sz, @@ -433,8 +466,42 @@ static vpx_codec_err_t vp9_decode(vpx_codec_alg_priv_t *ctx, const uint8_t *data_start = data; const uint8_t *data_end = data + data_sz; vpx_codec_err_t res = 0; + uint32_t sizes[8]; + int frames_this_pts, frame_count = 0; + + parse_superframe_index(data, data_sz, sizes, &frames_this_pts); do { + // Skip over the superframe index, if present + if (data_sz && (*data_start & 0xf0) == 0xc0) { + const uint8_t marker = *data_start; + const int frames = (marker & 0x7) + 1; + const int mag = ((marker >> 3) & 3) + 1; + const int index_sz = 2 + mag * frames; + + if (data_sz >= index_sz && data_start[index_sz - 1] == marker) { + data_start += index_sz; + data_sz -= index_sz; + if (data_start < data_end) + continue; + else + break; + } + } + + // Use the correct size for this frame, if an index is present. + if (frames_this_pts) { + uint32_t this_sz = sizes[frame_count]; + + if (data_sz < this_sz) { + ctx->base.err_detail = "Invalid frame size in index"; + return VPX_CODEC_CORRUPT_FRAME; + } + + data_sz = this_sz; + frame_count++; + } + res = decode_one(ctx, &data_start, data_sz, user_priv, deadline); assert(data_start >= data); assert(data_start <= data_end);