| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * V4L2 H264 helpers. |
| * |
| * Copyright (C) 2019 Collabora, Ltd. |
| * |
| * Author: Boris Brezillon <boris.brezillon@collabora.com> |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/sort.h> |
| |
| #include <media/v4l2-h264.h> |
| |
| /* |
| * Size of the tempory buffer allocated when printing reference lists. The |
| * output will be truncated if the size is too small. |
| */ |
| static const int tmp_str_size = 1024; |
| |
| /** |
| * v4l2_h264_init_reflist_builder() - Initialize a P/B0/B1 reference list |
| * builder |
| * |
| * @b: the builder context to initialize |
| * @dec_params: decode parameters control |
| * @sps: SPS control |
| * @dpb: DPB to use when creating the reference list |
| */ |
| void |
| v4l2_h264_init_reflist_builder(struct v4l2_h264_reflist_builder *b, |
| const struct v4l2_ctrl_h264_decode_params *dec_params, |
| const struct v4l2_ctrl_h264_sps *sps, |
| const struct v4l2_h264_dpb_entry dpb[V4L2_H264_NUM_DPB_ENTRIES]) |
| { |
| int cur_frame_num, max_frame_num; |
| unsigned int i; |
| |
| max_frame_num = 1 << (sps->log2_max_frame_num_minus4 + 4); |
| cur_frame_num = dec_params->frame_num; |
| |
| memset(b, 0, sizeof(*b)); |
| if (!(dec_params->flags & V4L2_H264_DECODE_PARAM_FLAG_FIELD_PIC)) { |
| b->cur_pic_order_count = min(dec_params->bottom_field_order_cnt, |
| dec_params->top_field_order_cnt); |
| b->cur_pic_fields = V4L2_H264_FRAME_REF; |
| } else if (dec_params->flags & V4L2_H264_DECODE_PARAM_FLAG_BOTTOM_FIELD) { |
| b->cur_pic_order_count = dec_params->bottom_field_order_cnt; |
| b->cur_pic_fields = V4L2_H264_BOTTOM_FIELD_REF; |
| } else { |
| b->cur_pic_order_count = dec_params->top_field_order_cnt; |
| b->cur_pic_fields = V4L2_H264_TOP_FIELD_REF; |
| } |
| |
| for (i = 0; i < V4L2_H264_NUM_DPB_ENTRIES; i++) { |
| if (!(dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE)) |
| continue; |
| |
| if (dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM) |
| b->refs[i].longterm = true; |
| |
| /* |
| * Handle frame_num wraparound as described in section |
| * '8.2.4.1 Decoding process for picture numbers' of the spec. |
| * For long term references, frame_num is set to |
| * long_term_frame_idx which requires no wrapping. |
| */ |
| if (!b->refs[i].longterm && dpb[i].frame_num > cur_frame_num) |
| b->refs[i].frame_num = (int)dpb[i].frame_num - |
| max_frame_num; |
| else |
| b->refs[i].frame_num = dpb[i].frame_num; |
| |
| b->refs[i].top_field_order_cnt = dpb[i].top_field_order_cnt; |
| b->refs[i].bottom_field_order_cnt = dpb[i].bottom_field_order_cnt; |
| |
| if (b->cur_pic_fields == V4L2_H264_FRAME_REF) { |
| u8 fields = V4L2_H264_FRAME_REF; |
| |
| b->unordered_reflist[b->num_valid].index = i; |
| b->unordered_reflist[b->num_valid].fields = fields; |
| b->num_valid++; |
| continue; |
| } |
| |
| if (dpb[i].fields & V4L2_H264_TOP_FIELD_REF) { |
| u8 fields = V4L2_H264_TOP_FIELD_REF; |
| |
| b->unordered_reflist[b->num_valid].index = i; |
| b->unordered_reflist[b->num_valid].fields = fields; |
| b->num_valid++; |
| } |
| |
| if (dpb[i].fields & V4L2_H264_BOTTOM_FIELD_REF) { |
| u8 fields = V4L2_H264_BOTTOM_FIELD_REF; |
| |
| b->unordered_reflist[b->num_valid].index = i; |
| b->unordered_reflist[b->num_valid].fields = fields; |
| b->num_valid++; |
| } |
| } |
| |
| for (i = b->num_valid; i < ARRAY_SIZE(b->unordered_reflist); i++) |
| b->unordered_reflist[i].index = i; |
| } |
| EXPORT_SYMBOL_GPL(v4l2_h264_init_reflist_builder); |
| |
| static s32 v4l2_h264_get_poc(const struct v4l2_h264_reflist_builder *b, |
| const struct v4l2_h264_reference *ref) |
| { |
| switch (ref->fields) { |
| case V4L2_H264_FRAME_REF: |
| return min(b->refs[ref->index].top_field_order_cnt, |
| b->refs[ref->index].bottom_field_order_cnt); |
| case V4L2_H264_TOP_FIELD_REF: |
| return b->refs[ref->index].top_field_order_cnt; |
| case V4L2_H264_BOTTOM_FIELD_REF: |
| return b->refs[ref->index].bottom_field_order_cnt; |
| } |
| |
| /* not reached */ |
| return 0; |
| } |
| |
| static int v4l2_h264_p_ref_list_cmp(const void *ptra, const void *ptrb, |
| const void *data) |
| { |
| const struct v4l2_h264_reflist_builder *builder = data; |
| u8 idxa, idxb; |
| |
| idxa = ((struct v4l2_h264_reference *)ptra)->index; |
| idxb = ((struct v4l2_h264_reference *)ptrb)->index; |
| |
| if (WARN_ON(idxa >= V4L2_H264_NUM_DPB_ENTRIES || |
| idxb >= V4L2_H264_NUM_DPB_ENTRIES)) |
| return 1; |
| |
| if (builder->refs[idxa].longterm != builder->refs[idxb].longterm) { |
| /* Short term pics first. */ |
| if (!builder->refs[idxa].longterm) |
| return -1; |
| else |
| return 1; |
| } |
| |
| /* |
| * For frames, short term pics are in descending pic num order and long |
| * term ones in ascending order. For fields, the same direction is used |
| * but with frame_num (wrapped). For frames, the value of pic_num and |
| * frame_num are the same (see formula (8-28) and (8-29)). For this |
| * reason we can use frame_num only and share this function between |
| * frames and fields reflist. |
| */ |
| if (!builder->refs[idxa].longterm) |
| return builder->refs[idxb].frame_num < |
| builder->refs[idxa].frame_num ? |
| -1 : 1; |
| |
| return builder->refs[idxa].frame_num < builder->refs[idxb].frame_num ? |
| -1 : 1; |
| } |
| |
| static int v4l2_h264_b0_ref_list_cmp(const void *ptra, const void *ptrb, |
| const void *data) |
| { |
| const struct v4l2_h264_reflist_builder *builder = data; |
| s32 poca, pocb; |
| u8 idxa, idxb; |
| |
| idxa = ((struct v4l2_h264_reference *)ptra)->index; |
| idxb = ((struct v4l2_h264_reference *)ptrb)->index; |
| |
| if (WARN_ON(idxa >= V4L2_H264_NUM_DPB_ENTRIES || |
| idxb >= V4L2_H264_NUM_DPB_ENTRIES)) |
| return 1; |
| |
| if (builder->refs[idxa].longterm != builder->refs[idxb].longterm) { |
| /* Short term pics first. */ |
| if (!builder->refs[idxa].longterm) |
| return -1; |
| else |
| return 1; |
| } |
| |
| /* Long term pics in ascending frame num order. */ |
| if (builder->refs[idxa].longterm) |
| return builder->refs[idxa].frame_num < |
| builder->refs[idxb].frame_num ? |
| -1 : 1; |
| |
| poca = v4l2_h264_get_poc(builder, ptra); |
| pocb = v4l2_h264_get_poc(builder, ptrb); |
| |
| /* |
| * Short term pics with POC < cur POC first in POC descending order |
| * followed by short term pics with POC > cur POC in POC ascending |
| * order. |
| */ |
| if ((poca < builder->cur_pic_order_count) != |
| (pocb < builder->cur_pic_order_count)) |
| return poca < pocb ? -1 : 1; |
| else if (poca < builder->cur_pic_order_count) |
| return pocb < poca ? -1 : 1; |
| |
| return poca < pocb ? -1 : 1; |
| } |
| |
| static int v4l2_h264_b1_ref_list_cmp(const void *ptra, const void *ptrb, |
| const void *data) |
| { |
| const struct v4l2_h264_reflist_builder *builder = data; |
| s32 poca, pocb; |
| u8 idxa, idxb; |
| |
| idxa = ((struct v4l2_h264_reference *)ptra)->index; |
| idxb = ((struct v4l2_h264_reference *)ptrb)->index; |
| |
| if (WARN_ON(idxa >= V4L2_H264_NUM_DPB_ENTRIES || |
| idxb >= V4L2_H264_NUM_DPB_ENTRIES)) |
| return 1; |
| |
| if (builder->refs[idxa].longterm != builder->refs[idxb].longterm) { |
| /* Short term pics first. */ |
| if (!builder->refs[idxa].longterm) |
| return -1; |
| else |
| return 1; |
| } |
| |
| /* Long term pics in ascending frame num order. */ |
| if (builder->refs[idxa].longterm) |
| return builder->refs[idxa].frame_num < |
| builder->refs[idxb].frame_num ? |
| -1 : 1; |
| |
| poca = v4l2_h264_get_poc(builder, ptra); |
| pocb = v4l2_h264_get_poc(builder, ptrb); |
| |
| /* |
| * Short term pics with POC > cur POC first in POC ascending order |
| * followed by short term pics with POC < cur POC in POC descending |
| * order. |
| */ |
| if ((poca < builder->cur_pic_order_count) != |
| (pocb < builder->cur_pic_order_count)) |
| return pocb < poca ? -1 : 1; |
| else if (poca < builder->cur_pic_order_count) |
| return pocb < poca ? -1 : 1; |
| |
| return poca < pocb ? -1 : 1; |
| } |
| |
| /* |
| * The references need to be reordered so that references are alternating |
| * between top and bottom field references starting with the current picture |
| * parity. This has to be done for short term and long term references |
| * separately. |
| */ |
| static void reorder_field_reflist(const struct v4l2_h264_reflist_builder *b, |
| struct v4l2_h264_reference *reflist) |
| { |
| struct v4l2_h264_reference tmplist[V4L2_H264_REF_LIST_LEN]; |
| u8 lt, i = 0, j = 0, k = 0; |
| |
| memcpy(tmplist, reflist, sizeof(tmplist[0]) * b->num_valid); |
| |
| for (lt = 0; lt <= 1; lt++) { |
| do { |
| for (; i < b->num_valid && b->refs[tmplist[i].index].longterm == lt; i++) { |
| if (tmplist[i].fields == b->cur_pic_fields) { |
| reflist[k++] = tmplist[i++]; |
| break; |
| } |
| } |
| |
| for (; j < b->num_valid && b->refs[tmplist[j].index].longterm == lt; j++) { |
| if (tmplist[j].fields != b->cur_pic_fields) { |
| reflist[k++] = tmplist[j++]; |
| break; |
| } |
| } |
| } while ((i < b->num_valid && b->refs[tmplist[i].index].longterm == lt) || |
| (j < b->num_valid && b->refs[tmplist[j].index].longterm == lt)); |
| } |
| } |
| |
| static char ref_type_to_char(u8 ref_type) |
| { |
| switch (ref_type) { |
| case V4L2_H264_FRAME_REF: |
| return 'f'; |
| case V4L2_H264_TOP_FIELD_REF: |
| return 't'; |
| case V4L2_H264_BOTTOM_FIELD_REF: |
| return 'b'; |
| } |
| |
| return '?'; |
| } |
| |
| static const char *format_ref_list_p(const struct v4l2_h264_reflist_builder *builder, |
| struct v4l2_h264_reference *reflist, |
| char **out_str) |
| { |
| int n = 0, i; |
| |
| *out_str = kmalloc(tmp_str_size, GFP_KERNEL); |
| if (!(*out_str)) |
| return NULL; |
| |
| n += snprintf(*out_str + n, tmp_str_size - n, "|"); |
| |
| for (i = 0; i < builder->num_valid; i++) { |
| /* this is pic_num for frame and frame_num (wrapped) for field, |
| * but for frame pic_num is equal to frame_num (wrapped). |
| */ |
| int frame_num = builder->refs[reflist[i].index].frame_num; |
| bool longterm = builder->refs[reflist[i].index].longterm; |
| |
| n += scnprintf(*out_str + n, tmp_str_size - n, "%i%c%c|", |
| frame_num, longterm ? 'l' : 's', |
| ref_type_to_char(reflist[i].fields)); |
| } |
| |
| return *out_str; |
| } |
| |
| static void print_ref_list_p(const struct v4l2_h264_reflist_builder *builder, |
| struct v4l2_h264_reference *reflist) |
| { |
| char *buf = NULL; |
| |
| pr_debug("ref_pic_list_p (cur_poc %u%c) %s\n", |
| builder->cur_pic_order_count, |
| ref_type_to_char(builder->cur_pic_fields), |
| format_ref_list_p(builder, reflist, &buf)); |
| |
| kfree(buf); |
| } |
| |
| static const char *format_ref_list_b(const struct v4l2_h264_reflist_builder *builder, |
| struct v4l2_h264_reference *reflist, |
| char **out_str) |
| { |
| int n = 0, i; |
| |
| *out_str = kmalloc(tmp_str_size, GFP_KERNEL); |
| if (!(*out_str)) |
| return NULL; |
| |
| n += snprintf(*out_str + n, tmp_str_size - n, "|"); |
| |
| for (i = 0; i < builder->num_valid; i++) { |
| int frame_num = builder->refs[reflist[i].index].frame_num; |
| u32 poc = v4l2_h264_get_poc(builder, reflist + i); |
| bool longterm = builder->refs[reflist[i].index].longterm; |
| |
| n += scnprintf(*out_str + n, tmp_str_size - n, "%i%c%c|", |
| longterm ? frame_num : poc, |
| longterm ? 'l' : 's', |
| ref_type_to_char(reflist[i].fields)); |
| } |
| |
| return *out_str; |
| } |
| |
| static void print_ref_list_b(const struct v4l2_h264_reflist_builder *builder, |
| struct v4l2_h264_reference *reflist, u8 list_num) |
| { |
| char *buf = NULL; |
| |
| pr_debug("ref_pic_list_b%u (cur_poc %u%c) %s", |
| list_num, builder->cur_pic_order_count, |
| ref_type_to_char(builder->cur_pic_fields), |
| format_ref_list_b(builder, reflist, &buf)); |
| |
| kfree(buf); |
| } |
| |
| /** |
| * v4l2_h264_build_p_ref_list() - Build the P reference list |
| * |
| * @builder: reference list builder context |
| * @reflist: 32 sized array used to store the P reference list. Each entry |
| * is a v4l2_h264_reference structure |
| * |
| * This functions builds the P reference lists. This procedure is describe in |
| * section '8.2.4 Decoding process for reference picture lists construction' |
| * of the H264 spec. This function can be used by H264 decoder drivers that |
| * need to pass a P reference list to the hardware. |
| */ |
| void |
| v4l2_h264_build_p_ref_list(const struct v4l2_h264_reflist_builder *builder, |
| struct v4l2_h264_reference *reflist) |
| { |
| memcpy(reflist, builder->unordered_reflist, |
| sizeof(builder->unordered_reflist[0]) * builder->num_valid); |
| sort_r(reflist, builder->num_valid, sizeof(*reflist), |
| v4l2_h264_p_ref_list_cmp, NULL, builder); |
| |
| if (builder->cur_pic_fields != V4L2_H264_FRAME_REF) |
| reorder_field_reflist(builder, reflist); |
| |
| print_ref_list_p(builder, reflist); |
| } |
| EXPORT_SYMBOL_GPL(v4l2_h264_build_p_ref_list); |
| |
| /** |
| * v4l2_h264_build_b_ref_lists() - Build the B0/B1 reference lists |
| * |
| * @builder: reference list builder context |
| * @b0_reflist: 32 sized array used to store the B0 reference list. Each entry |
| * is a v4l2_h264_reference structure |
| * @b1_reflist: 32 sized array used to store the B1 reference list. Each entry |
| * is a v4l2_h264_reference structure |
| * |
| * This functions builds the B0/B1 reference lists. This procedure is described |
| * in section '8.2.4 Decoding process for reference picture lists construction' |
| * of the H264 spec. This function can be used by H264 decoder drivers that |
| * need to pass B0/B1 reference lists to the hardware. |
| */ |
| void |
| v4l2_h264_build_b_ref_lists(const struct v4l2_h264_reflist_builder *builder, |
| struct v4l2_h264_reference *b0_reflist, |
| struct v4l2_h264_reference *b1_reflist) |
| { |
| memcpy(b0_reflist, builder->unordered_reflist, |
| sizeof(builder->unordered_reflist[0]) * builder->num_valid); |
| sort_r(b0_reflist, builder->num_valid, sizeof(*b0_reflist), |
| v4l2_h264_b0_ref_list_cmp, NULL, builder); |
| |
| memcpy(b1_reflist, builder->unordered_reflist, |
| sizeof(builder->unordered_reflist[0]) * builder->num_valid); |
| sort_r(b1_reflist, builder->num_valid, sizeof(*b1_reflist), |
| v4l2_h264_b1_ref_list_cmp, NULL, builder); |
| |
| if (builder->cur_pic_fields != V4L2_H264_FRAME_REF) { |
| reorder_field_reflist(builder, b0_reflist); |
| reorder_field_reflist(builder, b1_reflist); |
| } |
| |
| if (builder->num_valid > 1 && |
| !memcmp(b1_reflist, b0_reflist, builder->num_valid)) |
| swap(b1_reflist[0], b1_reflist[1]); |
| |
| print_ref_list_b(builder, b0_reflist, 0); |
| print_ref_list_b(builder, b1_reflist, 1); |
| } |
| EXPORT_SYMBOL_GPL(v4l2_h264_build_b_ref_lists); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_DESCRIPTION("V4L2 H264 Helpers"); |
| MODULE_AUTHOR("Boris Brezillon <boris.brezillon@collabora.com>"); |