diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5fb8d6f..8f725f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,6 +55,7 @@ jobs: mkdir -p corpus_patch head -c 60000 tests/streams/cv608_synth_1080p_cbr2m.svac3 > corpus_patch/idr.svac3 head -c 60000 tests/streams/cv608_smartp_1080p.svac3 > corpus_patch/library.svac3 + head -c 60000 tests/streams/cv608_patch4_1080p.svac3 > corpus_patch/patches.svac3 ./fuzz_patch -max_total_time=60 -max_len=65536 corpus_patch ffmpeg: diff --git a/CMakeLists.txt b/CMakeLists.txt index 531696b..61c17f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,11 +50,15 @@ add_test(NAME test_dec COMMAND test_dec ${CMAKE_SOURCE_DIR}/tests/streams/cv608_synth_1080p_cbr2m.svac3) add_test(NAME test_dec_library COMMAND test_dec -library ${CMAKE_SOURCE_DIR}/tests/streams/cv608_smartp_1080p.svac3) +add_test(NAME test_dec_patches + COMMAND test_dec -patches ${CMAKE_SOURCE_DIR}/tests/streams/cv608_patch4_1080p.svac3) add_executable(test_api tests/test_api.c) target_include_directories(test_api PRIVATE source/svac3 tools) target_link_libraries(test_api svac3d) add_test(NAME test_api COMMAND test_api ${CMAKE_SOURCE_DIR}/tests/streams/cv608_synth_1080p_cbr2m.svac3) +add_test(NAME test_api_lost_patch + COMMAND test_api -lost ${CMAKE_SOURCE_DIR}/tests/streams/cv608_patch4_1080p.svac3) find_package(Python3 COMPONENTS Interpreter REQUIRED) foreach(stream ${SVAC3_TEST_STREAMS}) get_filename_component(name ${stream} NAME_WE) diff --git a/README.md b/README.md index 3579229..7449299 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,8 @@ says what the decoder does instead. The Main profile (0x30) tool set that HiSilicon encoders emit: - 8-bit 4:2:0; -- I and P pictures, one patch per picture; +- I and P pictures, in one patch or several (uniform patches, with the + loop filters crossing patch boundaries); - MAEC entropy coding, SRCC coefficients, EIPM (66 intra modes) and MIPF; - TMVP, the median motion vector predictor, and skip/direct; - several reference pictures, and library pictures (the library stream @@ -24,8 +25,9 @@ The Main profile (0x30) tool set that HiSilicon encoders emit: Every picture of the test corpus is bit-exact against the reference decoder (`tests/golden/*.framemd5`). -Anything else (B pictures, non-displayed library pictures, several -patches, the security layer, the High profile's tools) is refused with +Anything else (B pictures, non-displayed library pictures, non-uniform +patches or loop filters stopping at patch boundaries, the security layer, +the High profile's tools) is refused with `SVAC3D_ERR_UNSUPPORTED` rather than decoded wrongly. ## Build diff --git a/source/svac3/svac3_dec.c b/source/svac3/svac3_dec.c index 107f6bf..7be5529 100644 --- a/source/svac3/svac3_dec.c +++ b/source/svac3/svac3_dec.c @@ -37,8 +37,19 @@ struct svac3_dec { int64_t cycle; int doi_prev, have_prev; int64_t last_poi; + + /* the picture whose patches are being decoded */ + svac3_picture_t *open; + pic_t *open_slot; + int open_lib; + int open_keep[DPB_SLOTS]; + svac3_refs_t open_refs; + int64_t open_doi, open_poi; + uint32_t open_index; }; +static void abandon(svac3_dec_t *d); + svac3_dec_t *svac3_dec_create(void) { return calloc(1, sizeof(svac3_dec_t)); @@ -63,6 +74,7 @@ void svac3_dec_destroy(svac3_dec_t *d) { if (!d) return; + abandon(d); free_pics(d); free(d->rbsp); free(d); @@ -70,6 +82,7 @@ void svac3_dec_destroy(svac3_dec_t *d) void svac3_dec_reset(svac3_dec_t *d) { + abandon(d); for (int i = 0; i < DPB_SLOTS; i++) d->pic[i].used = 0; for (int i = 0; i < LIB_SLOTS; i++) @@ -172,48 +185,43 @@ static int64_t clamp_dist(int64_t dist) return dist < -32768 ? -32768 : dist > 32767 ? 32767 : dist; } +static void abandon(svac3_dec_t *d) +{ + svac3_picture_free(d->open); + d->open = NULL; +} + /* A library picture (library_stream_flag 1): an I picture kept, with its * library_picture_index, for display pictures to reference, and output * since only display library patches (type 17) are accepted. A display - * picture naming another index finds no reference. */ -static int decode_library(svac3_dec_t *d, const uint8_t *rbsp, size_t rl, int64_t doi, - int64_t poi, const svac3_frame_t **out) + * picture naming another index finds no reference. It is decoded into + * the slot not holding the current library picture, which gives way only + * once the new one has decoded. */ +static int start_library(svac3_dec_t *d) { svac3_hls_t *hls = &d->hls; - const uint32_t index = hls->ph.library_picture_index; int ret; if (hls->ph.picture_coding_type != 1) return SVAC3_ERR_UNSUPPORTED; /* inter-coded library pictures */ - - /* decode into the slot not holding the current library picture, which - * gives way only once the new one has decoded */ pic_t *cur = d->lib[0].used ? &d->lib[1] : &d->lib[0]; cur->used = 0; if (!cur->buf && (ret = alloc_pic(cur, d->w, d->h))) return ret; - ret = svac3_patch_decode(hls, rbsp, rl, &cur->f, NULL, cur->tmv, &d->st); - if (ret) + if ((ret = svac3_picture_begin(hls, &cur->f, NULL, cur->tmv, &d->open))) return ret; - for (int i = 0; i < LIB_SLOTS; i++) - d->lib[i].used = 0; - cur->used = 1; - cur->lib_index = index; - cur->doi = doi; - cur->poi = poi; - d->last_poi = poi; - *out = &cur->f; + d->open_slot = cur; + d->open_lib = 1; + d->open_index = hls->ph.library_picture_index; return SVAC3_OK; } -static int decode_picture(svac3_dec_t *d, const uint8_t *rbsp, size_t rl, - const svac3_frame_t **out) +/* Begin a picture at its first patch: DOI and POI, reference lists, a slot */ +static int start_picture(svac3_dec_t *d) { svac3_hls_t *hls = &d->hls; const svac3_sps_t *sps = &hls->sps; - int ret = svac3_patch_supported(hls); - if (ret) - return ret; + int ret; const int w = sps->pic_width_in_luma, h = sps->pic_height_in_luma; if (w != d->w || h != d->h) { @@ -232,8 +240,10 @@ static int decode_picture(svac3_dec_t *d, const uint8_t *rbsp, size_t rl, } const int64_t doi = doi7 + 256 * d->cycle; const int64_t poi = doi + hls->ph.picture_output_delay; + d->open_doi = doi; + d->open_poi = poi; if (sps->library_stream_flag) - return decode_library(d, rbsp, rl, doi, poi, out); + return start_library(d); /* reference lists, and the pictures either of them keeps */ ref_t list[2][SVAC3_MAX_REFS]; @@ -246,7 +256,7 @@ static int decode_picture(svac3_dec_t *d, const uint8_t *rbsp, size_t rl, } /* which pictures the lists keep; the buffer is pruned only once the * picture has decoded, so a rejected one costs no reference */ - int keep[DPB_SLOTS]; + int *keep = d->open_keep; for (int i = 0; i < DPB_SLOTS; i++) { const pic_t *p = &d->pic[i]; keep[i] = 0; @@ -255,20 +265,20 @@ static int decode_picture(svac3_dec_t *d, const uint8_t *rbsp, size_t rl, keep[i] |= p->used && !list[l][j].lib && p->doi == list[l][j].key; } - svac3_refs_t refs; - memset(&refs, 0, sizeof(refs)); + svac3_refs_t *refs = &d->open_refs; + memset(refs, 0, sizeof(*refs)); if (hls->ph.picture_coding_type == 2) { - refs.num = nlist[0]; + refs->num = nlist[0]; for (int j = 0; j < nlist[0]; j++) { const ref_t *e = &list[0][j]; const pic_t *r = e->lib ? find_lib(d, e->key) : find_doi(d, e->key); if (!r) return SVAC3_ERR_CORRUPT; /* reference not decoded */ - refs.ref[j] = &r->f; + refs->ref[j] = &r->f; /* a library reference's DistanceIndex is 2 * (POI - 1) (5.4.3.3) */ - refs.dist[j] = (int)clamp_dist(e->lib ? 2 : 2 * (poi - r->poi)); + refs->dist[j] = (int)clamp_dist(e->lib ? 2 : 2 * (poi - r->poi)); if (j == 0) - refs.col = r->tmv; + refs->col = r->tmv; } } @@ -286,18 +296,67 @@ static int decode_picture(svac3_dec_t *d, const uint8_t *rbsp, size_t rl, cur->used = 0; /* its picture is about to be overwritten */ if (!cur->buf && (ret = alloc_pic(cur, w, h))) return ret; - - ret = svac3_patch_decode(hls, rbsp, rl, &cur->f, &refs, cur->tmv, &d->st); - if (ret) + if ((ret = svac3_picture_begin(hls, &cur->f, refs, cur->tmv, &d->open))) return ret; - for (int i = 0; i < DPB_SLOTS; i++) - d->pic[i].used = keep[i]; + d->open_slot = cur; + d->open_lib = 0; + return SVAC3_OK; +} + +/* The picture's last patch has decoded: the buffer takes it */ +static void commit(svac3_dec_t *d, const svac3_frame_t **out) +{ + pic_t *cur = d->open_slot; + if (d->open_lib) { + for (int i = 0; i < LIB_SLOTS; i++) + d->lib[i].used = 0; + cur->lib_index = d->open_index; + } else { + for (int i = 0; i < DPB_SLOTS; i++) + d->pic[i].used = d->open_keep[i]; + } cur->used = 1; - cur->doi = doi; - cur->poi = poi; - d->last_poi = poi; + cur->doi = d->open_doi; + cur->poi = d->open_poi; + d->last_poi = d->open_poi; *out = &cur->f; - return SVAC3_OK; +} + +/* One patch NAL unit. A picture that loses patches is dropped, and the + * unit that shows it (the next picture's first patch, or a patch out of + * order) returns SVAC3_ERR_CORRUPT. */ +static int decode_patch_nal(svac3_dec_t *d, const uint8_t *rbsp, size_t rl, + const svac3_frame_t **out) +{ + svac3_hls_t *hls = &d->hls; + int ret = svac3_patch_supported(hls), lost = 0; + if (ret) + return ret; + + if (hls->patch.patch_index == 0) { + if (d->open) { + abandon(d); + lost = 1; + } + if ((ret = start_picture(d))) + return ret; + } else if (!d->open) { + return SVAC3_ERR_CORRUPT; /* its picture's first patch was lost */ + } + + ret = svac3_picture_patch(d->open, hls, rbsp, rl, &d->st); + if (ret < 0) { + abandon(d); + return ret; + } + if (ret == 1) { + svac3_picture_t *pic = d->open; + d->open = NULL; + if ((ret = svac3_picture_end(pic, hls))) + return ret; + commit(d, out); + } + return lost ? SVAC3_ERR_CORRUPT : SVAC3_OK; } int svac3_dec_nal(svac3_dec_t *d, const uint8_t *nal, size_t len, const svac3_frame_t **out) @@ -305,11 +364,19 @@ int svac3_dec_nal(svac3_dec_t *d, const uint8_t *nal, size_t len, const svac3_fr *out = NULL; if (len < 2) return SVAC3_ERR_INVALID; + /* a picture still missing patches when a header that replaces its + * parameters arrives is lost */ + const unsigned t = nal[1] & 31u; + int lost = 0; + if (d->open && (t == SVAC3_NAL_SPS || t == SVAC3_NAL_PPS || t == SVAC3_NAL_PIC_HEAD)) { + abandon(d); + lost = 1; + } int ret = svac3_parse_nal(&d->hls, nal, len, 0, NULL); if (ret) return ret; - if (!svac3_nal_is_patch(nal[1] & 31u)) - return SVAC3_OK; + if (!svac3_nal_is_patch(t)) + return lost ? SVAC3_ERR_CORRUPT : SVAC3_OK; if (len - 2 > d->rbsp_cap) { uint8_t *b = realloc(d->rbsp, len - 2); if (!b) @@ -318,5 +385,5 @@ int svac3_dec_nal(svac3_dec_t *d, const uint8_t *nal, size_t len, const svac3_fr d->rbsp_cap = len - 2; } const size_t rl = svac3_unescape(nal + 2, len - 2, d->rbsp); - return decode_picture(d, d->rbsp, rl, out); + return decode_patch_nal(d, d->rbsp, rl, out); } diff --git a/source/svac3/svac3_dec.h b/source/svac3/svac3_dec.h index 212de46..8eb293f 100644 --- a/source/svac3/svac3_dec.h +++ b/source/svac3/svac3_dec.h @@ -25,7 +25,9 @@ void svac3_dec_destroy(svac3_dec_t *d); * svac3_dec_hls()->sps), and stays valid until the next call; otherwise * *out is NULL. Errors are the SVAC3_ERR_* codes: SVAC3_ERR_CORRUPT also * covers a P picture whose reference is not in the buffer, as when - * decoding starts after the I picture. */ + * decoding starts after the I picture, and a unit that reveals that an + * earlier picture lost a patch. Such a unit is itself decoded, and may + * complete a picture: *out is set alongside the error then. */ int svac3_dec_nal(svac3_dec_t *d, const uint8_t *nal, size_t len, const svac3_frame_t **out); /* Drop every decoded picture (after a seek); parameter sets are kept */ diff --git a/source/svac3/svac3_patch.c b/source/svac3/svac3_patch.c index 1b73d10..ade12a4 100644 --- a/source/svac3/svac3_patch.c +++ b/source/svac3/svac3_patch.c @@ -63,6 +63,7 @@ typedef struct { uint8_t ipm; /* IntraLumaPredMode, when intra */ uint8_t qp; /* QPY of the coding unit */ uint8_t cbf; /* the coding unit has coefficients */ + uint8_t patch; /* patch_index of the patch it was decoded in */ int8_t ref; /* L0 reference index, -1 when intra */ int16_t mv[2]; /* L0 motion vector */ uint32_t cu_id; /* which coding unit, for the deblocking filter */ @@ -87,6 +88,11 @@ typedef struct { int scu_stride; scu_t *scu; + /* the patch being decoded, in LCUs: [plx0, plx1) x [ply0, ply1) */ + int cur_patch, plx0, plx1, ply0, ply1; + int num_patches, next_patch; /* patches the picture has, and the one due */ + svac3_tmv_t *tmv_out; + /* scratch for one transform block */ int16_t order[64 * 64]; uint8_t sig[64 * 64]; @@ -151,7 +157,8 @@ static int byp_tu(parser_t *p, int max) return v; } -static const scu_t *scu_at(const parser_t *p, int x, int y) +/* The decoded unit at (x, y) of any patch */ +static const scu_t *scu_any(const parser_t *p, int x, int y) { if (x < 0 || y < 0 || x >= p->pic_w || y >= p->pic_h) return NULL; @@ -159,6 +166,14 @@ static const scu_t *scu_at(const parser_t *p, int x, int y) return s->coded ? s : NULL; } +/* The unit at (x, y) if it is available: decoded, and in the current + * patch, since a neighbour in another patch is not available */ +static const scu_t *scu_at(const parser_t *p, int x, int y) +{ + const scu_t *s = scu_any(p, x, y); + return s && s->patch == p->cur_patch ? s : NULL; +} + static void scu_mark(parser_t *p, int x0, int y0, int w, int h, int skip, int intra) { for (int y = y0; y < y0 + h && y < p->pic_h; y += 4) @@ -170,6 +185,7 @@ static void scu_mark(parser_t *p, int x0, int y0, int w, int h, int skip, int in s->skip = (uint8_t)skip; s->intra = (uint8_t)intra; s->cu_id = p->next_cu_id; + s->patch = (uint8_t)p->cur_patch; } p->next_cu_id++; } @@ -180,7 +196,8 @@ static void scu_mark(parser_t *p, int x0, int y0, int w, int h, int skip, int in static void parse_sao(parser_t *p, int lcu_x, int lcu_y, const uint8_t sao_en[3], svac3_sao_t out[3]) { - const int left = lcu_x > 0, up = lcu_y > 0; + /* merge candidates are LCUs of the same patch */ + const int left = lcu_x > p->plx0, up = lcu_y > p->ply0; int merge = 0; memset(out, 0, sizeof(svac3_sao_t) * 3); @@ -1126,7 +1143,7 @@ static void store_tmv(const parser_t *p, svac3_tmv_t *t) const int x0 = tx << 4, y0 = ty << 4; const int nw = x0 + 16 >= p->pic_w ? p->pic_w - x0 : 16; const int nh = y0 + 16 >= p->pic_h ? p->pic_h - y0 : 16; - const scu_t *s = scu_at(p, x0 + nw / 2, y0 + nh / 2); + const scu_t *s = scu_any(p, x0 + nw / 2, y0 + nh / 2); svac3_tmv_t *o = &t[ty * tw + tx]; memset(o, 0, sizeof(*o)); o->ref = -1; @@ -1181,6 +1198,10 @@ int svac3_patch_supported(const svac3_hls_t *hls) return SVAC3_ERR_UNSUPPORTED; /* run-level coefficients not written yet */ if (!s->stable_patch_flag || !s->uniform_patch_flag) return SVAC3_ERR_UNSUPPORTED; + /* loop filters stopping at patch boundaries are not written: several + * patches need cross_patch_loop_filter_enable_flag */ + if (!s->cross_patch_loop_filter_enable_flag && svac3_picture_patches(hls) != 1) + return SVAC3_ERR_UNSUPPORTED; /* picture_qp and patch_qp are 0..63 for 8-bit video (7-bit fields) */ if (hls->ph.picture_qp > 63 || (!hls->pps.fixed_picture_qp_flag && hls->patch.patch_qp > 63)) return SVAC3_ERR_INVALID; @@ -1199,12 +1220,6 @@ int svac3_patch_supported(const svac3_hls_t *hls) return SVAC3_OK; } -int svac3_patch_parse(const svac3_hls_t *hls, const uint8_t *data, size_t len, - svac3_patch_stats_t *st) -{ - return svac3_patch_decode(hls, data, len, NULL, NULL, NULL, st); -} - /* References for a P picture: enough of them, each the size of frame, with * temporal motion when TMVP needs it */ static int refs_ok(const svac3_refs_t *r, int need, const svac3_frame_t *frame, int tmvp) @@ -1223,26 +1238,45 @@ static int refs_ok(const svac3_refs_t *r, int need, const svac3_frame_t *frame, return !tmvp || r->col; } -int svac3_patch_decode(const svac3_hls_t *hls, const uint8_t *data, size_t len, - svac3_frame_t *frame, const svac3_refs_t *refs, svac3_tmv_t *tmv, - svac3_patch_stats_t *st) +struct svac3_picture { + parser_t p; +}; + +int svac3_picture_patches(const svac3_hls_t *hls) +{ + const svac3_sps_t *s = &hls->sps; + const int lcu = 1 << (s->log2_lcu_size_minus2 + 2); + const uint64_t lw = ((uint64_t)s->pic_width_in_luma + lcu - 1) / lcu; + const uint64_t lh = ((uint64_t)s->pic_height_in_luma + lcu - 1) / lcu; + const uint64_t pw = (uint64_t)s->patch_width_minus1 + 1, ph = (uint64_t)s->patch_height_minus1 + 1; + const uint64_t n = ((lw + pw - 1) / pw) * ((lh + ph - 1) / ph); + return n > 256 ? -1 : (int)n; /* patch_index is 8 bits */ +} + +void svac3_picture_free(svac3_picture_t *pic) +{ + if (!pic) + return; + free(pic->p.sao); + free(pic->p.scu); + free(pic); +} + +int svac3_picture_begin(const svac3_hls_t *hls, svac3_frame_t *frame, const svac3_refs_t *refs, + svac3_tmv_t *tmv, svac3_picture_t **out) { - parser_t *p; int ret = svac3_patch_supported(hls); - memset(st, 0, sizeof(*st)); + *out = NULL; if (ret) return ret; - if (hls->patch.aec_data_offset > len) - return SVAC3_ERR_INVALID; - - p = calloc(1, sizeof(*p)); - if (!p) + svac3_picture_t *pic = calloc(1, sizeof(*pic)); + if (!pic) return SVAC3_ERR_NOMEM; + parser_t *p = &pic->p; const svac3_sps_t *s = p->sps = &hls->sps; p->pps = &hls->pps; p->ph = &hls->ph; - p->st = st; p->pct = hls->ph.picture_coding_type; p->pic_w = s->pic_width_in_luma; p->pic_h = s->pic_height_in_luma; @@ -1255,68 +1289,89 @@ int svac3_patch_decode(const svac3_hls_t *hls, const uint8_t *data, size_t len, p->max_eqt = 1 << (s->log2_max_eqt_size_minus3 + 3); p->max_part_ratio = 1 << (s->log2_max_part_ratio_minus2 + 2); p->max_split = s->max_split_times_minus6 + 6; - p->fixed_qp = hls->pps.fixed_picture_qp_flag ? 1 : hls->patch.fixed_patch_qp_flag; p->cu_delta_qp_flag = hls->pps.cu_delta_qp_flag; p->qp_group_area = 1 << ((hls->pps.cu_qp_group_size_log2_minus3 + 3) * 2); + p->num_patches = svac3_picture_patches(hls); const uint32_t ref_minus1 = hls->ph.num_ref_active_override_flag ? hls->ph.num_ref_active_minus1[0] : s->num_ref_default_active_minus1[0]; - if (ref_minus1 >= SVAC3_MAX_REFS) { - free(p); - return SVAC3_ERR_INVALID; - } - p->num_ref_active0 = (int)ref_minus1 + 1; - if (frame && !frame_ok(frame, s->pic_width_in_luma, s->pic_height_in_luma)) { - free(p); - return SVAC3_ERR_INVALID; - } + ret = SVAC3_OK; + if (p->num_patches < 1) + ret = SVAC3_ERR_INVALID; + else if (ref_minus1 >= SVAC3_MAX_REFS) + ret = SVAC3_ERR_INVALID; + else if (frame && !frame_ok(frame, s->pic_width_in_luma, s->pic_height_in_luma)) + ret = SVAC3_ERR_INVALID; + p->num_ref_active0 = ret ? 1 : (int)ref_minus1 + 1; /* in range once ret is 0 */ p->tmvp = hls->ph.picture_tmvp_enable_flag; - if (frame && p->pct == 2 && !refs_ok(refs, p->num_ref_active0, frame, p->tmvp)) { - free(p); - return SVAC3_ERR_INVALID; + if (!ret && frame && p->pct == 2 && !refs_ok(refs, p->num_ref_active0, frame, p->tmvp)) + ret = SVAC3_ERR_INVALID; + if (ret) { + free(pic); + return ret; } p->frame = frame; p->refs = p->pct == 2 ? refs : NULL; p->recon = frame != NULL; + p->tmv_out = tmv; p->mipf = s->mipf_enable_flag; - p->patch_qp = hls->pps.fixed_picture_qp_flag ? hls->ph.picture_qp : hls->patch.patch_qp; - p->prev_cu_qp = p->lcu_qp = p->patch_qp; p->cb_delta = hls->ph.chroma_quant_param_disable_flag ? 0 : hls->ph.chroma_quant_param_delta_cb; p->cr_delta = hls->ph.chroma_quant_param_disable_flag ? 0 : hls->ph.chroma_quant_param_delta_cr; p->scu_stride = (p->pic_w + 3) / 4; p->scu = calloc((size_t)p->scu_stride * ((p->pic_h + 3) / 4), sizeof(scu_t)); - if (!p->scu) { - free(p); + if (p->scu && p->recon) + p->sao = calloc((size_t)p->lcu_w * p->lcu_h, sizeof(*p->sao)); + if (!p->scu || (p->recon && !p->sao)) { + svac3_picture_free(pic); return SVAC3_ERR_NOMEM; } + *out = pic; + return SVAC3_OK; +} - /* a single patch covering the picture */ - if ((uint64_t)s->patch_width_minus1 + 1 < (uint64_t)p->lcu_w || - (uint64_t)s->patch_height_minus1 + 1 < (uint64_t)p->lcu_h || - hls->patch.patch_index != 0) { - free(p->scu); - free(p); - return SVAC3_ERR_UNSUPPORTED; /* several patches per picture */ - } +/* Decode the patch whose header is hls->patch into the picture. */ +static int decode_patch(parser_t *p, const svac3_hls_t *hls, const uint8_t *data, size_t len, + svac3_patch_stats_t *st) +{ + const svac3_sps_t *s = &hls->sps; + const int k = hls->patch.patch_index; - if (p->recon) { - p->sao = calloc((size_t)p->lcu_w * p->lcu_h, sizeof(*p->sao)); - if (!p->sao) { - free(p->scu); - free(p); - return SVAC3_ERR_NOMEM; - } - } + memset(st, 0, sizeof(*st)); + if (hls->patch.aec_data_offset > len || k >= p->num_patches) + return SVAC3_ERR_INVALID; + p->st = st; + + /* uniform patches in raster order (7.2.4) */ + /* a patch at least as wide (tall) as the picture is one column (row) */ + const uint64_t pw64 = (uint64_t)s->patch_width_minus1 + 1, ph64 = (uint64_t)s->patch_height_minus1 + 1; + const int pw = pw64 > (uint64_t)p->lcu_w ? p->lcu_w : (int)pw64; + const int ph = ph64 > (uint64_t)p->lcu_h ? p->lcu_h : (int)ph64; + const int cols = (p->lcu_w + pw - 1) / pw; + p->cur_patch = k; + p->plx0 = (k % cols) * pw; + p->ply0 = (k / cols) * ph; + p->plx1 = p->plx0 + pw < p->lcu_w ? p->plx0 + pw : p->lcu_w; + p->ply1 = p->ply0 + ph < p->lcu_h ? p->ply0 + ph : p->lcu_h; + + p->fixed_qp = hls->pps.fixed_picture_qp_flag ? 1 : hls->patch.fixed_patch_qp_flag; + p->patch_qp = hls->pps.fixed_picture_qp_flag ? hls->ph.picture_qp : hls->patch.patch_qp; + p->prev_cu_qp = p->lcu_qp = p->patch_qp; + /* a QP group does not continue into the next patch */ + p->num_delta_qp = 0; + p->qp_group_x = p->plx0 * p->lcu_size; + p->qp_group_y = p->ply0 * p->lcu_size; svac3_ctx_init(p->ctx, SVAC3_NUM_CTX); svac3_aec_init(&p->aec, data + hls->patch.aec_data_offset, len - hls->patch.aec_data_offset, s->maec_enable_flag, p->pct); st->bytes_avail = len - hls->patch.aec_data_offset; st->stuffing_ok = 1; - const int n_lcu = p->lcu_w * p->lcu_h; + const int pcols = p->plx1 - p->plx0; + const int n_lcu = pcols * (p->ply1 - p->ply0); int prev_dqp = 0; - for (int l = 0; l < n_lcu; l++) { - const int lx = l % p->lcu_w, ly = l / p->lcu_w; + for (int i = 0; i < n_lcu; i++) { + const int lx = p->plx0 + i % pcols, ly = p->ply0 + i / pcols; + const int l = ly * p->lcu_w + lx; if (!p->fixed_qp && !p->cu_delta_qp_flag) { /* lcu_qp_delta: unary, 5.3.3.3.2.2 / 5.3.3.4.5 */ int b = 0, sym = 0; @@ -1339,7 +1394,7 @@ int svac3_patch_decode(const svac3_hls_t *hls, const uint8_t *data, size_t len, 1, PRED_NO_CONSTRAINT); const int stuff = svac3_aec_stuffing_bit(&p->aec); st->lcus++; - if (stuff != (l == n_lcu - 1)) { + if (stuff != (i == n_lcu - 1)) { if (st->stuffing_ok) st->first_bad_lcu = (unsigned)l; st->stuffing_ok = 0; @@ -1354,19 +1409,75 @@ int svac3_patch_decode(const svac3_hls_t *hls, const uint8_t *data, size_t len, } st->bytes_used = svac3_aec_bytes_read(&p->aec); st->overrun_bits = p->aec.overrun; + return SVAC3_OK; +} - ret = SVAC3_OK; - if (p->frame) { - /* a picture that did not parse to its exact end is not a picture */ - if (!st->stuffing_ok || st->overrun_bits) - ret = SVAC3_ERR_CORRUPT; - else - ret = loop_filters(p, hls); - if (ret == SVAC3_OK && tmv) - store_tmv(p, tmv); - } - free(p->sao); - free(p->scu); - free(p); +int svac3_picture_patch(svac3_picture_t *pic, const svac3_hls_t *hls, const uint8_t *data, + size_t len, svac3_patch_stats_t *st) +{ + parser_t *p = &pic->p; + int ret = svac3_patch_supported(hls); + + memset(st, 0, sizeof(*st)); + if (ret) + return ret; + if (hls->patch.patch_index != p->next_patch) + return SVAC3_ERR_CORRUPT; /* a patch lost or out of order */ + ret = decode_patch(p, hls, data, len, st); + if (ret) + return ret; + /* a patch that did not parse to its exact end is not a patch */ + if (!st->stuffing_ok || st->overrun_bits) + return SVAC3_ERR_CORRUPT; + p->next_patch++; + return p->next_patch == p->num_patches ? 1 : 0; +} + +int svac3_picture_end(svac3_picture_t *pic, const svac3_hls_t *hls) +{ + parser_t *p = &pic->p; + int ret = SVAC3_OK; + + if (p->next_patch != p->num_patches) + ret = SVAC3_ERR_CORRUPT; /* patches missing */ + else if (p->recon) + ret = loop_filters(p, hls); + if (ret == SVAC3_OK && p->tmv_out) + store_tmv(p, p->tmv_out); + svac3_picture_free(pic); + return ret; +} + +int svac3_patch_parse(const svac3_hls_t *hls, const uint8_t *data, size_t len, + svac3_patch_stats_t *st) +{ + svac3_picture_t *pic; + int ret; + + memset(st, 0, sizeof(*st)); + if ((ret = svac3_picture_begin(hls, NULL, NULL, NULL, &pic))) + return ret; + ret = decode_patch(&pic->p, hls, data, len, st); + svac3_picture_free(pic); return ret; } + +int svac3_patch_decode(const svac3_hls_t *hls, const uint8_t *data, size_t len, + svac3_frame_t *frame, const svac3_refs_t *refs, svac3_tmv_t *tmv, + svac3_patch_stats_t *st) +{ + svac3_picture_t *pic; + int ret; + + memset(st, 0, sizeof(*st)); + if (svac3_patch_supported(hls) == SVAC3_OK && svac3_picture_patches(hls) != 1) + return SVAC3_ERR_UNSUPPORTED; /* use svac3_picture_*() */ + if ((ret = svac3_picture_begin(hls, frame, refs, tmv, &pic))) + return ret; + ret = svac3_picture_patch(pic, hls, data, len, st); + if (ret < 0) { + svac3_picture_free(pic); + return ret; + } + return svac3_picture_end(pic, hls); +} diff --git a/source/svac3/svac3_patch.h b/source/svac3/svac3_patch.h index 7eda9e4..e58c5ba 100644 --- a/source/svac3/svac3_patch.h +++ b/source/svac3/svac3_patch.h @@ -31,7 +31,9 @@ typedef struct { /* Returns SVAC3_OK if the SPS/PPS/PH use only tools this parser handles. */ int svac3_patch_supported(const svac3_hls_t *hls); -/* Parse the patch whose header is hls->patch. data/len is the patch RBSP +/* Parse the patch whose header is hls->patch, of any patch_index (a + * neighbour in another patch is unavailable, so a patch parses alone). + * data/len is the patch RBSP * (after emulation prevention removal); the AEC data starts at * hls->patch.aec_data_offset. */ int svac3_patch_parse(const svac3_hls_t *hls, const uint8_t *data, size_t len, @@ -57,7 +59,8 @@ typedef struct { /* Number of svac3_tmv_t a picture of this size stores. */ size_t svac3_tmv_count(int pic_w, int pic_h); -/* As svac3_patch_parse(), and also reconstruct the patch into frame, +/* For a picture that is a single patch: as svac3_patch_parse(), and also + * reconstruct it into frame, * including deblocking and SAO. frame must hold the coded picture in 4:2:0 * (SVAC3_ERR_INVALID otherwise). A P picture needs refs, with at least * NumRefActive[0] pictures of the same size, each with its temporal motion @@ -70,4 +73,31 @@ int svac3_patch_decode(const svac3_hls_t *hls, const uint8_t *data, size_t len, svac3_frame_t *frame, const svac3_refs_t *refs, svac3_tmv_t *tmv, svac3_patch_stats_t *stats); +/* A picture of several patches, decoded one patch at a time: + * svac3_picture_begin() with the parameter sets and picture header in hls + * (and frame, refs and tmv as for svac3_patch_decode()), then + * svac3_picture_patch() for each patch in patch_index order, then + * svac3_picture_end(), which runs the loop filters over the whole picture, + * stores its temporal motion and frees it. hls must stay the same object + * throughout, with only its patch header changing. */ +typedef struct svac3_picture svac3_picture_t; + +/* Number of patches in a picture with this SPS (uniform patches), or -1 */ +int svac3_picture_patches(const svac3_hls_t *hls); + +int svac3_picture_begin(const svac3_hls_t *hls, svac3_frame_t *frame, const svac3_refs_t *refs, + svac3_tmv_t *tmv, svac3_picture_t **pic); + +/* Decode the patch whose header is hls->patch. Returns 1 when it was the + * picture's last patch, 0 when more are due, SVAC3_ERR_CORRUPT for a patch + * out of order or not ending on its last LCU. */ +int svac3_picture_patch(svac3_picture_t *pic, const svac3_hls_t *hls, const uint8_t *data, + size_t len, svac3_patch_stats_t *stats); + +/* Finish and free the picture: SVAC3_ERR_CORRUPT if patches are missing. */ +int svac3_picture_end(svac3_picture_t *pic, const svac3_hls_t *hls); + +/* Free a picture without finishing it. */ +void svac3_picture_free(svac3_picture_t *pic); + #endif /* SVAC3_PATCH_H */ diff --git a/source/svac3/svac3d.c b/source/svac3/svac3d.c index 798b1f6..0c3193b 100644 --- a/source/svac3/svac3d.c +++ b/source/svac3/svac3d.c @@ -58,13 +58,16 @@ int svac3d_decode(svac3d_t *d, const uint8_t *buf, size_t len, svac3d_output_fn return SVAC3D_ERR_ARG; size_t pos = 0, nal_len; const uint8_t *nal; + int first = 0; while (svac3_next_nal(buf, len, &pos, &nal, &nal_len)) { if (!nal_len) continue; /* two start codes in a row */ const svac3_frame_t *f; const int ret = svac3_dec_nal(d->dec, nal, nal_len, &f); - if (ret) - return ret; + if (ret && !first) + first = ret; + /* a unit in error may still complete a picture: one that reveals + * an earlier picture's lost patch */ if (!f || !out) continue; const svac3_hls_t *hls = svac3_dec_hls(d->dec); @@ -80,7 +83,7 @@ int svac3d_decode(svac3d_t *d, const uint8_t *buf, size_t len, svac3d_output_fn } out(opaque, &pic); } - return 0; + return first; } int svac3d_get_info(const svac3d_t *d, svac3d_info_t *info) diff --git a/source/svac3/svac3d.h b/source/svac3/svac3d.h index dff0119..d802ce5 100644 --- a/source/svac3/svac3d.h +++ b/source/svac3/svac3d.h @@ -5,7 +5,7 @@ * draft of GB/T SVAC 3.0 (national standard plan 20243294-T-312). * * Supported: the Main profile (0x30) tool set HiSilicon encoders use, - * 8-bit 4:2:0, I and P pictures, one patch per picture, and displayed + * 8-bit 4:2:0, I and P pictures in one or several uniform patches, and displayed * library pictures with the pictures that reference them. Pictures come out * in decoding order, which for these low-delay streams is display order. * Anything else is refused with SVAC3D_ERR_UNSUPPORTED. @@ -68,9 +68,11 @@ void svac3d_reset(svac3d_t *d); /* Decode an Annex-B buffer (start codes, then NAL units) holding any whole * number of NAL units, typically one access unit. out is called once for - * each picture completed, before the call returns. Decoding stops at the - * first NAL unit in error, whose code is returned; the decoder stays usable - * and later buffers decode normally once their references exist. */ + * each picture completed, before the call returns. A NAL unit in error + * does not stop the others: the first error is returned once the whole + * buffer is decoded, and every picture that did complete has been given + * to out. The decoder stays usable; pictures referencing a lost one are + * refused until the next I picture. */ int svac3d_decode(svac3d_t *d, const uint8_t *buf, size_t len, svac3d_output_fn out, void *opaque); diff --git a/tests/README.md b/tests/README.md index 5b823b0..8362e69 100644 --- a/tests/README.md +++ b/tests/README.md @@ -8,11 +8,13 @@ | `streams/cv608_fixqp22_1080p.svac3` | 1920x1080, first 6 of 30 frames, fixed QP 22/24 (I/P): dense coefficients | the same | | `streams/cv608_fixqp48_1080p.svac3` | 1920x1080, 30 frames, fixed QP 48/50: coarse, mostly skipped | the same | | `streams/cv608_smartp_1080p.svac3` | 1920x1080, first 45 of 60 frames, CBR 2 Mbit/s, GOP 20, SmartP with a 40-frame background interval: library pictures (NAL type 17, a library-stream SPS) every 40 frames, RL pictures (type 14) referencing them every 20, and P pictures with two references (the previous picture and the library picture) | the same | +| `streams/cv608_patch4_1080p.svac3` | 1920x1080, 30 frames, CBR 2 Mbit/s, GOP 15, slice split every 4 LCU rows: 9 patches per picture | the same | +| `streams/cv608_patch2_720p.svac3` | 1280x720, 20 frames, CBR 1 Mbit/s, GOP 10, split every 2 LCU rows: 12 patches per picture | the same | -The first five were captured with a small program that feeds synthetic +The cv608 streams other than the first were captured with a small program that feeds synthetic frames to the encoder through the SDK's video buffer pool, with the encoder's rate-control and GOP modes set as above. The golden hashes of all -six come from the reference decoder. A stream cut short ("first N of M +of them come from the reference decoder. A stream cut short ("first N of M frames") ends on an access-unit boundary, and its hashes are those of the same pictures of the whole stream. diff --git a/tests/golden/cv608_patch2_720p.framemd5 b/tests/golden/cv608_patch2_720p.framemd5 new file mode 100644 index 0000000..7e484cb --- /dev/null +++ b/tests/golden/cv608_patch2_720p.framemd5 @@ -0,0 +1,21 @@ +# per-frame MD5 of I420 1280x720 output (Y then U then V, no padding) +0 155e68cff7390d2e71b68385aa2d9bdc +1 040eb53fb8cdf16c19d763fdf8a0c9c4 +2 ca1fb2e3e7f090c59e3a919ed24da9f5 +3 ecf6fcfcd0d7eb32928846a812cd2115 +4 8f5c1bf08859434673b6ebe8b2130978 +5 cae92c532adb098c9050109b536f4c31 +6 0fa21fd94674288b1266f450c3f801b5 +7 7b700770665bedf105452a2984aa4913 +8 2fa089f977e0e4a60023f156aad17484 +9 274e64a6a67e5f007201e4ca586d1b01 +10 d184da8ce978751bdf0abc9d199717fd +11 12ac5314b9a6551ff3402bc4ee68b285 +12 0da82f234cc62f9110a76d5324c5ec18 +13 79f8221898f636c1728831a143bb1e75 +14 dd39690df29c98823a1fde439b6bcfa5 +15 6a280c2fd660316610e68d17db6bca20 +16 a36f19e886c3e8f97e3c73b98de68234 +17 1ec089ba6d96b5fb18c009fd56ca4e18 +18 17ec6932dbb63e6739889e5579d4542c +19 ea4f981a9fd23b3d4bcfa9fb8748ca21 diff --git a/tests/golden/cv608_patch4_1080p.framemd5 b/tests/golden/cv608_patch4_1080p.framemd5 new file mode 100644 index 0000000..f2daefd --- /dev/null +++ b/tests/golden/cv608_patch4_1080p.framemd5 @@ -0,0 +1,31 @@ +# per-frame MD5 of I420 1920x1080 output (Y then U then V, no padding) +0 e41ad4296e5d5fbf66e816eb93fec626 +1 a9e51a56985c6679c528dab2b9d888ac +2 c8d15e895b7dc4f52661486ed93834f2 +3 0acc6ac4df1724b80ce7a91f6c2a82de +4 d5f590361f90c0f5f045e5262f6724d0 +5 bc1bddce59d36821a5d4cfcb80bf580e +6 0d70cadfe3e95a70e2001b28fa21b011 +7 f1cc3f442559b0d81fcb9637580108d4 +8 1dd827500af38fc49f64bc7a64251ff9 +9 876df351d83d574baeab8ae5d456a3b1 +10 bf7d8adf935da7e7e741c1758566ce15 +11 bd1798054232a03c68f8482d8020a22f +12 09232b974933ef05a203965ab64ddf54 +13 2a4df41dbd57831857740de2ede59ad1 +14 4e4c988a3e3ac23ad1141816902499ee +15 0cbf4a2b07df1a0389fff1fb3b0d843b +16 b6c528c2a5ef474f41b752c80d75ccd5 +17 2f846eac808de9b5b6f968d2eb8a1b86 +18 37d55f280e9fb15ccab2c44ac01d47b5 +19 3a039e0b45631b33318f0fffc7d997dd +20 6c017e8b2488ea6fb262173e8fdd1e2f +21 31b43787cf94a57b1558690736681ab8 +22 b0f4091e38317e7428c2035478cec8c1 +23 c5bbac39874a99fc6fe5f4d25834a821 +24 5614dc6e8097bda5b16989cb68618972 +25 b33a94c38893d158c1b7e00b043402b1 +26 3d84893dfa159b0f80db05d982afa449 +27 1020b8690d9c1a60dec6352f07917b6f +28 5180c798f6b2647058f960a5076e346c +29 9939564b0f0b55ceaebaf4ef30c14311 diff --git a/tests/streams/cv608_patch2_720p.svac3 b/tests/streams/cv608_patch2_720p.svac3 new file mode 100644 index 0000000..66e7b56 Binary files /dev/null and b/tests/streams/cv608_patch2_720p.svac3 differ diff --git a/tests/streams/cv608_patch4_1080p.svac3 b/tests/streams/cv608_patch4_1080p.svac3 new file mode 100644 index 0000000..71291ca Binary files /dev/null and b/tests/streams/cv608_patch4_1080p.svac3 differ diff --git a/tests/test_api.c b/tests/test_api.c index 9a52616..4698c0b 100644 --- a/tests/test_api.c +++ b/tests/test_api.c @@ -51,10 +51,83 @@ static void out(void *opaque, const svac3d_picture_t *pic) s->last_poi = pic->poi; } +/* A stream of several patches per picture, with the first picture's last + * patch removed, in one buffer: the call reports the loss and still gives + * every picture the units complete, as the internal decoder fed the same + * units one by one, carrying on past errors, does. */ +static void lost_patch_checks(const uint8_t *buf, size_t len) +{ + size_t pos = 0, nal_len, cut0 = 0, cut1 = 0; + const uint8_t *nal; + int pictures = 0; + static svac3_hls_t h; + memset(&h, 0, sizeof(h)); + while (svac3_next_nal(buf, len, &pos, &nal, &nal_len)) { + if (nal_len < 2 || svac3_parse_nal(&h, nal, nal_len, 0, NULL)) + continue; + if (!svac3_nal_is_patch(nal[1] & 31u)) + continue; + if (h.patch.patch_index == 0) + pictures++; + if (pictures == 1) { /* remember the last patch of picture 0 */ + cut0 = (size_t)(nal - buf) - 3; + cut1 = pos; + } + } + CHECK(pictures > 2 && cut1 > cut0); + uint8_t *lost = malloc(len); + if (!lost) + return; + memcpy(lost, buf, cut0); + memcpy(lost + cut0, buf + cut1, len - cut1); + const size_t lost_len = len - (cut1 - cut0); + + sink_t want = { 0xcbf29ce484222325ULL, 0, 0, 0 }; + { + svac3_dec_t *d = svac3_dec_create(); + pos = 0; + while (svac3_next_nal(lost, lost_len, &pos, &nal, &nal_len)) { + const svac3_frame_t *f; + if (!nal_len) + continue; + svac3_dec_nal(d, nal, nal_len, &f); + if (!f) + continue; + const svac3_sps_t *s = &svac3_dec_hls(d)->sps; + svac3d_picture_t pic = { .width = s->horizontal_size, .height = s->vertical_size }; + for (int c = 0; c < 3; c++) { + pic.data[c] = f->plane[c]; + pic.linesize[c] = f->stride[c]; + } + out(&want, &pic); + } + svac3_dec_destroy(d); + } + svac3d_t *d = svac3d_open(); + sink_t got = { 0xcbf29ce484222325ULL, 0, 0, 0 }; + CHECK(svac3d_decode(d, lost, lost_len, out, &got) == SVAC3D_ERR_CORRUPT); + CHECK(got.pics > 0 && got.pics < pictures && got.pics == want.pics && got.hash == want.hash); + svac3d_close(d); + free(lost); +} + int main(int argc, char **argv) { + if (argc == 3 && !strcmp(argv[1], "-lost")) { + size_t len; + uint8_t *buf = read_file(argv[2], &len); + if (!buf) + return 1; + lost_patch_checks(buf, len); + free(buf); + if (failures) + fprintf(stderr, "%d check(s) failed\n", failures); + else + printf("all checks passed\n"); + return failures ? 1 : 0; + } if (argc != 2) { - fprintf(stderr, "usage: %s stream.svac3\n", argv[0]); + fprintf(stderr, "usage: %s [-lost] stream.svac3\n", argv[0]); return 2; } size_t len; diff --git a/tests/test_dec.c b/tests/test_dec.c index f83d9ea..1af06a2 100644 --- a/tests/test_dec.c +++ b/tests/test_dec.c @@ -5,6 +5,7 @@ * usage: test_dec (a stream from tests/streams: SPS, PPS, * an IDR picture, then P pictures each referencing the one before) * test_dec -library (a stream with library pictures) + * test_dec -patches (a stream of several patches per picture) */ #include #include @@ -94,11 +95,73 @@ static void library_checks(const unit_t *u, int n) svac3_dec_destroy(d); } +/* A stream of several patches per picture: all of it decodes; a picture + * missing a patch is dropped, and the unit that shows the loss says so. */ +static void patch_checks(const unit_t *u, int n) +{ + /* each patch unit's patch_index, from a header parse of its own */ + static svac3_hls_t h; + int pictures = 0, second = -1, p1 = -1, last0 = -1, npatch0 = 0, ph2 = -1; + memset(&h, 0, sizeof(h)); + for (int i = 0; i < n; i++) { + if (svac3_parse_nal(&h, u[i].p, u[i].len, 0, NULL) || !u[i].patch) + continue; + if (h.patch.patch_index == 0 && ++pictures == 2 && second < 0) + second = i; + if (pictures == 1) { + npatch0++; + last0 = i; + if (h.patch.patch_index == 1) + p1 = i; + } + } + for (int i = last0 + 1; i < second; i++) + if ((u[i].p[1] & 31u) == SVAC3_NAL_PIC_HEAD) + ph2 = i; + CHECK(npatch0 > 2 && p1 > 0 && second > last0 && ph2 > last0); + if (npatch0 <= 2 || p1 < 0 || ph2 < 0) + return; + + svac3_dec_t *d = svac3_dec_create(); + int pics = 0; + CHECK(feed(d, u, 0, n, &pics) == 0 && pics == pictures); + + /* without its second patch, the first picture is not output: each + * later patch of it is out of order or has no picture to go to */ + svac3_dec_reset(d); + int errors = 0, out = 0; + for (int i = 0; i < second; i++) { + const svac3_frame_t *f; + if (i == p1) + continue; + errors += svac3_dec_nal(d, u[i].p, u[i].len, &f) == SVAC3_ERR_CORRUPT; + out += f != NULL; + } + CHECK(errors == npatch0 - 2 && out == 0); + + /* without its last patch, the next picture header ends it */ + svac3_dec_reset(d); + errors = out = 0; + for (int i = 0; i <= ph2; i++) { + const svac3_frame_t *f; + if (i == last0) + continue; + const int r = svac3_dec_nal(d, u[i].p, u[i].len, &f); + errors += r != 0; + out += f != NULL; + if (r) + CHECK(r == SVAC3_ERR_CORRUPT && i == ph2); + } + CHECK(errors == 1 && out == 0); + svac3_dec_destroy(d); +} + int main(int argc, char **argv) { const int library = argc == 3 && !strcmp(argv[1], "-library"); - if (argc != 2 && !library) { - fprintf(stderr, "usage: %s [-library] stream.svac3\n", argv[0]); + const int patches = argc == 3 && !strcmp(argv[1], "-patches"); + if (argc != 2 && !library && !patches) { + fprintf(stderr, "usage: %s [-library|-patches] stream.svac3\n", argv[0]); return 2; } size_t len; @@ -113,8 +176,11 @@ int main(int argc, char **argv) if (nal_len >= 2) u[n++] = (unit_t){ nal, nal_len, svac3_nal_is_patch(nal[1] & 31u) }; - if (library) { - library_checks(u, n); + if (library || patches) { + if (library) + library_checks(u, n); + else + patch_checks(u, n); free(u); free(buf); if (failures) diff --git a/tests/test_patch.c b/tests/test_patch.c index dbf56cc..ee54f10 100644 --- a/tests/test_patch.c +++ b/tests/test_patch.c @@ -69,11 +69,29 @@ int main(int argc, char **argv) CHECK(svac3_patch_parse(t, rbsp, rl, &st) == SVAC3_ERR_INVALID); /* huge patch dimensions are compared without overflow and still accepted - * as covering the picture; small ones mean several patches */ + * as covering the picture */ *t = *hls; t->sps.patch_width_minus1 = 0x7fffffff; t->sps.patch_height_minus1 = 0xffffffffu; - CHECK(svac3_patch_parse(t, rbsp, rl, &st) == SVAC3_OK); + CHECK(svac3_patch_parse(t, rbsp, rl, &st) == SVAC3_OK && st.stuffing_ok); + CHECK(svac3_picture_patches(t) == 1); + /* small ones mean several patches: this patch's data then fills the + * first, one LCU wide, and does not end there */ *t = *hls; t->sps.patch_width_minus1 = 0; + CHECK(svac3_picture_patches(t) == 60 && svac3_patch_supported(t) == SVAC3_OK); + CHECK(svac3_patch_parse(t, rbsp, rl, &st) == SVAC3_OK && !st.stuffing_ok && st.lcus == 34); + /* the single-patch call refuses them; svac3_picture_*() decodes them */ + { + uint8_t one = 0; + svac3_frame_t none = { .plane = { &one, &one, &one } }; + CHECK(svac3_patch_decode(t, rbsp, rl, &none, NULL, NULL, &st) == SVAC3_ERR_UNSUPPORTED); + } + /* a patch_index past the picture's patches */ + t->patch.patch_index = 60; + CHECK(svac3_patch_parse(t, rbsp, rl, &st) == SVAC3_ERR_INVALID); + /* several patches whose loop filters stop at patch boundaries */ + *t = *hls; t->sps.patch_width_minus1 = 0; t->sps.cross_patch_loop_filter_enable_flag = 0; CHECK(svac3_patch_parse(t, rbsp, rl, &st) == SVAC3_ERR_UNSUPPORTED); + *t = *hls; t->sps.cross_patch_loop_filter_enable_flag = 0; + CHECK(svac3_patch_parse(t, rbsp, rl, &st) == SVAC3_OK); /* one patch: nothing to stop at */ /* patch kinds other than display-picture patches */ *t = *hls; t->patch.nal_unit_type = SVAC3_NAL_CRR_PRIVACY;