diff --git a/include/upipe-modules/upipe_interlace.h b/include/upipe-modules/upipe_interlace.h new file mode 100644 index 000000000..6014c0546 --- /dev/null +++ b/include/upipe-modules/upipe_interlace.h @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2026 EasyTools + * + * Authors: Arnaud de Turckheim + * + * SPDX-License-Identifier: MIT + */ + +/** @file + * @short Upipe interlacing module + */ + +#ifndef _UPIPE_MODULES_UPIPE_INTERLACE_H_ +/** @hidden */ +#define _UPIPE_MODULES_UPIPE_INTERLACE_H_ +#ifdef __cplusplus +extern "C" { +#endif + +#include "upipe/upipe.h" + +#define UPIPE_INTERLACE_SIGNATURE UBASE_FOURCC('i','n','t','l') + +/** @This extends upipe_command with specific commands for interlace pipes. */ +enum upipe_interlace_command { + UPIPE_INTERLACE_SENTINEL = UPIPE_CONTROL_LOCAL, + + /** set top field first output (bool) */ + UPIPE_INTERLACE_SET_TFF, + /** get the configured value for top field first output (bool *) */ + UPIPE_INTERLACE_GET_TFF, + /** set field drop (bool) */ + UPIPE_INTERLACE_SET_DROP, + /** get the configured value for field drop (bool *) */ + UPIPE_INTERLACE_GET_DROP, + /** set low pass filter (bool) */ + UPIPE_INTERLACE_SET_LOWPASS, + /** get the configured value for low pass filter (bool *) */ + UPIPE_INTERLACE_GET_LOWPASS, +}; + +/** @This converts @ref upipe_interlace_command to a string. + * + * @param command command to convert + * @return a string or NULL if invalid + */ +static inline const char *upipe_interlace_command_str(int command) +{ + switch ((enum upipe_interlace_command)command) { + UBASE_CASE_TO_STR(UPIPE_INTERLACE_SET_TFF); + UBASE_CASE_TO_STR(UPIPE_INTERLACE_GET_TFF); + UBASE_CASE_TO_STR(UPIPE_INTERLACE_SET_DROP); + UBASE_CASE_TO_STR(UPIPE_INTERLACE_GET_DROP); + UBASE_CASE_TO_STR(UPIPE_INTERLACE_SET_LOWPASS); + UBASE_CASE_TO_STR(UPIPE_INTERLACE_GET_LOWPASS); + case UPIPE_INTERLACE_SENTINEL: break; + } + return NULL; +} + +/** @This sets the top field first output. + * + * @param upipe description structure of the pipe + * @param tff true for top field first, false for bottom field first + * @return an error code + */ +static inline int upipe_interlace_set_tff(struct upipe *upipe, bool tff) +{ + return upipe_control(upipe, UPIPE_INTERLACE_SET_TFF, + UPIPE_INTERLACE_SIGNATURE, tff ? 1 : 0); +} + +/** @This gets the top field first output configuration. + * + * @param upipe description structure of the pipe + * @param tff filled with the configured value + * @return an error code + */ +static inline int upipe_interlace_get_tff(struct upipe *upipe, bool *tff) +{ + return upipe_control(upipe, UPIPE_INTERLACE_GET_TFF, + UPIPE_INTERLACE_SIGNATURE, tff); +} + +/** @This sets field drop. + * + * If set to true, two frames are merged into one, keeping one field + * of each, so the output frame rate will be divided by two. + * + * 1111 2222 3333 4444 -> 1111 3333 + * 1111 2222 3333 4444 2222 4444 + * 1111 2222 3333 4444 1111 3333 + * 1111 2222 3333 4444 2222 4444 + * + * If set to false, each frame is merged with the previous and the + * next so the output frame rate is unchanged. + * + * 1111 2222 3333 4444 -> 1111 2222 3333 4444 + * 1111 2222 3333 4444 2222 3333 4444 5555 + * 1111 2222 3333 4444 1111 2222 3333 4444 + * 1111 2222 3333 4444 2222 3333 4444 5555 + * + * @param upipe description structure of the pipe + * @param drop true for dropping field + * @return an error code + */ +static inline int upipe_interlace_set_drop(struct upipe *upipe, bool drop) +{ + return upipe_control(upipe, UPIPE_INTERLACE_SET_DROP, + UPIPE_INTERLACE_SIGNATURE, drop ? 1 : 0); +} + +/** @This gets the field drop configuration. + * + * @param upipe description structure of the pipe + * @param drop filled with the configured value + * @return an error code + */ +static inline int upipe_interlace_get_drop(struct upipe *upipe, bool *drop) +{ + return upipe_control(upipe, UPIPE_INTERLACE_GET_DROP, + UPIPE_INTERLACE_SIGNATURE, drop); +} + +/** @This sets low pass filter. + * + * @param upipe description structure of the pipe + * @param lowpass true to enable low pass filter + * @return an error code + */ +static inline int upipe_interlace_set_lowpass(struct upipe *upipe, bool lowpass) +{ + return upipe_control(upipe, UPIPE_INTERLACE_SET_LOWPASS, + UPIPE_INTERLACE_SIGNATURE, lowpass ? 1 : 0); +} + +/** @This gets the configured value for low pass filter. + * + * @param upipe description structure of the pipe + * @param lowpass filled with the configured value + * @return an error code + */ +static inline int upipe_interlace_get_lowpass(struct upipe *upipe, + bool *lowpass) +{ + return upipe_control(upipe, UPIPE_INTERLACE_GET_LOWPASS, + UPIPE_INTERLACE_SIGNATURE, lowpass); +} + +/** @This returns the management structure for all interlace pipes. + * + * @return pointer to manager + */ +struct upipe_mgr *upipe_interlace_mgr_alloc(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/lib/upipe-modules/Build.mk b/lib/upipe-modules/Build.mk index 824c92fe6..e2d492612 100644 --- a/lib/upipe-modules/Build.mk +++ b/lib/upipe-modules/Build.mk @@ -35,6 +35,7 @@ libupipe_modules-includes = \ upipe_htons.h \ upipe_http_source.h \ upipe_idem.h \ + upipe_interlace.h \ upipe_m3u_reader.h \ upipe_match_attr.h \ upipe_multicat_probe.h \ @@ -116,6 +117,7 @@ libupipe_modules-src = \ upipe_htons.c \ upipe_http_source.c \ upipe_idem.c \ + upipe_interlace.c \ upipe_m3u_reader.c \ upipe_match_attr.c \ upipe_multicat_probe.c \ diff --git a/lib/upipe-modules/upipe_interlace.c b/lib/upipe-modules/upipe_interlace.c new file mode 100644 index 000000000..ed7e66d0a --- /dev/null +++ b/lib/upipe-modules/upipe_interlace.c @@ -0,0 +1,696 @@ +/* + * Copyright (C) 2026 EasyTools + * + * Authors: Arnaud de Turckheim + * + * SPDX-License-Identifier: MIT + */ + +/** @file + * @short Upipe interlacing module + */ + +#include "upipe-modules/upipe_interlace.h" +#include "upipe/ubuf.h" +#include "upipe/upipe.h" +#include "upipe/upipe_helper_input.h" +#include "upipe/upipe_helper_output.h" +#include "upipe/upipe_helper_ubuf_mgr.h" +#include "upipe/upipe_helper_upipe.h" +#include "upipe/upipe_helper_urefcount.h" +#include "upipe/upipe_helper_void.h" +#include "upipe/uref.h" +#include "upipe/uref_clock.h" +#include "upipe/uref_flow.h" +#include "upipe/uref_pic.h" +#include "upipe/uref_pic_flow.h" + +#include +#include + +/** @hidden */ +static bool upipe_interlace_handle(struct upipe *upipe, struct uref *uref, + struct upump **upump_p); +/** @hidden */ +static int upipe_interlace_check(struct upipe *upipe, struct uref *flow_format); + +/** @internal upipe_interlace private structure */ +struct upipe_interlace { + /** refcount management structure */ + struct urefcount urefcount; + + /** ubuf manager */ + struct ubuf_mgr *ubuf_mgr; + /** flow format packet */ + struct uref *flow_format; + /** ubuf manager request */ + struct urequest ubuf_mgr_request; + + /** output pipe */ + struct upipe *output; + /** flow_definition packet */ + struct uref *flow_def; + /** output state */ + enum upipe_helper_output_state output_state; + /** list of output requests */ + struct uchain request_list; + + /** temporary uref storage (used during urequest) */ + struct uchain urefs; + /** nb urefs in storage */ + unsigned int nb_urefs; + /** max urefs in storage */ + unsigned int max_urefs; + /** list of blockers (used during udeal) */ + struct uchain blockers; + + /** input is already interlaced? */ + bool bypass; + /** output top field first */ + bool tff; + /** drop field? */ + bool drop; + /** low pass filtering? */ + bool lowpass; + /** last input frame */ + struct uref *uref_last; + /** current input width */ + uint64_t width; + /** current input heigth */ + uint64_t height; + + /** public structure */ + struct upipe upipe; +}; + +UPIPE_HELPER_UPIPE(upipe_interlace, upipe, UPIPE_INTERLACE_SIGNATURE) +UPIPE_HELPER_UREFCOUNT(upipe_interlace, urefcount, upipe_interlace_free) +UPIPE_HELPER_VOID(upipe_interlace) +UPIPE_HELPER_OUTPUT(upipe_interlace, output, flow_def, output_state, + request_list) +UPIPE_HELPER_UBUF_MGR(upipe_interlace, ubuf_mgr, flow_format, ubuf_mgr_request, + upipe_interlace_check, + upipe_interlace_register_output_request, + upipe_interlace_unregister_output_request) +UPIPE_HELPER_INPUT(upipe_interlace, urefs, nb_urefs, max_urefs, blockers, + upipe_interlace_handle) + +/** @internal @This allocates a filter pipe. + * + * @param mgr common management structure + * @param uprobe structure used to raise events + * @param signature signature of the pipe allocator + * @param args optional arguments + * @return pointer to upipe or NULL in case of allocation error + */ +static struct upipe *upipe_interlace_alloc(struct upipe_mgr *mgr, + struct uprobe *uprobe, + uint32_t signature, va_list args) +{ + struct upipe *upipe = + upipe_interlace_alloc_void(mgr, uprobe, signature, args); + if (unlikely(upipe == NULL)) + return NULL; + + upipe_interlace_init_urefcount(upipe); + upipe_interlace_init_ubuf_mgr(upipe); + upipe_interlace_init_output(upipe); + upipe_interlace_init_input(upipe); + + struct upipe_interlace *upipe_interlace = upipe_interlace_from_upipe(upipe); + upipe_interlace->bypass = true; + upipe_interlace->uref_last = NULL; + upipe_interlace->tff = true; + upipe_interlace->drop = true; + upipe_interlace->lowpass = false; + + upipe_throw_ready(upipe); + + return upipe; +} + +/** @This frees a upipe. + * + * @param upipe description structure of the pipe + */ +static void upipe_interlace_free(struct upipe *upipe) +{ + struct upipe_interlace *upipe_interlace = upipe_interlace_from_upipe(upipe); + + upipe_throw_dead(upipe); + + uref_free(upipe_interlace->uref_last); + + upipe_interlace_clean_input(upipe); + upipe_interlace_clean_ubuf_mgr(upipe); + upipe_interlace_clean_output(upipe); + upipe_interlace_clean_urefcount(upipe); + upipe_interlace_free_void(upipe); +} + +/** @internal @This copies a line in the output buffer. + * + * @param upipe description structure of the pipe + * @param in pointer to the line to copy + * @param above pointer to the line above for filtering + * @param below pointer to the line below for filtering + * @param width number of pixel to copy + * @param mpixel size of a pixel in byte + * @param out pointer to the destination buffer + */ +static void upipe_interlace_line(struct upipe *upipe, const uint8_t *in, + const uint8_t *above, const uint8_t *below, + uint64_t width, uint8_t mpixel, uint8_t *out) +{ + struct upipe_interlace *upipe_interlace = upipe_interlace_from_upipe(upipe); + + if (!upipe_interlace->lowpass || mpixel > 2) { + memcpy(out, in, width * mpixel); + } else if (mpixel == 1) { + for (uint64_t i = 0; i < width; i++) + out[i] = (1 + (in[i] << 1) + above[i] + below[i]) >> 2; + } else { + const uint16_t *in16 = (const uint16_t *)in; + const uint16_t *above16 = (const uint16_t *)above; + const uint16_t *below16 = (const uint16_t *)below; + uint16_t *out16 = (uint16_t *)out; + for (uint64_t i = 0; i < width; i++) + out16[i] = (1 + (in16[i] << 1) + above16[i] + below16[i]) >> 2; + } +} + +/** @internal @This processes a picture plane + * + * @param upipe description structure of the pipe + * @param top input frame buffer used as top lines + * @param bottom input frame buffer used as bottom lines + * @param ubuf output frame buffer + * @param chroma chroma plane to interlace + * @return an error code + */ +static int upipe_interlace_plane(struct upipe *upipe, struct uref *top, + struct uref *bottom, struct ubuf *ubuf, + const char *chroma) +{ + struct upipe_interlace *upipe_interlace = upipe_interlace_from_upipe(upipe); + const uint8_t *t_in; + const uint8_t *b_in; + uint8_t *out; + uint8_t t_hsub, b_hsub, out_hsub; + uint8_t t_vsub, b_vsub, out_vsub; + uint8_t t_size, b_size, out_size; + size_t t_stride = 0, b_stride = 0, out_stride = 0; + int ret; + + ret = + uref_pic_plane_size(top, chroma, &t_stride, &t_hsub, &t_vsub, &t_size); + if (unlikely(!ubase_check(ret))) { + upipe_err(upipe, "Could not get top input plane size"); + return ret; + } + + ret = uref_pic_plane_size(bottom, chroma, &b_stride, &b_hsub, &b_vsub, + &b_size); + if (unlikely(!ubase_check(ret))) { + upipe_err(upipe, "Could not get bottom input plane size"); + return ret; + } + + ret = ubuf_pic_plane_size(ubuf, chroma, &out_stride, &out_hsub, &out_vsub, + &out_size); + if (unlikely(!ubase_check(ret))) { + upipe_err(upipe, "Could not get output plane size"); + return ret; + } + + if (!t_hsub || !t_vsub || !t_size) { + upipe_err(upipe, "Invalid input frame"); + return UBASE_ERR_INVALID; + } + + if (t_hsub != b_hsub || t_vsub != b_vsub || t_size != b_size) { + upipe_err(upipe, "Incompatible input frames"); + return UBASE_ERR_INVALID; + } + + if (t_hsub != out_hsub || t_vsub != out_vsub || t_size != out_size) { + upipe_err(upipe, "Incompatible output frame"); + return UBASE_ERR_INVALID; + } + + // map + ret = uref_pic_plane_read(top, chroma, 0, 0, -1, -1, &t_in); + if (unlikely(!ubase_check(ret))) { + upipe_warn_va(upipe, "fail to read %s plane", chroma); + return ret; + } + ret = uref_pic_plane_read(bottom, chroma, 0, 0, -1, -1, &b_in); + if (unlikely(!ubase_check(ret))) { + upipe_warn_va(upipe, "fail to read %s plane", chroma); + uref_pic_plane_unmap(top, chroma, 0, 0, -1, -1); + return ret; + + } + ret = ubuf_pic_plane_write(ubuf, chroma, 0, 0, -1, -1, &out); + if (unlikely(!ubase_check(ret))) { + upipe_warn_va(upipe, "fail to write %s plane", chroma); + uref_pic_plane_unmap(bottom, chroma, 0, 0, -1, -1); + uref_pic_plane_unmap(top, chroma, 0, 0, -1, -1); + return ret; + } + + // interlace plane + uint64_t lines = upipe_interlace->height / t_vsub; + uint64_t width = upipe_interlace->width / t_hsub; + for (uint64_t l = 0; l < lines; l++) { + const uint8_t *in = l % 2 ? b_in : t_in; + const uint8_t *above = l ? in - t_stride : in; + const uint8_t *below = l + 1 < lines ? in + t_stride : in; + upipe_interlace_line(upipe, in, above, below, width, t_size, out); + t_in += t_stride; + b_in += b_stride; + out += out_stride; + } + + // unmap + uref_pic_plane_unmap(top, chroma, 0, 0, -1, -1); + uref_pic_plane_unmap(bottom, chroma, 0, 0, -1, -1); + ubuf_pic_plane_unmap(ubuf, chroma, 0, 0, -1, -1); + + return UBASE_ERR_NONE; +} + +/** @internal @This updates the output flow definition. + * + * @param upipe description structure of the pipe + * @param flow_def input flow definition packet + */ +static void upipe_interlace_set_flow_def_real(struct upipe *upipe, + struct uref *flow_def) +{ + struct upipe_interlace *upipe_interlace = upipe_interlace_from_upipe(upipe); + + uref_free(upipe_interlace->uref_last); + upipe_interlace->uref_last = NULL; + + if (uref_pic_check_progressive(flow_def)) { + upipe_interlace->bypass = false; + struct urational fps; + if (upipe_interlace->drop && + ubase_check(uref_pic_flow_get_fps(flow_def, &fps))) { + fps.den *= 2; + urational_simplify(&fps); + uref_pic_flow_set_fps(flow_def, fps); + } + uref_pic_set_progressive(flow_def, false); + uref_pic_set_tff(flow_def, upipe_interlace->tff); + upipe_interlace_store_flow_def(upipe, NULL); + upipe_interlace_require_ubuf_mgr(upipe, flow_def); + } else { + upipe_interlace->bypass = true; + upipe_interlace_store_flow_def(upipe, flow_def); + } +} + +/** @internal @This checks the input frames + * + * @param upipe description structure of the pipe + * @param top input frame used for top field + * @param bottom input frame used for bottom field + * @return an error code + */ +static int upipe_interlace_check_frames(struct upipe *upipe, struct uref *top, + struct uref *bottom) +{ + struct upipe_interlace *upipe_interlace = upipe_interlace_from_upipe(upipe); + + size_t t_width = 0; + size_t t_height = 0; + uint8_t t_macropixel = 0; + uref_pic_size(top, &t_width, &t_height, &t_macropixel); + + size_t b_width = 0; + size_t b_height = 0; + uint8_t b_macropixel = 0; + uref_pic_size(bottom, &b_width, &b_height, &b_macropixel); + + if (t_width != b_width || t_height != b_height || + t_macropixel != b_macropixel) { + upipe_err(upipe, "Incompatible frames received"); + return UBASE_ERR_INVALID; + } + + upipe_interlace->width = t_width; + upipe_interlace->height = t_height; + + return UBASE_ERR_NONE; +} + +/** @internal @This handles input. + * + * @param upipe description structure of the pipe + * @param uref uref structure + * @param upump_p reference to upump structure + * @return always true + */ +static bool upipe_interlace_handle(struct upipe *upipe, struct uref *uref, + struct upump **upump_p) +{ + struct upipe_interlace *upipe_interlace = upipe_interlace_from_upipe(upipe); + const char *def; + if (unlikely(ubase_check(uref_flow_get_def(uref, &def)))) { + upipe_interlace_set_flow_def_real(upipe, uref); + return true; + } + + if (!upipe_interlace->flow_def) { + if (urequest_get_opaque(&upipe_interlace->ubuf_mgr_request, + struct upipe *) == NULL) { + upipe_warn(upipe, "no input flow def received, dropping..."); + uref_free(uref); + return true; + } + + return false; + } + + if (upipe_interlace->bypass) { + upipe_interlace_output(upipe, uref, upump_p); + return true; + } + + if (!upipe_interlace->uref_last) { + upipe_interlace->uref_last = uref; + return true; + } + + // Now process input frames + struct uref *last = upipe_interlace->uref_last; + struct uref *top = upipe_interlace->tff ? last : uref; + struct uref *bottom = upipe_interlace->tff ? uref : last; + upipe_interlace->uref_last = upipe_interlace->drop ? NULL : uref_dup(uref); + if (unlikely( + !ubase_check(upipe_interlace_check_frames(upipe, top, bottom)))) { + uref_free(top); + uref_free(bottom); + return true; + } + + // Allocate output frame + struct ubuf *ubuf = NULL; + if (upipe_interlace->ubuf_mgr) + ubuf = ubuf_pic_alloc(upipe_interlace->ubuf_mgr, upipe_interlace->width, + upipe_interlace->height); + if (unlikely(!ubuf)) { + uref_free(top); + uref_free(bottom); + upipe_throw_fatal(upipe, UBASE_ERR_ALLOC); + return true; + } + + // Interlace planes + const char *chroma; + uref_pic_foreach_plane(uref, chroma) { + int ret = upipe_interlace_plane(upipe, top, bottom, ubuf, chroma); + if (unlikely(!ubase_check(ret))) { + uref_free(top); + uref_free(bottom); + ubuf_free(ubuf); + return true; + } + } + + // Compute output frame duration + uint64_t t_duration = UINT64_MAX; + uint64_t b_duration = UINT64_MAX; + uref_clock_get_duration(top, &t_duration); + uref_clock_get_duration(bottom, &b_duration); + + // Free last frame + uref_free(last); + + // Attach new ubuf to current frame + uref_attach_ubuf(uref, ubuf); + + // Update attributes + uref_pic_set_progressive(uref, false); + uref_pic_set_tff(uref, upipe_interlace->tff); + if (upipe_interlace->drop) { + uref_clock_delete_duration(uref); + if (t_duration != UINT64_MAX && b_duration != UINT64_MAX) + uref_clock_set_duration(uref, t_duration + b_duration); + } + + // Output frame + upipe_interlace_output(upipe, uref, upump_p); + + return true; +} + +/** @internal @This inputs data. + * + * @param upipe description structure of the pipe + * @param uref uref structure + * @param upump_p reference to pump that generated the buffer + */ +static void upipe_interlace_input(struct upipe *upipe, struct uref *uref, + struct upump **upump_p) +{ + if (!upipe_interlace_check_input(upipe)) { + upipe_interlace_hold_input(upipe, uref); + upipe_interlace_block_input(upipe, upump_p); + } else if (!upipe_interlace_handle(upipe, uref, upump_p)) { + upipe_interlace_hold_input(upipe, uref); + upipe_interlace_block_input(upipe, upump_p); + /* Increment upipe refcount to avoid disappearing before all packets + * have been sent. */ + upipe_use(upipe); + } +} + +/** @internal @This checks if the input may start. + * + * @param upipe description structure of the pipe + * @param flow_format amended flow format + * @return an error code + */ +static int upipe_interlace_check(struct upipe *upipe, struct uref *flow_format) +{ + struct upipe_interlace *upipe_interlace = upipe_interlace_from_upipe(upipe); + if (flow_format != NULL) + upipe_interlace_store_flow_def(upipe, flow_format); + + if (upipe_interlace->flow_def == NULL) + return UBASE_ERR_NONE; + + bool was_buffered = !upipe_interlace_check_input(upipe); + upipe_interlace_output_input(upipe); + upipe_interlace_unblock_input(upipe); + if (was_buffered && upipe_interlace_check_input(upipe)) { + /* All packets have been output, release again the pipe that has been + * used in @ref upipe_interlace_input. */ + upipe_release(upipe); + } + return UBASE_ERR_NONE; +} + +/** @internal @This sets the input flow definition. + * + * @param upipe description structure of the pipe + * @param flow_def flow definition packet + * @return an error code + */ +static int upipe_interlace_set_flow_def(struct upipe *upipe, + struct uref *flow_def) +{ + struct upipe_interlace *upipe_interlace = upipe_interlace_from_upipe(upipe); + + if (unlikely(!flow_def)) + return UBASE_ERR_INVALID; + + UBASE_RETURN(uref_flow_match_def(flow_def, "pic.")) + if (!uref_pic_check_progressive(flow_def)) { + bool input_tff = uref_pic_check_tff(flow_def); + if (input_tff != upipe_interlace->tff) { + upipe_err_va(upipe, "input interlacing mismatch, got %s need %s", + input_tff ? "tff" : "bff", + upipe_interlace->tff ? "tff" : "bff"); + return UBASE_ERR_INVALID; + } + } + + struct uref *flow_def_dup = uref_dup(flow_def); + UBASE_ALLOC_RETURN(flow_def_dup); + upipe_input(upipe, flow_def_dup, NULL); + return UBASE_ERR_NONE; +} + +/** @internal @This sets the top field first output. + * + * @param upipe description structure of the pipe + * @param tff true for top field first, false for bottom field first + * @return an error code + */ +static int _upipe_interlace_set_tff(struct upipe *upipe, bool tff) +{ + struct upipe_interlace *upipe_interlace = upipe_interlace_from_upipe(upipe); + upipe_interlace->tff = tff; + return UBASE_ERR_NONE; +} + +/** @internal @This get the top field first output configuration. + * + * @param upipe description structure of the pipe + * @param tff filled with the configured value + * @return an error code + */ +static int _upipe_interlace_get_tff(struct upipe *upipe, bool *tff) +{ + struct upipe_interlace *upipe_interlace = upipe_interlace_from_upipe(upipe); + if (tff) + *tff = upipe_interlace->tff; + return UBASE_ERR_NONE; +} + +/** @internal @This sets field drop. + * + * @param upipe description structure of the pipe + * @param drop true for dropping field + * @return an error code + */ +static int _upipe_interlace_set_drop(struct upipe *upipe, bool drop) +{ + struct upipe_interlace *upipe_interlace = upipe_interlace_from_upipe(upipe); + upipe_interlace->drop = drop; + return UBASE_ERR_NONE; +} + +/** @internal @This get the field drop configuration. + * + * @param upipe description structure of the pipe + * @param drop filled with the configured value + * @return an error code + */ +static int _upipe_interlace_get_drop(struct upipe *upipe, bool *drop) +{ + struct upipe_interlace *upipe_interlace = upipe_interlace_from_upipe(upipe); + if (drop) + *drop = upipe_interlace->drop; + return UBASE_ERR_NONE; +} + +/** @internal @This sets low pass filter. + * + * @param upipe description structure of the pipe + * @param lowpass true to enable low pass filter + * @return an error code + */ +static int _upipe_interlace_set_lowpass(struct upipe *upipe, bool lowpass) +{ + struct upipe_interlace *upipe_interlace = upipe_interlace_from_upipe(upipe); + upipe_interlace->lowpass = lowpass; + return UBASE_ERR_NONE; +} + +/** @internal @This get the configured value for low pass filter. + * + * @param upipe description structure of the pipe + * @param lowpass filled with the configured value + * @return an error code + */ +static int _upipe_interlace_get_lowpass(struct upipe *upipe, bool *lowpass) +{ + struct upipe_interlace *upipe_interlace = upipe_interlace_from_upipe(upipe); + if (lowpass) + *lowpass = upipe_interlace->lowpass; + return UBASE_ERR_NONE; +} + +/** @internal @This processes control commands on the pipe. + * + * @param upipe description structure of the pipe + * @param command type of command to process + * @param args arguments of the command + * @return an error code + */ +static int upipe_interlace_control(struct upipe *upipe, int command, + va_list args) +{ + switch (command) { + case UPIPE_REGISTER_REQUEST: { + struct urequest *request = va_arg(args, struct urequest *); + if (request->type == UREQUEST_UBUF_MGR || + request->type == UREQUEST_FLOW_FORMAT) + return upipe_throw_provide_request(upipe, request); + return upipe_interlace_alloc_output_proxy(upipe, request); + } + case UPIPE_UNREGISTER_REQUEST: { + struct urequest *request = va_arg(args, struct urequest *); + if (request->type == UREQUEST_UBUF_MGR || + request->type == UREQUEST_FLOW_FORMAT) + return UBASE_ERR_NONE; + return upipe_interlace_free_output_proxy(upipe, request); + } + case UPIPE_SET_FLOW_DEF: { + struct uref *flow_def = va_arg(args, struct uref *); + return upipe_interlace_set_flow_def(upipe, flow_def); + } + case UPIPE_GET_FLOW_DEF: + case UPIPE_GET_OUTPUT: + case UPIPE_SET_OUTPUT: + return upipe_interlace_control_output(upipe, command, args); + + case UPIPE_INTERLACE_SET_TFF: { + UBASE_SIGNATURE_CHECK(args, UPIPE_INTERLACE_SIGNATURE); + int tff = va_arg(args, int); + return _upipe_interlace_set_tff(upipe, tff != 0); + } + case UPIPE_INTERLACE_GET_TFF: { + UBASE_SIGNATURE_CHECK(args, UPIPE_INTERLACE_SIGNATURE); + bool *tff = va_arg(args, bool *); + return _upipe_interlace_get_tff(upipe, tff); + } + case UPIPE_INTERLACE_SET_DROP: { + UBASE_SIGNATURE_CHECK(args, UPIPE_INTERLACE_SIGNATURE); + int drop = va_arg(args, int); + return _upipe_interlace_set_drop(upipe, drop != 0); + } + case UPIPE_INTERLACE_GET_DROP: { + UBASE_SIGNATURE_CHECK(args, UPIPE_INTERLACE_SIGNATURE); + bool *drop = va_arg(args, bool *); + return _upipe_interlace_get_drop(upipe, drop); + } + case UPIPE_INTERLACE_SET_LOWPASS: { + UBASE_SIGNATURE_CHECK(args, UPIPE_INTERLACE_SIGNATURE); + int lowpass = va_arg(args, int); + return _upipe_interlace_set_lowpass(upipe, lowpass != 0); + } + case UPIPE_INTERLACE_GET_LOWPASS: { + UBASE_SIGNATURE_CHECK(args, UPIPE_INTERLACE_SIGNATURE); + bool *lowpass = va_arg(args, bool *); + return _upipe_interlace_get_lowpass(upipe, lowpass); + } + } + + return UBASE_ERR_UNHANDLED; +} + +/** module manager static descriptor */ +static struct upipe_mgr upipe_interlace_mgr = { + .refcount = NULL, + .signature = UPIPE_INTERLACE_SIGNATURE, + + .upipe_alloc = upipe_interlace_alloc, + .upipe_input = upipe_interlace_input, + .upipe_control = upipe_interlace_control, + .upipe_command_str = upipe_interlace_command_str, +}; + +/** @This returns the management structure for interlace pipes + * + * @return pointer to manager + */ +struct upipe_mgr *upipe_interlace_mgr_alloc(void) +{ + return &upipe_interlace_mgr; +} diff --git a/tests/Build.mk b/tests/Build.mk index 459c32db7..783091174 100644 --- a/tests/Build.mk +++ b/tests/Build.mk @@ -242,6 +242,10 @@ upipe_http_src_test-src = upipe_http_src_test.c upipe_http_src_test-libs = libupipe libupipe_modules libupump_ev upipe_http_src_test-opt-libs = libupipe_bearssl libupipe_openssl +tests += upipe_interlace_test +upipe_interlace_test-src = upipe_interlace_test.c +upipe_interlace_test-libs = libupipe libupipe_modules + tests += upipe_m3u_reader_test.sh upipe_m3u_reader_test.sh-deps = upipe_m3u_reader_test diff --git a/tests/upipe_interlace_test.c b/tests/upipe_interlace_test.c new file mode 100644 index 000000000..785735c92 --- /dev/null +++ b/tests/upipe_interlace_test.c @@ -0,0 +1,474 @@ +/* + * Copyright (C) 2026 EasyTools + * + * Authors: Arnaud de Turckheim + * + * SPDX-License-Identifier: MIT + */ + +#undef NDEBUG + +#include "upipe/uprobe.h" +#include "upipe/uprobe_prefix.h" +#include "upipe/uprobe_stdio.h" +#include "upipe/uprobe_ubuf_mem.h" +#include "upipe/umem.h" +#include "upipe/umem_alloc.h" +#include "upipe/udict.h" +#include "upipe/udict_inline.h" +#include "upipe/ubuf.h" +#include "upipe/ubuf_pic_mem.h" +#include "upipe/ubuf_mem.h" +#include "upipe/uref.h" +#include "upipe/uref_pic_flow.h" +#include "upipe/uref_pic.h" +#include "upipe/uref_std.h" +#include "upipe/upipe.h" +#include "upipe/uref_dump.h" +#include "upipe/uref_pic_flow_formats.h" +#include "upipe-modules/upipe_interlace.h" + +#include +#include +#include + +#define UDICT_POOL_DEPTH 5 +#define UREF_POOL_DEPTH 5 +#define UBUF_POOL_DEPTH 5 +#define UBUF_PREPEND 0 +#define UBUF_APPEND 0 +#define UBUF_ALIGN 32 +#define UBUF_ALIGN_HOFFSET 0 +#define UPROBE_LOG_LEVEL UPROBE_LOG_DEBUG + +#define WIDTH 4 +#define HEIGHT 8 + +static struct urational fps = { .num = 25, .den = 1 }; +static struct umem_mgr *umem_mgr = NULL; +static struct uref_mgr *uref_mgr; +static struct upipe_mgr output_mgr; +static struct upipe output; +static int output_counter = 0; +static void (*current_test)(struct upipe *) = NULL; + +static void test_no_input_flow_def(struct upipe *); +static void test_rgb_packed(struct upipe *); +static void test_yuv_planar(struct upipe *); +static void test_yuv_interlaced(struct upipe *); + +static void (*tests[])(struct upipe *) = { + test_no_input_flow_def, + test_rgb_packed, + test_yuv_planar, + test_yuv_interlaced, +}; + +/** definition of our uprobe */ +static int catch(struct uprobe *uprobe, struct upipe *upipe, + int event, va_list args) +{ + switch (event) { + default: + assert(0); + break; + case UPROBE_READY: + case UPROBE_DEAD: + case UPROBE_NEW_FLOW_DEF: + break; + } + return UBASE_ERR_NONE; +} + +static void dump_pic_plane(struct uref *uref, const char *chroma) +{ + size_t height; + size_t width; + uint8_t macro; + uint8_t hsub; + uint8_t vsub; + size_t stride; + uint8_t macropixel; + const uint8_t *buf; + + printf(" chroma %s:\n", chroma); + + ubase_assert(uref_pic_size(uref, &width, &height, ¯o)); + ubase_assert( + uref_pic_plane_size(uref, chroma, &stride, &hsub, &vsub, ¯opixel)); + + assert(hsub && vsub && width && height && macro && stride && macropixel); + + ubase_assert(uref_pic_plane_read(uref, chroma, 0, 0, -1, -1, &buf)); + for (uint64_t y = 0; y < (height / vsub); y++) { + for (uint64_t x = 0; x < width * macropixel / hsub; x += macropixel) { + printf(x ? " " : " "); + for (uint8_t o = 0; o < macropixel; o++) + printf("%x", buf[x + o]); + } + printf("\n"); + buf += stride; + } + uref_pic_plane_unmap(uref, chroma, 0, 0, -1, -1); +} + +static void dump_pic(struct uref *uref, const char *name) +{ + printf("%s:\n", name); + const char *chroma; + uref_pic_foreach_plane(uref, chroma) { + dump_pic_plane(uref, chroma); + } +} + +static struct uref *pic_alloc(struct ubuf_mgr *ubuf_mgr, unsigned counter) +{ + struct uref *uref = uref_pic_alloc(uref_mgr, ubuf_mgr, WIDTH, HEIGHT); + assert(uref); + ubase_assert(uref_pic_set_progressive(uref, true)); + + size_t width; + size_t height; + uint8_t macro; + ubase_assert(uref_pic_size(uref, &width, &height, ¯o)); + assert(width && height); + + const char *chroma; + uref_pic_foreach_plane(uref, chroma) { + uint8_t hsub; + uint8_t vsub; + uint8_t macropixel; + size_t stride; + uint8_t *buf; + + ubase_assert(uref_pic_plane_write(uref, chroma, 0, 0, -1, -1, &buf)); + ubase_assert(uref_pic_plane_size(uref, chroma, &stride, &hsub, &vsub, + ¯opixel)); + assert(stride && hsub && vsub); + + unsigned lines = height / vsub; + unsigned size = width * macropixel / hsub; + for (uint64_t y = 0; y < lines; y++) { + for (uint64_t x = 0; x < size; x++) + buf[x] = counter; + buf += stride; + } + uref_pic_plane_unmap(uref, chroma, 0, 0, -1, -1); + } + return uref; +} + +static int output_control(struct upipe *upipe, int command, va_list args) +{ + assert(current_test != test_no_input_flow_def); + + switch (command) { + case UPIPE_SET_FLOW_DEF: { + struct uref *flow_def = va_arg(args, struct uref *); + uref_dump_notice(flow_def, upipe->uprobe); + assert(!uref_pic_check_progressive(flow_def)); + output_counter = 0; + return UBASE_ERR_NONE; + } + } + return UBASE_ERR_UNHANDLED; +} + +static void output_input(struct upipe *upipe, struct uref *uref, + struct upump **upump_p) +{ + char *name = NULL; + + assert(current_test != test_no_input_flow_def); + if (current_test == test_no_input_flow_def) + + uref_dump(uref, upipe->uprobe); + assert(asprintf(&name, "output %i", output_counter) > 0); + dump_pic(uref, name); + free(name); + assert(!uref_pic_check_progressive(uref)); + uref_free(uref); + + output_counter++; +} + +static struct ubuf_mgr *test_alloc_ubuf_mgr(struct uref *flow_def) +{ + assert(flow_def); + + struct ubuf_mgr *ubuf_mgr = ubuf_mem_mgr_alloc_from_flow_def( + UBUF_POOL_DEPTH, UBUF_POOL_DEPTH, umem_mgr, flow_def); + assert(ubuf_mgr); + + return ubuf_mgr; +} + +static void test_no_input_flow_def(struct upipe *upipe) +{ + struct uref *flow_def = uref_pic_flow_alloc_rgb24(uref_mgr); + struct ubuf_mgr *ubuf_mgr = test_alloc_ubuf_mgr(flow_def); + uref_free(flow_def); + + for (int counter = 0; counter < 10; counter++) { + struct uref *uref = pic_alloc(ubuf_mgr, counter); + + printf("Sending pic %d\n", counter); + + char *name; + assert(asprintf(&name, "input %i", counter) > 0); + dump_pic(uref, name); + free(name); + upipe_input(upipe, uref, NULL); + } + + ubuf_mgr_release(ubuf_mgr); +} + +static void test_rgb_packed(struct upipe *upipe) +{ + struct uref *flow_def = uref_pic_flow_alloc_rgb24(uref_mgr); + struct ubuf_mgr *ubuf_mgr = test_alloc_ubuf_mgr(flow_def); + ubase_assert(uref_pic_set_progressive(flow_def, true)); + + ubase_assert(upipe_interlace_set_drop(upipe, true)); + ubase_assert(upipe_interlace_set_tff(upipe, true)); + ubase_assert(upipe_set_flow_def(upipe, flow_def)); + for (int counter = 0; counter < 10; counter++) { + struct uref *uref = pic_alloc(ubuf_mgr, counter); + + char *name; + assert(asprintf(&name, "input %i", counter) > 0); + dump_pic(uref, name); + free(name); + upipe_input(upipe, uref, NULL); + } + + ubase_assert(upipe_interlace_set_drop(upipe, false)); + ubase_assert(upipe_interlace_set_tff(upipe, false)); + ubase_assert(upipe_set_flow_def(upipe, flow_def)); + for (int counter = 0; counter < 10; counter++) { + struct uref *uref = pic_alloc(ubuf_mgr, counter); + + char *name; + assert(asprintf(&name, "input %i", counter) > 0); + dump_pic(uref, name); + free(name); + upipe_input(upipe, uref, NULL); + } + + ubase_assert(uref_pic_flow_set_fps(flow_def, fps)); + + ubase_assert(upipe_interlace_set_drop(upipe, true)); + ubase_assert(upipe_interlace_set_tff(upipe, false)); + ubase_assert(upipe_set_flow_def(upipe, flow_def)); + for (int counter = 0; counter < 10; counter++) { + struct uref *uref = pic_alloc(ubuf_mgr, counter); + + char *name; + assert(asprintf(&name, "input %i", counter) > 0); + dump_pic(uref, name); + free(name); + upipe_input(upipe, uref, NULL); + } + + ubase_assert(upipe_interlace_set_drop(upipe, false)); + ubase_assert(upipe_interlace_set_tff(upipe, true)); + ubase_assert(upipe_set_flow_def(upipe, flow_def)); + for (int counter = 0; counter < 10; counter++) { + struct uref *uref = pic_alloc(ubuf_mgr, counter); + + char *name; + assert(asprintf(&name, "input %i", counter) > 0); + dump_pic(uref, name); + free(name); + upipe_input(upipe, uref, NULL); + } + + ubuf_mgr_release(ubuf_mgr); + uref_free(flow_def); +} + +static void test_yuv_planar(struct upipe *upipe) +{ + struct uref *flow_def = uref_pic_flow_alloc_yuv420p(uref_mgr); + struct ubuf_mgr *ubuf_mgr = test_alloc_ubuf_mgr(flow_def); + ubase_assert(uref_pic_set_progressive(flow_def, true)); + + ubase_assert(upipe_interlace_set_drop(upipe, true)); + ubase_assert(upipe_interlace_set_tff(upipe, true)); + ubase_assert(upipe_set_flow_def(upipe, flow_def)); + for (int counter = 0; counter < 10; counter++) { + struct uref *uref = pic_alloc(ubuf_mgr, counter); + + char *name; + assert(asprintf(&name, "input %i", counter) > 0); + dump_pic(uref, name); + free(name); + upipe_input(upipe, uref, NULL); + } + + ubase_assert(upipe_interlace_set_drop(upipe, false)); + ubase_assert(upipe_interlace_set_tff(upipe, false)); + ubase_assert(upipe_set_flow_def(upipe, flow_def)); + for (int counter = 0; counter < 10; counter++) { + struct uref *uref = pic_alloc(ubuf_mgr, counter); + + char *name; + assert(asprintf(&name, "input %i", counter) > 0); + dump_pic(uref, name); + free(name); + upipe_input(upipe, uref, NULL); + } + + ubase_assert(uref_pic_flow_set_fps(flow_def, fps)); + + ubase_assert(upipe_interlace_set_drop(upipe, true)); + ubase_assert(upipe_interlace_set_tff(upipe, false)); + ubase_assert(upipe_set_flow_def(upipe, flow_def)); + for (int counter = 0; counter < 10; counter++) { + struct uref *uref = pic_alloc(ubuf_mgr, counter); + + char *name; + assert(asprintf(&name, "input %i", counter) > 0); + dump_pic(uref, name); + free(name); + upipe_input(upipe, uref, NULL); + } + + ubase_assert(upipe_interlace_set_drop(upipe, false)); + ubase_assert(upipe_interlace_set_tff(upipe, true)); + ubase_assert(upipe_set_flow_def(upipe, flow_def)); + for (int counter = 0; counter < 10; counter++) { + struct uref *uref = pic_alloc(ubuf_mgr, counter); + + char *name; + assert(asprintf(&name, "input %i", counter) > 0); + dump_pic(uref, name); + free(name); + upipe_input(upipe, uref, NULL); + } + + ubuf_mgr_release(ubuf_mgr); + uref_free(flow_def); +} + +static void test_yuv_interlaced(struct upipe *upipe) +{ + struct uref *flow_def = uref_pic_flow_alloc_yuv420p(uref_mgr); + struct ubuf_mgr *ubuf_mgr = test_alloc_ubuf_mgr(flow_def); + ubase_assert(uref_pic_set_progressive(flow_def, false)); + + ubase_assert(upipe_interlace_set_drop(upipe, true)); + ubase_assert(upipe_interlace_set_tff(upipe, true)); + ubase_nassert(upipe_set_flow_def(upipe, flow_def)); + for (int counter = 0; counter < 10; counter++) { + struct uref *uref = pic_alloc(ubuf_mgr, counter); + uref_pic_set_progressive(uref, false); + + char *name; + assert(asprintf(&name, "input %i", counter) > 0); + dump_pic(uref, name); + free(name); + upipe_input(upipe, uref, NULL); + } + + ubase_assert(upipe_interlace_set_drop(upipe, false)); + ubase_assert(upipe_interlace_set_tff(upipe, false)); + ubase_assert(upipe_set_flow_def(upipe, flow_def)); + for (int counter = 0; counter < 10; counter++) { + struct uref *uref = pic_alloc(ubuf_mgr, counter); + uref_pic_set_progressive(uref, false); + + char *name; + assert(asprintf(&name, "input %i", counter) > 0); + dump_pic(uref, name); + free(name); + upipe_input(upipe, uref, NULL); + } + + ubase_assert(uref_pic_flow_set_fps(flow_def, fps)); + + ubase_assert(upipe_interlace_set_drop(upipe, true)); + ubase_assert(upipe_interlace_set_tff(upipe, false)); + ubase_assert(upipe_set_flow_def(upipe, flow_def)); + for (int counter = 0; counter < 10; counter++) { + struct uref *uref = pic_alloc(ubuf_mgr, counter); + uref_pic_set_progressive(uref, false); + + char *name; + assert(asprintf(&name, "input %i", counter) > 0); + dump_pic(uref, name); + free(name); + upipe_input(upipe, uref, NULL); + } + + ubase_assert(upipe_interlace_set_drop(upipe, false)); + ubase_assert(upipe_interlace_set_tff(upipe, true)); + ubase_nassert(upipe_set_flow_def(upipe, flow_def)); + for (int counter = 0; counter < 10; counter++) { + struct uref *uref = pic_alloc(ubuf_mgr, counter); + uref_pic_set_progressive(uref, false); + + char *name; + assert(asprintf(&name, "input %i", counter) > 0); + dump_pic(uref, name); + free(name); + upipe_input(upipe, uref, NULL); + } + + ubuf_mgr_release(ubuf_mgr); + uref_free(flow_def); +} + +int main(int argc, char **argv) +{ + printf("Compiled %s %s (%s)\n", __DATE__, __TIME__, __FILE__); + + /* upipe env */ + umem_mgr = umem_alloc_mgr_alloc(); + assert(umem_mgr != NULL); + struct udict_mgr *udict_mgr = udict_inline_mgr_alloc(UDICT_POOL_DEPTH, umem_mgr, -1, -1); + assert(udict_mgr != NULL); + uref_mgr = uref_std_mgr_alloc(UREF_POOL_DEPTH, udict_mgr, 0); + assert(uref_mgr != NULL); + + struct uprobe uprobe; + uprobe_init(&uprobe, catch, NULL); + struct uprobe *logger = uprobe_stdio_alloc(&uprobe, stdout, + UPROBE_LOG_LEVEL); + assert(logger != NULL); + logger = uprobe_ubuf_mem_alloc(logger, umem_mgr, UBUF_POOL_DEPTH, + UBUF_POOL_DEPTH); + assert(logger != NULL); + + /* output pipe */ + upipe_mgr_init(&output_mgr); + output_mgr.upipe_input = output_input; + output_mgr.upipe_control = output_control; + + upipe_init(&output, upipe_mgr_use(&output_mgr), uprobe_use(logger)); + + /* interlace */ + struct upipe_mgr *upipe_interlace_mgr = upipe_interlace_mgr_alloc(); + struct upipe *upipe_interlace = upipe_void_alloc( + upipe_interlace_mgr, + uprobe_pfx_alloc(uprobe_use(logger), UPROBE_LOG_LEVEL, "interlace")); + assert(upipe_interlace); + ubase_assert(upipe_set_output(upipe_interlace, &output)); + + for (unsigned i = 0; i < UBASE_ARRAY_SIZE(tests); i++) { + current_test = tests[i]; + current_test(upipe_interlace); + } + + upipe_release(upipe_interlace); + upipe_clean(&output); + + upipe_mgr_release(upipe_interlace_mgr); + uref_mgr_release(uref_mgr); + uprobe_release(logger); + uprobe_clean(&uprobe); + udict_mgr_release(udict_mgr); + umem_mgr_release(umem_mgr); + return 0; +}