diff --git a/src/meson.build b/src/meson.build index 3e8b8f1..c5540ba 100644 --- a/src/meson.build +++ b/src/meson.build @@ -47,7 +47,8 @@ sources = [ 'h264_slice_header.c', 'request_pool.c', 'cap_pool.c', - 'h265.c' + 'h265.c', + 'vp8.c' ] headers = [ @@ -70,7 +71,8 @@ headers = [ 'h264_slice_header.h', 'request_pool.h', 'cap_pool.h', - 'h265.h' + 'h265.h', + 'vp8.h' ] includes = [ diff --git a/src/vp8.c b/src/vp8.c new file mode 100644 index 0000000..cc4abdf --- /dev/null +++ b/src/vp8.c @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2026 Markus Fritsche + * + * fresnel-fourier iter3 Phase 6 commit B: VP8 codec dispatcher + * implemented against V4L2_CID_STATELESS_VP8_FRAME (kernel UAPI + * :1900-1958). Single batched control per + * frame, no init-time device-wide menus (VP8 has no DECODE_MODE/ + * START_CODE — confirmed by Phase 0 V4L2 inventory + Phase 3 + * cross-validator strace). + * + * Reference: FFmpeg libavcodec/v4l2_request_vp8.c (kwiboo branch); + * FFmpeg libavcodec/vaapi_vp8.c (VAAPI source-side + * verification of the field semantics); + * kernel drivers/media/platform/verisilicon/ + * hantro_g1_vp8_dec.c (RK3399 hardware reads + * first_part_header_bits + first_part_size to compute + * MB-data DMA offset). + * + * Phase 5 review amendments incorporated (see phase5_iter3_review.md): + * C1 first_part_header_bits = slice->macroblock_offset + * (NOT 0; kernel reads it unconditionally; same formula as + * v4l2_request_vp8.c uses internally) + * C2 first_part_size = slice->partition_size[0] + + * ((macroblock_offset + 7) / 8) + * (recover total partition size from VAAPI's post-parse + * remainder) + * C3 VAProbabilityBufferType (not VAProbabilityDataBufferType) + * C4 (int8_t) cast (not (s8); kernel-internal typedef not in + * userspace UAPI) + * S3 assert(probability_set) runtime guard (kernel has NO + * coeff_probs default fallback; consumer MUST send + * VAProbabilityBufferType per frame) + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sub license, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "vp8.h" +#include "context.h" +#include "request.h" +#include "surface.h" + +#include +#include +#include + +#include + +#include +#include + +#include "v4l2.h" + +int vp8_set_controls(struct request_data *driver_data, + struct object_context *context_object, + struct object_surface *surface_object) +{ + VAPictureParameterBufferVP8 *picture = + &surface_object->params.vp8.picture; + VASliceParameterBufferVP8 *slice = + &surface_object->params.vp8.slice; + VAIQMatrixBufferVP8 *iqmatrix = + &surface_object->params.vp8.iqmatrix; + VAProbabilityDataBufferVP8 *probability = + &surface_object->params.vp8.probability; + bool iqmatrix_set = surface_object->params.vp8.iqmatrix_set; + bool probability_set = surface_object->params.vp8.probability_set; + + struct v4l2_ctrl_vp8_frame frame; + struct object_surface *last_ref; + struct object_surface *golden_ref; + struct object_surface *alt_ref; + int rc; + int i, j; + + memset(&frame, 0, sizeof frame); + + /* Phase 5 S3: kernel has no coeff_probs default fallback. The + * VAAPI consumer chain (FFmpeg's vaapi_vp8.c:146-148, used by + * mpv and ffmpeg-vaapi) always sends VAProbabilityBufferType + * per frame. Surface immediately if a future consumer doesn't. */ + assert(probability_set); + + /* Clause 3: frame geometry + per-frame scalars */ + frame.width = picture->frame_width; + frame.height = picture->frame_height; + frame.horizontal_scale = 0; /* not exposed by VAAPI */ + frame.vertical_scale = 0; + frame.version = picture->pic_fields.bits.version; + frame.prob_skip_false = picture->prob_skip_false; + frame.prob_intra = picture->prob_intra; + frame.prob_last = picture->prob_last; + frame.prob_gf = picture->prob_gf; + /* Phase 3 Q2: VAAPI counts include control partition; + * kernel counts DCT only — off-by-one. */ + frame.num_dct_parts = slice->num_of_partitions - 1; + + /* Clause 4: DPB timestamp resolution (mirrors mpeg2.c pattern; + * NULL surface → timestamp stays 0 from memset). */ + last_ref = SURFACE(driver_data, picture->last_ref_frame); + golden_ref = SURFACE(driver_data, picture->golden_ref_frame); + alt_ref = SURFACE(driver_data, picture->alt_ref_frame); + if (last_ref != NULL) + frame.last_frame_ts = + v4l2_timeval_to_ns(&last_ref->timestamp); + if (golden_ref != NULL) + frame.golden_frame_ts = + v4l2_timeval_to_ns(&golden_ref->timestamp); + if (alt_ref != NULL) + frame.alt_frame_ts = + v4l2_timeval_to_ns(&alt_ref->timestamp); + + /* Clause 5: loop filter mapping */ + for (i = 0; i < 4; i++) { + frame.lf.ref_frm_delta[i] = + picture->loop_filter_deltas_ref_frame[i]; + frame.lf.mb_mode_delta[i] = + picture->loop_filter_deltas_mode[i]; + } + frame.lf.sharpness_level = picture->pic_fields.bits.sharpness_level; + frame.lf.level = picture->loop_filter_level[0]; + if (picture->pic_fields.bits.loop_filter_adj_enable) + frame.lf.flags |= V4L2_VP8_LF_ADJ_ENABLE; + if (picture->pic_fields.bits.mode_ref_lf_delta_update) + frame.lf.flags |= V4L2_VP8_LF_DELTA_UPDATE; + if (picture->pic_fields.bits.filter_type) + frame.lf.flags |= V4L2_VP8_LF_FILTER_TYPE_SIMPLE; + + /* Clause 6: quantization base + delta derivation */ + if (iqmatrix_set) { + frame.quant.y_ac_qi = + iqmatrix->quantization_index[0][0]; + frame.quant.y_dc_delta = (int8_t) + (iqmatrix->quantization_index[0][1] - + iqmatrix->quantization_index[0][0]); + frame.quant.y2_dc_delta = (int8_t) + (iqmatrix->quantization_index[0][2] - + iqmatrix->quantization_index[0][0]); + frame.quant.y2_ac_delta = (int8_t) + (iqmatrix->quantization_index[0][3] - + iqmatrix->quantization_index[0][0]); + frame.quant.uv_dc_delta = (int8_t) + (iqmatrix->quantization_index[0][4] - + iqmatrix->quantization_index[0][0]); + frame.quant.uv_ac_delta = (int8_t) + (iqmatrix->quantization_index[0][5] - + iqmatrix->quantization_index[0][0]); + } + + if (picture->pic_fields.bits.segmentation_enabled && iqmatrix_set) { + for (i = 1; i < 4; i++) + frame.segment.quant_update[i] = (int8_t) + (iqmatrix->quantization_index[i][0] - + iqmatrix->quantization_index[0][0]); + } + + /* Clause 7: segment fields */ + for (i = 0; i < 3; i++) + frame.segment.segment_probs[i] = + picture->mb_segment_tree_probs[i]; + if (picture->pic_fields.bits.segmentation_enabled) + frame.segment.flags |= V4L2_VP8_SEGMENT_FLAG_ENABLED; + if (picture->pic_fields.bits.update_mb_segmentation_map) + frame.segment.flags |= V4L2_VP8_SEGMENT_FLAG_UPDATE_MAP; + if (picture->pic_fields.bits.update_segment_feature_data) + frame.segment.flags |= + V4L2_VP8_SEGMENT_FLAG_UPDATE_FEATURE_DATA; + /* DELTA_VALUE_MODE: VAAPI doesn't expose abs_delta. FFmpeg sets + * unconditionally per !s->segmentation.absolute_vals (default). + * Kernel ignores when ENABLED bit clear (BBB case). */ + frame.segment.flags |= V4L2_VP8_SEGMENT_FLAG_DELTA_VALUE_MODE; + + if (picture->pic_fields.bits.segmentation_enabled) { + for (i = 0; i < 4; i++) + frame.segment.lf_update[i] = (int8_t) + (picture->loop_filter_level[i] - + picture->loop_filter_level[0]); + } + + /* Clause 8: entropy table mapping (3 VAAPI sources merged) */ + for (i = 0; i < 4; i++) + frame.entropy.y_mode_probs[i] = picture->y_mode_probs[i]; + for (i = 0; i < 3; i++) + frame.entropy.uv_mode_probs[i] = picture->uv_mode_probs[i]; + for (i = 0; i < 2; i++) + for (j = 0; j < 19; j++) + frame.entropy.mv_probs[i][j] = + picture->mv_probs[i][j]; + /* coeff_probs[4][8][3][11]: VAAPI layout matches kernel exactly; + * direct memcpy. Both vaapi_vp8.c:133-143 and v4l2_request_vp8.c: + * 141-153 apply identical coeff_bands_inverse reordering before + * writing — VAAPI consumer has done the reordering for us. */ + memcpy(frame.entropy.coeff_probs, + probability->dct_coeff_probs, + sizeof frame.entropy.coeff_probs); + + /* Clause 9: coder state + first-partition fields */ + frame.coder_state.range = picture->bool_coder_ctx.range; + frame.coder_state.value = picture->bool_coder_ctx.value; + frame.coder_state.bit_count = picture->bool_coder_ctx.count; + + /* Phase 5 C1+C2: macroblock_offset IS first_part_header_bits by + * source identity; kernel hantro_g1_vp8_dec.c:260 reads it + * unconditionally to compute MB-data DMA offset. partition_size[0] + * is the post-parse REMAINDER; recover total via + * + ceil(macroblock_offset/8). */ + frame.first_part_header_bits = slice->macroblock_offset; + frame.first_part_size = + slice->partition_size[0] + + ((uint32_t)slice->macroblock_offset + 7) / 8; + + for (i = 0; i < 8; i++) + frame.dct_part_sizes[i] = slice->partition_size[i + 1]; + + /* Clause 9: flags assembly (6 mainline-documented bits only; + * EXPERIMENTAL + bit 0x40 NOT replicated despite ffmpeg-v4l2- + * request-git setting them — kernel hantro_vp8.c only inspects + * KEY_FRAME bit). VAAPI inverts: key_frame=0 means it IS a + * keyframe per VP8 spec. */ + if (!picture->pic_fields.bits.key_frame) + frame.flags |= V4L2_VP8_FRAME_FLAG_KEY_FRAME; + frame.flags |= V4L2_VP8_FRAME_FLAG_SHOW_FRAME; + if (picture->pic_fields.bits.mb_no_coeff_skip) + frame.flags |= V4L2_VP8_FRAME_FLAG_MB_NO_SKIP_COEFF; + if (picture->pic_fields.bits.sign_bias_golden) + frame.flags |= V4L2_VP8_FRAME_FLAG_SIGN_BIAS_GOLDEN; + if (picture->pic_fields.bits.sign_bias_alternate) + frame.flags |= V4L2_VP8_FRAME_FLAG_SIGN_BIAS_ALT; + + /* Clause 1+10: single-control batched submission */ + struct v4l2_ext_control ctrls[1] = { + { + .id = V4L2_CID_STATELESS_VP8_FRAME, + .ptr = &frame, + .size = sizeof frame, + }, + }; + + rc = v4l2_set_controls(driver_data->video_fd, + surface_object->request_fd, + ctrls, 1); + if (rc < 0) + return VA_STATUS_ERROR_OPERATION_FAILED; + + return 0; +} diff --git a/src/vp8.h b/src/vp8.h new file mode 100644 index 0000000..0cac476 --- /dev/null +++ b/src/vp8.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2026 Markus Fritsche + * + * fresnel-fourier iter3: VP8 codec dispatcher header. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sub license, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef _VP8_H_ +#define _VP8_H_ + +struct object_context; +struct object_surface; +struct request_data; + +int vp8_set_controls(struct request_data *driver_data, + struct object_context *context, + struct object_surface *surface_object); + +#endif