From a95e7930830767d02cee9e6ccfbd93c08c154ed2 Mon Sep 17 00:00:00 2001 From: Romain Bouqueau Date: Wed, 24 Sep 2025 16:28:00 -0400 Subject: [PATCH 1/7] add av1 switching frames to av1s sample group --- include/gpac/filters.h | 2 ++ include/gpac/internal/isomedia_dev.h | 5 +++++ include/gpac/internal/media_dev.h | 2 +- include/gpac/isomedia.h | 25 +++++++++++++++++++++++++ src/filters/mux_isom.c | 24 ++++++++++++++++++++---- src/filters/reframe_av1.c | 13 ++++++++++--- src/isomedia/box_code_base.c | 2 ++ src/isomedia/box_funcs.c | 2 ++ src/isomedia/isom_write.c | 26 ++++++++++++++++++++++++++ src/media_tools/av_parsers.c | 3 +++ 10 files changed, 96 insertions(+), 8 deletions(-) diff --git a/include/gpac/filters.h b/include/gpac/filters.h index 7e5c9055a..13851e576 100644 --- a/include/gpac/filters.h +++ b/include/gpac/filters.h @@ -4826,6 +4826,8 @@ typedef enum GF_FILTER_SAP_NONE = 0, /*! closed gop no leading */ GF_FILTER_SAP_1, + /*! AV1 switch frame */ + GF_FILTER_SAP_1_SWITCH, /*! closed gop leading */ GF_FILTER_SAP_2, /*! open gop */ diff --git a/include/gpac/internal/isomedia_dev.h b/include/gpac/internal/isomedia_dev.h index 29aff7f38..432ac5692 100644 --- a/include/gpac/internal/isomedia_dev.h +++ b/include/gpac/internal/isomedia_dev.h @@ -3565,6 +3565,11 @@ typedef struct u8 *data; } GF_DefaultSampleGroupDescriptionEntry; +/*AV1 Switching Entry - Switching Frames*/ +typedef struct +{ +} GF_AV1SwitchingEntry; + /*VisualRandomAccessEntry - 'rap ' type*/ typedef struct { diff --git a/include/gpac/internal/media_dev.h b/include/gpac/internal/media_dev.h index c0fd15202..d456ed03b 100644 --- a/include/gpac/internal/media_dev.h +++ b/include/gpac/internal/media_dev.h @@ -916,7 +916,7 @@ typedef struct { Bool is_first_frame; Bool seen_frame_header, seen_seq_header; - Bool key_frame, show_frame; + Bool key_frame, switch_frame, show_frame; AV1FrameType frame_type; GF_List *header_obus, *frame_obus; /*GF_AV1_OBUArrayEntry*/ AV1Tile tiles[AV1_MAX_TILE_ROWS * AV1_MAX_TILE_COLS]; diff --git a/include/gpac/isomedia.h b/include/gpac/isomedia.h index ac2506cf2..915142a54 100644 --- a/include/gpac/isomedia.h +++ b/include/gpac/isomedia.h @@ -572,6 +572,8 @@ typedef enum { RAP = 1, /*! sync point (IDR)*/ SAP_TYPE_1 = 1, + /*! sync point (IDR Switch Frame)*/ + SAP_TYPE_1_SWITCH = 1, /*! sync point (IDR)*/ SAP_TYPE_2 = 2, /*! RAP, OpenGOP*/ @@ -5012,6 +5014,16 @@ GF_Err gf_isom_fragment_set_sample_roll_group(GF_ISOFile *isom_file, GF_ISOTrack */ GF_Err gf_isom_fragment_set_sample_rap_group(GF_ISOFile *isom_file, GF_ISOTrackID trackID, u32 sample_number_in_frag, Bool is_rap, u32 num_leading_samples); +/*! sets AV1 Switching Frame information for a sample in a track fragment +\param isom_file the target ISO file +\param trackID the ID of the target track +\param sample_number_in_frag the sample number of the sample in the traf +\param is_rap set to GF_TRUE to indicate the sample is a RAP sample (open-GOP), GF_FALSE otherwise +\param num_leading_samples set to the number of leading pictures for a RAP sample +\return error if any +*/ +GF_Err gf_isom_fragment_set_sample_av1_switch_frame_group(GF_ISOFile *isom_file, GF_ISOTrackID trackID, u32 sample_number_in_frag, Bool is_switch_Frame); + /*! sets sample dependency flags in a track fragment - see ISO/IEC 14496-12 and \ref gf_filter_pck_set_dependency_flags \param isom_file the target ISO file \param trackID the ID of the target track @@ -7193,6 +7205,7 @@ enum { GF_ISOM_SAMPLE_GROUP_VIPR = GF_4CC( 'v', 'i', 'p', 'r'), //p15 GF_ISOM_SAMPLE_GROUP_LBLI = GF_4CC( 'l', 'b', 'l', 'i'), //p15 GF_ISOM_SAMPLE_GROUP_3GAG = GF_4CC( '3', 'g', 'a', 'g'), //3gpp + GF_ISOM_SAMPLE_GROUP_AV1S = GF_4CC( 'a', 'v', '1', 's'), //av1-isobmff GF_ISOM_SAMPLE_GROUP_AVCB = GF_4CC( 'a', 'v', 'c', 'b'), //avif GF_ISOM_SAMPLE_GROUP_SPOR = GF_4CC( 's', 'p', 'o', 'r'), //p15 GF_ISOM_SAMPLE_GROUP_SULM = GF_4CC( 's', 'u', 'l', 'm'), //p15 @@ -7297,6 +7310,18 @@ GF_Err gf_isom_enum_sample_aux_data(GF_ISOFile *isom_file, u32 trackNumber, u32 */ GF_Err gf_isom_set_sample_rap_group(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, Bool is_rap, u32 num_leading_samples); +/*! sets AV1 Switching Frame info for sample_number +\warning Sample group info MUST be added in order (no insertion in the tables) + +\param isom_file the target ISO file +\param trackNumber the target track +\param sampleNumber the target sample number +\param is_rap indicates if the sample is a RAP (open gop) sample +\param num_leading_samples indicates the number of leading samples (samples after this RAP that have dependences on samples before this RAP and hence should be discarded when tuning in) +\return error if any +*/ +GF_Err gf_isom_set_sample_av1_switch_frame_group(GF_ISOFile *isom_file, u32 trackNumber, u32 sampleNumber, Bool is_switch_Frame); + /*! sets roll_distance info for sample_number (number of frames before (<0) or after (>0) this sample to have a complete refresh of the decoded data (used by GDR in AVC) \warning Sample group info MUST be added in order (no insertion in the tables) diff --git a/src/filters/mux_isom.c b/src/filters/mux_isom.c index 8067f076e..542ba0ff9 100644 --- a/src/filters/mux_isom.c +++ b/src/filters/mux_isom.c @@ -4911,7 +4911,7 @@ static GF_Err mp4_mux_process_sample(GF_MP4MuxCtx *ctx, TrackWriter *tkw, GF_Fil if ((tkw->xps_inband==XPS_IB_PPS) && sap_type==GF_FILTER_SAP_3) sap_type=GF_FILTER_SAP_1; } - if (sap_type==GF_FILTER_SAP_1) + if (sap_type==GF_FILTER_SAP_1 || sap_type==GF_FILTER_SAP_1_SWITCH) tkw->sample.IsRAP = SAP_TYPE_1; else if (sap_type==GF_FILTER_SAP_2) tkw->sample.IsRAP = SAP_TYPE_2; @@ -5199,15 +5199,15 @@ static GF_Err mp4_mux_process_sample(GF_MP4MuxCtx *ctx, TrackWriter *tkw, GF_Fil } //compat with old arch: write sample to group info for all samples - if ((sap_type==3) || tkw->has_open_gop) { + if ((sap_type==GF_FILTER_SAP_3) || tkw->has_open_gop) { if (!ctx->norap) { if (for_fragment) { #ifndef GPAC_DISABLE_ISOM_FRAGMENTS - e = gf_isom_fragment_set_sample_rap_group(ctx->file, tkw->track_id, tkw->samples_in_frag, (sap_type==3) ? GF_TRUE : GF_FALSE, 0); + e = gf_isom_fragment_set_sample_rap_group(ctx->file, tkw->track_id, tkw->samples_in_frag, (sap_type==GF_FILTER_SAP_3) ? GF_TRUE : GF_FALSE, 0); #else e = GF_NOT_SUPPORTED; #endif - } else if (sap_type==3) { + } else if (sap_type==GF_FILTER_SAP_3) { e = gf_isom_set_sample_rap_group(ctx->file, tkw->track_num, tkw->nb_samples, GF_TRUE /*(sap_type==3) ? GF_TRUE : GF_FALSE*/, 0); } if (e) { @@ -5216,6 +5216,22 @@ static GF_Err mp4_mux_process_sample(GF_MP4MuxCtx *ctx, TrackWriter *tkw, GF_Fil } tkw->has_open_gop = GF_TRUE; } + + if (sap_type == GF_FILTER_SAP_1_SWITCH) { + if (for_fragment) { +#ifndef GPAC_DISABLE_ISOM_FRAGMENTS + e = gf_isom_fragment_set_sample_av1_switch_frame_group(ctx->file, tkw->track_id, tkw->samples_in_frag, GF_TRUE); +#else + e = GF_NOT_SUPPORTED; +#endif + } else { + e = gf_isom_set_sample_av1_switch_frame_group(ctx->file, tkw->track_num, tkw->nb_samples, GF_TRUE); + } + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Failed to set sample DTS "LLU" SAP 3 in RAP group: %s\n", tkw->sample.DTS, gf_error_to_string(e) )); + } + } + if (!ctx->noroll) { if ((sap_type==GF_FILTER_SAP_4) || (sap_type==GF_FILTER_SAP_4_PROL) || tkw->gdr_type) { GF_ISOSampleRollType roll_type = 0; diff --git a/src/filters/reframe_av1.c b/src/filters/reframe_av1.c index fc7716a8b..f9093cd47 100644 --- a/src/filters/reframe_av1.c +++ b/src/filters/reframe_av1.c @@ -492,7 +492,7 @@ static void av1dmx_check_dur(GF_Filter *filter, GF_AV1DmxCtx *ctx) duration += ctx->cur_fps.den; cur_dur += ctx->cur_fps.den; } - if (ctx->bsmode != IAMF && av1state->frame_state.key_frame) + if (ctx->bsmode != IAMF && (av1state->frame_state.key_frame || av1state->frame_state.switch_frame) ) is_sap = GF_TRUE; //only index at I-frame start @@ -1020,6 +1020,7 @@ static GF_Err av1dmx_parse_flush_sample(GF_Filter *filter, GF_AV1DmxCtx *ctx) u32 pck_size = 0; GF_FilterPacket *pck = NULL; u8 *output = NULL; + GF_FilterSAPType sap = GF_FILTER_SAP_NONE; if (!ctx->opid) return GF_NON_COMPLIANT_BITSTREAM; @@ -1050,7 +1051,13 @@ static GF_Err av1dmx_parse_flush_sample(GF_Filter *filter, GF_AV1DmxCtx *ctx) gf_filter_pck_merge_properties(ctx->src_pck, pck); gf_filter_pck_set_cts(pck, ctx->cts); - gf_filter_pck_set_sap(pck, ctx->state.frame_state.key_frame ? GF_FILTER_SAP_1 : 0); + + if (ctx->state.frame_state.key_frame) { + sap = GF_FILTER_SAP_1; + } else if (ctx->state.frame_state.switch_frame) { + sap = GF_FILTER_SAP_1_SWITCH; + } + gf_filter_pck_set_sap(pck, sap); if (ctx->is_iamf) { memcpy(output, ctx->iamfstate.temporal_unit_obus, pck_size); @@ -1069,7 +1076,7 @@ static GF_Err av1dmx_parse_flush_sample(GF_Filter *filter, GF_AV1DmxCtx *ctx) if (ctx->deps) { u8 flags = 0; //dependsOn - flags = ( ctx->state.frame_state.key_frame) ? 2 : 1; + flags = (ctx->state.frame_state.key_frame || ctx->state.frame_state.switch_frame) ? 2 : 1; flags <<= 2; //dependedOn flags |= ctx->state.frame_state.refresh_frame_flags ? 1 : 2; diff --git a/src/isomedia/box_code_base.c b/src/isomedia/box_code_base.c index 62d47f460..bfc23781e 100644 --- a/src/isomedia/box_code_base.c +++ b/src/isomedia/box_code_base.c @@ -10372,6 +10372,7 @@ void *sgpd_parse_entry(GF_SampleGroupDescriptionBox *p, GF_BitStream *bs, s32 by } break; + case GF_ISOM_SAMPLE_GROUP_AV1S: case GF_ISOM_SAMPLE_GROUP_TSAS: case GF_ISOM_SAMPLE_GROUP_STSA: null_size_ok = GF_TRUE; @@ -10708,6 +10709,7 @@ static u32 sgpd_size_entry(u32 grouping_type, void *entry) return 20; case GF_ISOM_SAMPLE_GROUP_LBLI: return 2; + case GF_ISOM_SAMPLE_GROUP_AV1S: case GF_ISOM_SAMPLE_GROUP_TSAS: case GF_ISOM_SAMPLE_GROUP_STSA: return 0; diff --git a/src/isomedia/box_funcs.c b/src/isomedia/box_funcs.c index 25fbcb628..891fafe7f 100644 --- a/src/isomedia/box_funcs.c +++ b/src/isomedia/box_funcs.c @@ -1171,6 +1171,8 @@ static struct box_registry_entry { SGPD_DEFINE( GF_ISOM_BOX_TYPE_SGPD, sgpd, "stbl traf", GF_ISOM_SAMPLE_GROUP_3GAG, "3gpp"), SGPD_DEFINE( GF_ISOM_BOX_TYPE_SGPD, sgpd, "stbl traf", GF_ISOM_SAMPLE_GROUP_AVCB, "3gpp"), + SGPD_DEFINE( GF_ISOM_BOX_TYPE_SGPD, sgpd, "stbl traf", GF_ISOM_SAMPLE_GROUP_AV1S, "av1-isobmff"), + //internal boxes BOX_DEFINE_S( GF_ISOM_BOX_TYPE_GNRM, gnrm, "stsd", "unknown"), BOX_DEFINE_S( GF_ISOM_BOX_TYPE_GNRV, gnrv, "stsd", "unknown"), diff --git a/src/isomedia/isom_write.c b/src/isomedia/isom_write.c index f4ba56aff..d711694e7 100644 --- a/src/isomedia/isom_write.c +++ b/src/isomedia/isom_write.c @@ -7647,6 +7647,7 @@ GF_Err gf_isom_add_sample_info(GF_ISOFile *movie, u32 track, u32 sample_number, return gf_isom_add_sample_group_entry(groupList, sample_number, sgdesc, grouping_type_parameter, sampleGroupDescriptionIndex, trak->Media->information->sampleTable->child_boxes, trak->Media->information->sampleTable); } + void *sg_rap_create_entry(void *udta) { GF_VisualRandomAccessEntry *entry; @@ -7678,6 +7679,31 @@ GF_Err gf_isom_fragment_set_sample_rap_group(GF_ISOFile *movie, GF_ISOTrackID tr } +void *sg_av1s_create_entry(void *udta) +{ + GF_AV1SwitchingEntry *entry; + GF_SAFEALLOC(entry, GF_AV1SwitchingEntry); + if (!entry) return NULL; + return entry; +} + +Bool sg_av1s_compare_entry(void *udta, void *entry) +{ + return GF_TRUE; +} + +GF_EXPORT +GF_Err gf_isom_set_sample_av1_switch_frame_group(GF_ISOFile *movie, u32 track, u32 sample_number, Bool is_switch_Frame) +{ + return gf_isom_set_sample_group_info_internal(movie, track, 0, sample_number, GF_ISOM_SAMPLE_GROUP_AV1S, 0, NULL, is_switch_Frame ? sg_av1s_create_entry : NULL, is_switch_Frame ? sg_av1s_compare_entry : NULL); +} + +GF_Err gf_isom_fragment_set_sample_av1_switch_frame_group(GF_ISOFile *movie, GF_ISOTrackID trackID, u32 sample_number_in_frag, Bool is_switch_Frame) +{ + return gf_isom_set_sample_group_info_internal(movie, 0, trackID, sample_number_in_frag, GF_ISOM_SAMPLE_GROUP_AV1S, 0, NULL, is_switch_Frame ? sg_av1s_create_entry : NULL, is_switch_Frame ? sg_av1s_compare_entry : NULL); +} + + void *sg_roll_create_entry(void *udta) { diff --git a/src/media_tools/av_parsers.c b/src/media_tools/av_parsers.c index 0210c694b..28a06d38d 100644 --- a/src/media_tools/av_parsers.c +++ b/src/media_tools/av_parsers.c @@ -3513,6 +3513,9 @@ static void av1_parse_uncompressed_header(GF_BitStream *bs, AV1State *state) if (frame_state->is_first_frame) { frame_state->key_frame = frame_state->seen_seq_header && frame_state->show_frame && frame_state->frame_type == AV1_KEY_FRAME && frame_state->seen_frame_header; } + if (frame_state->frame_type == AV1_SWITCH_FRAME) { + frame_state->switch_frame = GF_TRUE; + } if (frame_state->show_frame && state->decoder_model_info_present_flag && !state->equal_picture_interval) { gf_bs_read_int_log(bs, state->frame_presentation_time_length, "frame_presentation_time"); } From ba4200240a8cc25b9d336389f24f09633c64df7f Mon Sep 17 00:00:00 2001 From: Romain Bouqueau Date: Mon, 10 Nov 2025 14:38:42 -0400 Subject: [PATCH 2/7] AV1 switch frames are marked as packet flags as there is consensus to not consider them as SAPs --- include/gpac/filters.h | 15 +++++++++++++-- include/gpac/isomedia.h | 2 -- src/export.cpp | 2 ++ src/filter_core/filter_pck.c | 17 +++++++++++++++++ src/filter_core/filter_session.h | 4 ++++ src/filters/mux_isom.c | 22 +++++++++++----------- src/filters/reframe_av1.c | 12 +++--------- 7 files changed, 50 insertions(+), 24 deletions(-) diff --git a/include/gpac/filters.h b/include/gpac/filters.h index 33db770c8..53b1d1bd6 100644 --- a/include/gpac/filters.h +++ b/include/gpac/filters.h @@ -4831,8 +4831,6 @@ typedef enum GF_FILTER_SAP_NONE = 0, /*! closed gop no leading */ GF_FILTER_SAP_1, - /*! AV1 switch frame */ - GF_FILTER_SAP_1_SWITCH, /*! closed gop leading */ GF_FILTER_SAP_2, /*! open gop */ @@ -4856,6 +4854,19 @@ GF_Err gf_filter_pck_set_sap(GF_FilterPacket *pck, GF_FilterSAPType sap_type); */ GF_FilterSAPType gf_filter_pck_get_sap(GF_FilterPacket *pck); +/*! Sets packet switch frame flag +\param pck target packet +\param is_switch_frame switch frame flag of the packet +\return error code if any +*/ +GF_Err gf_filter_pck_set_switch_frame(GF_FilterPacket *pck, Bool is_switch_frame); + +/*! Sets packet switch frame flag +\param pck target packet +\return switch frame flag of the packet +*/ +Bool gf_filter_pck_get_switch_frame(GF_FilterPacket *pck); + /*! Sets packet video interlacing flag \param pck target packet diff --git a/include/gpac/isomedia.h b/include/gpac/isomedia.h index 915142a54..e56ba483c 100644 --- a/include/gpac/isomedia.h +++ b/include/gpac/isomedia.h @@ -572,8 +572,6 @@ typedef enum { RAP = 1, /*! sync point (IDR)*/ SAP_TYPE_1 = 1, - /*! sync point (IDR Switch Frame)*/ - SAP_TYPE_1_SWITCH = 1, /*! sync point (IDR)*/ SAP_TYPE_2 = 2, /*! RAP, OpenGOP*/ diff --git a/src/export.cpp b/src/export.cpp index 998f41ae8..bc45be876 100644 --- a/src/export.cpp +++ b/src/export.cpp @@ -2634,6 +2634,8 @@ #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_timescale ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_sap ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_sap ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_switch_frame ) ) +#pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_switch_frame ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_roll_info ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_get_roll_info ) ) #pragma comment (linker, EXPORT_SYMBOL(gf_filter_pck_set_interlaced ) ) diff --git a/src/filter_core/filter_pck.c b/src/filter_core/filter_pck.c index 2631edc6e..4e178c9e5 100644 --- a/src/filter_core/filter_pck.c +++ b/src/filter_core/filter_pck.c @@ -1748,6 +1748,23 @@ GF_FilterSAPType gf_filter_pck_get_sap(GF_FilterPacket *pck) return (GF_FilterSAPType) ( (pck->pck->info.flags & GF_PCK_SAP_MASK) >> GF_PCK_SAP_POS); } +GF_EXPORT +GF_Err gf_filter_pck_set_switch_frame(GF_FilterPacket *pck, Bool is_switch_frame) +{ + PCK_SETTER_CHECK("switch_frame") + pck->info.flags_ext &= ~GF_PCKF_SWITCH_FRAME; + if (is_switch_frame) pck->info.flags_ext |= GF_PCKF_SWITCH_FRAME; + return GF_OK; +} + +GF_EXPORT +Bool gf_filter_pck_get_switch_frame(GF_FilterPacket *pck) +{ + gf_assert(pck); + //get true packet pointer + return (Bool) (pck->pck->info.flags_ext & GF_PCKF_SWITCH_FRAME) ? GF_TRUE : GF_FALSE; +} + GF_EXPORT GF_Err gf_filter_pck_set_roll_info(GF_FilterPacket *pck, s16 roll_count) { diff --git a/src/filter_core/filter_session.h b/src/filter_core/filter_session.h index bf8feb3ca..58b1cbb22 100644 --- a/src/filter_core/filter_session.h +++ b/src/filter_core/filter_session.h @@ -215,11 +215,15 @@ enum GF_PCK_CMD_PID_EOS = 1<timescale units u64 dts, cts; diff --git a/src/filters/mux_isom.c b/src/filters/mux_isom.c index 542ba0ff9..760bb87fd 100644 --- a/src/filters/mux_isom.c +++ b/src/filters/mux_isom.c @@ -4911,7 +4911,7 @@ static GF_Err mp4_mux_process_sample(GF_MP4MuxCtx *ctx, TrackWriter *tkw, GF_Fil if ((tkw->xps_inband==XPS_IB_PPS) && sap_type==GF_FILTER_SAP_3) sap_type=GF_FILTER_SAP_1; } - if (sap_type==GF_FILTER_SAP_1 || sap_type==GF_FILTER_SAP_1_SWITCH) + if (sap_type==GF_FILTER_SAP_1) tkw->sample.IsRAP = SAP_TYPE_1; else if (sap_type==GF_FILTER_SAP_2) tkw->sample.IsRAP = SAP_TYPE_2; @@ -5217,19 +5217,19 @@ static GF_Err mp4_mux_process_sample(GF_MP4MuxCtx *ctx, TrackWriter *tkw, GF_Fil tkw->has_open_gop = GF_TRUE; } - if (sap_type == GF_FILTER_SAP_1_SWITCH) { - if (for_fragment) { + if (gf_filter_pck_get_switch_frame(pck)) { + if (for_fragment) { #ifndef GPAC_DISABLE_ISOM_FRAGMENTS - e = gf_isom_fragment_set_sample_av1_switch_frame_group(ctx->file, tkw->track_id, tkw->samples_in_frag, GF_TRUE); + e = gf_isom_fragment_set_sample_av1_switch_frame_group(ctx->file, tkw->track_id, tkw->samples_in_frag, GF_TRUE); #else - e = GF_NOT_SUPPORTED; + e = GF_NOT_SUPPORTED; #endif - } else { - e = gf_isom_set_sample_av1_switch_frame_group(ctx->file, tkw->track_num, tkw->nb_samples, GF_TRUE); - } - if (e) { - GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Failed to set sample DTS "LLU" SAP 3 in RAP group: %s\n", tkw->sample.DTS, gf_error_to_string(e) )); - } + } else { + e = gf_isom_set_sample_av1_switch_frame_group(ctx->file, tkw->track_num, tkw->nb_samples, GF_TRUE); + } + if (e) { + GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[MP4Mux] Failed to set sample DTS "LLU" SAP 3 in RAP group: %s\n", tkw->sample.DTS, gf_error_to_string(e) )); + } } if (!ctx->noroll) { diff --git a/src/filters/reframe_av1.c b/src/filters/reframe_av1.c index f9093cd47..22d47c39d 100644 --- a/src/filters/reframe_av1.c +++ b/src/filters/reframe_av1.c @@ -492,7 +492,7 @@ static void av1dmx_check_dur(GF_Filter *filter, GF_AV1DmxCtx *ctx) duration += ctx->cur_fps.den; cur_dur += ctx->cur_fps.den; } - if (ctx->bsmode != IAMF && (av1state->frame_state.key_frame || av1state->frame_state.switch_frame) ) + if (ctx->bsmode != IAMF && av1state->frame_state.key_frame) is_sap = GF_TRUE; //only index at I-frame start @@ -1020,7 +1020,6 @@ static GF_Err av1dmx_parse_flush_sample(GF_Filter *filter, GF_AV1DmxCtx *ctx) u32 pck_size = 0; GF_FilterPacket *pck = NULL; u8 *output = NULL; - GF_FilterSAPType sap = GF_FILTER_SAP_NONE; if (!ctx->opid) return GF_NON_COMPLIANT_BITSTREAM; @@ -1051,13 +1050,8 @@ static GF_Err av1dmx_parse_flush_sample(GF_Filter *filter, GF_AV1DmxCtx *ctx) gf_filter_pck_merge_properties(ctx->src_pck, pck); gf_filter_pck_set_cts(pck, ctx->cts); - - if (ctx->state.frame_state.key_frame) { - sap = GF_FILTER_SAP_1; - } else if (ctx->state.frame_state.switch_frame) { - sap = GF_FILTER_SAP_1_SWITCH; - } - gf_filter_pck_set_sap(pck, sap); + gf_filter_pck_set_sap(pck, ctx->state.frame_state.key_frame ? GF_FILTER_SAP_1 : 0); + gf_filter_pck_set_switch_frame(pck, ctx->state.frame_state.switch_frame); if (ctx->is_iamf) { memcpy(output, ctx->iamfstate.temporal_unit_obus, pck_size); From d85af302b845b958df287f611bee89c2dbb0f320 Mon Sep 17 00:00:00 2001 From: Romain Bouqueau Date: Mon, 10 Nov 2025 16:29:21 -0400 Subject: [PATCH 3/7] msvc warning: C requires that a struct or union has at least one member --- include/gpac/internal/isomedia_dev.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/gpac/internal/isomedia_dev.h b/include/gpac/internal/isomedia_dev.h index 6a92d4e44..d335f09f2 100644 --- a/include/gpac/internal/isomedia_dev.h +++ b/include/gpac/internal/isomedia_dev.h @@ -3591,6 +3591,7 @@ typedef struct /*AV1 Switching Entry - Switching Frames*/ typedef struct { + int unused; // C requires that a struct or union has at least one member } GF_AV1SwitchingEntry; /*VisualRandomAccessEntry - 'rap ' type*/ From 03fee41f85e694f10dff90e2f42ab04d8cd3df0d Mon Sep 17 00:00:00 2001 From: Romain Bouqueau Date: Thu, 13 Nov 2025 12:20:12 -0400 Subject: [PATCH 4/7] #3346 : address @jeanlf review comments --- src/filter_core/filter_pck.c | 6 +++--- src/filter_core/filter_session.h | 7 ++----- src/filters/isoffin_read.c | 2 +- src/isomedia/box_funcs.c | 3 +-- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/filter_core/filter_pck.c b/src/filter_core/filter_pck.c index 4e178c9e5..e5b267059 100644 --- a/src/filter_core/filter_pck.c +++ b/src/filter_core/filter_pck.c @@ -1752,8 +1752,8 @@ GF_EXPORT GF_Err gf_filter_pck_set_switch_frame(GF_FilterPacket *pck, Bool is_switch_frame) { PCK_SETTER_CHECK("switch_frame") - pck->info.flags_ext &= ~GF_PCKF_SWITCH_FRAME; - if (is_switch_frame) pck->info.flags_ext |= GF_PCKF_SWITCH_FRAME; + pck->info.flags &= ~GF_PCKF_IS_SWITCH_FRAME; + if (is_switch_frame) pck->info.flags |= GF_PCKF_IS_SWITCH_FRAME; return GF_OK; } @@ -1762,7 +1762,7 @@ Bool gf_filter_pck_get_switch_frame(GF_FilterPacket *pck) { gf_assert(pck); //get true packet pointer - return (Bool) (pck->pck->info.flags_ext & GF_PCKF_SWITCH_FRAME) ? GF_TRUE : GF_FALSE; + return (Bool) (pck->pck->info.flags & GF_PCKF_IS_SWITCH_FRAME) ? GF_TRUE : GF_FALSE; } GF_EXPORT diff --git a/src/filter_core/filter_session.h b/src/filter_core/filter_session.h index 58b1cbb22..2c3738550 100644 --- a/src/filter_core/filter_session.h +++ b/src/filter_core/filter_session.h @@ -196,7 +196,8 @@ enum GF_PCKF_FORCE_MAIN = 1<<12, //only valid when GF_PCK_CMD_PID_EOS is set GF_PCKF_IS_FLUSH = 1<<11, - //RESERVED bits [8,10] + GF_PCKF_IS_SWITCH_FRAME = 1<<10, + //RESERVED bits [8,9] //2 bits for is_leading GF_PCK_ISLEADING_POS = 6, @@ -215,15 +216,11 @@ enum GF_PCK_CMD_PID_EOS = 1<timescale units u64 dts, cts; diff --git a/src/filters/isoffin_read.c b/src/filters/isoffin_read.c index 260c91a4a..657c9511a 100644 --- a/src/filters/isoffin_read.c +++ b/src/filters/isoffin_read.c @@ -1189,7 +1189,7 @@ static Bool isoffin_process_event(GF_Filter *filter, const GF_FilterEvent *evt) } } } - //activate first channel - if input is loaded and we canceled the event, remember we may no onger receive eos signals from source + //activate first channel - if input is loaded and we canceled the event, remember we may no longer receive eos signals from source //this happens because the last playing track may have send a STOP to the source but we here no longer send play if (!read->nb_playing) { read->in_is_eos = (read->input_loaded && cancel_event) ? GF_TRUE : GF_FALSE; diff --git a/src/isomedia/box_funcs.c b/src/isomedia/box_funcs.c index e5431205e..f4a3e1149 100644 --- a/src/isomedia/box_funcs.c +++ b/src/isomedia/box_funcs.c @@ -1174,8 +1174,6 @@ static struct box_registry_entry { SGPD_DEFINE( GF_ISOM_BOX_TYPE_SGPD, sgpd, "stbl traf", GF_ISOM_SAMPLE_GROUP_3GAG, "3gpp"), SGPD_DEFINE( GF_ISOM_BOX_TYPE_SGPD, sgpd, "stbl traf", GF_ISOM_SAMPLE_GROUP_AVCB, "3gpp"), - SGPD_DEFINE( GF_ISOM_BOX_TYPE_SGPD, sgpd, "stbl traf", GF_ISOM_SAMPLE_GROUP_AV1S, "av1-isobmff"), - //internal boxes BOX_DEFINE_S( GF_ISOM_BOX_TYPE_GNRM, gnrm, "stsd", "unknown"), BOX_DEFINE_S( GF_ISOM_BOX_TYPE_GNRV, gnrv, "stsd", "unknown"), @@ -1441,6 +1439,7 @@ static struct box_registry_entry { //AV1 in ISOBMFF boxes BOX_DEFINE_S_CHILD(GF_ISOM_BOX_TYPE_AV01, video_sample_entry, "stsd", "av1"), BOX_DEFINE_S(GF_ISOM_BOX_TYPE_AV1C, av1c, "av01 encv resv ipco dav1", "av1"), + SGPD_DEFINE( GF_ISOM_BOX_TYPE_SGPD, sgpd, "stbl traf", GF_ISOM_SAMPLE_GROUP_AV1S, "av1"), // VP8-9 boxes FBOX_DEFINE_FLAGS_S( GF_ISOM_BOX_TYPE_VPCC, vpcc, "vp08 vp09 vp10 encv resv", 1, 0, "vp"), From 95ecc28f66a1b6bb51e4e68e69fb97223d4a0cc9 Mon Sep 17 00:00:00 2001 From: Romain Bouqueau Date: Mon, 17 Nov 2025 12:19:45 -0400 Subject: [PATCH 5/7] cosmetics --- include/gpac/isomedia.h | 2 +- src/filter_core/filter_pck.c | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/include/gpac/isomedia.h b/include/gpac/isomedia.h index 8d1538348..a570d4749 100644 --- a/include/gpac/isomedia.h +++ b/include/gpac/isomedia.h @@ -7297,7 +7297,7 @@ Bool gf_isom_get_sample_group_info(GF_ISOFile *isom_file, u32 trackNumber, u32 s \param trackNumber the target track \param sample_number sample number to query \param grouping_type four character code of grouping type of sample group description to query -\param grouping_type_parameter grouping type parameter of sample group description to query +\param grouping_type_parameter grouping type parameter of sample group description to query \param sampleGroupDescIndex set to the 1-based sample group description index, or 0 if no sample group of this type is associated \return error if any */ diff --git a/src/filter_core/filter_pck.c b/src/filter_core/filter_pck.c index e5b267059..3aa34ddf0 100644 --- a/src/filter_core/filter_pck.c +++ b/src/filter_core/filter_pck.c @@ -1753,7 +1753,9 @@ GF_Err gf_filter_pck_set_switch_frame(GF_FilterPacket *pck, Bool is_switch_frame { PCK_SETTER_CHECK("switch_frame") pck->info.flags &= ~GF_PCKF_IS_SWITCH_FRAME; - if (is_switch_frame) pck->info.flags |= GF_PCKF_IS_SWITCH_FRAME; + if (is_switch_frame) { + pck->info.flags |= GF_PCKF_IS_SWITCH_FRAME; + } return GF_OK; } From c3771f0ff1692239902c1e6d2c7df8e223485e3e Mon Sep 17 00:00:00 2001 From: Romain Bouqueau Date: Mon, 17 Nov 2025 12:21:24 -0400 Subject: [PATCH 6/7] mp4dmx: forward av1s sample group as a packet flag --- src/filters/isoffin.h | 2 +- src/filters/isoffin_read.c | 6 +++- src/filters/isoffin_read_ch.c | 6 ++-- src/isomedia/isom_read.c | 64 +++++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/src/filters/isoffin.h b/src/filters/isoffin.h index 19b3b7176..d17b077dd 100644 --- a/src/filters/isoffin.h +++ b/src/filters/isoffin.h @@ -161,7 +161,7 @@ typedef struct GF_ISOSample *sample; u64 sample_data_offset, last_valid_sample_data_offset; GF_Err last_state; - Bool sap_3; + Bool sap_3, switch_frame; GF_ISOSampleRollType sap_4_type; s32 roll; u32 xps_mask; diff --git a/src/filters/isoffin_read.c b/src/filters/isoffin_read.c index 657c9511a..db72b7e48 100644 --- a/src/filters/isoffin_read.c +++ b/src/filters/isoffin_read.c @@ -1652,7 +1652,7 @@ static GF_Err isoffin_process(GF_Filter *filter) } gf_filter_pck_set_dts(pck, ch->dts); gf_filter_pck_set_cts(pck, ch->cts + ch->cts_offset); - if (ch->sample->IsRAP==-1) { + if (ch->sample->IsRAP==RAP_REDUNDANT) { gf_filter_pck_set_sap(pck, GF_FILTER_SAP_1); ch->redundant = 1; } else { @@ -1666,6 +1666,10 @@ static GF_Err isoffin_process(GF_Filter *filter) gf_filter_pck_set_roll_info(pck, ch->roll); } + if (ch->switch_frame) { + gf_filter_pck_set_switch_frame(pck, GF_TRUE); + } + sample_dur = ch->sample->duration; if (ch->sample->nb_pack) sample_dur *= ch->sample->nb_pack; diff --git a/src/filters/isoffin_read_ch.c b/src/filters/isoffin_read_ch.c index 913c15125..eb17902ae 100644 --- a/src/filters/isoffin_read_ch.c +++ b/src/filters/isoffin_read_ch.c @@ -611,12 +611,16 @@ void isor_reader_get_sample(ISOMChannel *ch) ch->last_state = GF_OK; ch->sap_3 = GF_FALSE; + ch->switch_frame = GF_FALSE; ch->sap_4_type = 0; ch->roll = 0; if (ch->sample) { gf_isom_get_sample_rap_roll_info(ch->owner->mov, ch->track, ch->sample_num, &ch->sap_3, &ch->sap_4_type, &ch->roll); + GF_Err isom_get_sample_switch_frame(GF_ISOFile *the_file, u32 trackNumber, u32 sample_number, Bool *switch_frame); + isom_get_sample_switch_frame(ch->owner->mov, ch->track, ch->sample_num, &ch->switch_frame); + /*still seeking or not ? 1- when speed is negative, the RAP found is "after" the seek point in playback order since we used backward RAP search: nothing to do 2- otherwise set DTS+CTS to start value @@ -1170,7 +1174,6 @@ void isor_set_sample_groups_and_aux_data(ISOMReader *read, ISOMChannel *ch, GF_F gf_filter_pck_set_property_dyn(pck, szPName, &PROP_DATA_NO_COPY(sai_data, sai_size) ); } - while (1) { GF_Err gf_isom_pop_emsg(GF_ISOFile *the_file, u8 **emsg_data, u32 *emsg_size); u8 *data=NULL; @@ -1180,7 +1183,6 @@ void isor_set_sample_groups_and_aux_data(ISOMReader *read, ISOMChannel *ch, GF_F gf_filter_pck_set_property_str(pck, "emsg", &PROP_DATA_NO_COPY(data, size)); } - } diff --git a/src/isomedia/isom_read.c b/src/isomedia/isom_read.c index c07fa89ce..473c1264c 100644 --- a/src/isomedia/isom_read.c +++ b/src/isomedia/isom_read.c @@ -5051,6 +5051,70 @@ Bool gf_isom_has_cenc_sample_group(GF_ISOFile *the_file, u32 trackNumber, Bool * return GF_TRUE; } +GF_Err isom_get_sample_switch_frame(GF_ISOFile *the_file, u32 trackNumber, u32 sample_number, Bool *switch_frame) +{ + GF_TrackBox *trak; + u32 i, count; + + if (switch_frame) *switch_frame = GF_FALSE; + + trak = gf_isom_get_track_box(the_file, trackNumber); + if (!trak) return GF_BAD_PARAM; + if (!trak->Media->information->sampleTable->sampleGroups) return GF_OK; + + if (!sample_number) { + count = gf_list_count(trak->Media->information->sampleTable->sampleGroupsDescription); + for (i=0; iMedia->information->sampleTable->sampleGroupsDescription, i); + switch (sgdesc->grouping_type) { + case GF_ISOM_SAMPLE_GROUP_AV1S: + if (switch_frame) *switch_frame = GF_TRUE; + break; + } + } + return GF_OK; + } + + count = gf_list_count(trak->Media->information->sampleTable->sampleGroups); + for (i=0; iMedia->information->sampleTable->sampleGroups, i); + for (j=0; jentry_count; j++) { + last_sample_in_entry = first_sample_in_entry + sg->sample_entries[j].sample_count - 1; + if ((sample_numberlast_sample_in_entry)) { + first_sample_in_entry = last_sample_in_entry+1; + continue; + } + /*we found our sample*/ + group_desc_index = sg->sample_entries[j].group_description_index; + break; + } + /*no sampleGroup info associated*/ + if (!group_desc_index) continue; + + sgdesc = NULL; + for (j=0; jMedia->information->sampleTable->sampleGroupsDescription); j++) { + sgdesc = (GF_SampleGroupDescriptionBox*)gf_list_get(trak->Media->information->sampleTable->sampleGroupsDescription, j); + if (sgdesc->grouping_type==sg->grouping_type) break; + sgdesc = NULL; + } + /*no sampleGroup description found for this group (invalid file)*/ + if (!sgdesc) continue; + + switch (sgdesc->grouping_type) { + case GF_ISOM_SAMPLE_GROUP_AV1S: + if (switch_frame) *switch_frame = GF_TRUE; + break; + } + } + return GF_OK; +} + GF_EXPORT GF_Err gf_isom_get_sample_rap_roll_info(GF_ISOFile *the_file, u32 trackNumber, u32 sample_number, Bool *is_rap, GF_ISOSampleRollType *roll_type, s32 *roll_distance) { From 9288842feb714829d9c54b665a0f6b47d9b54fbb Mon Sep 17 00:00:00 2001 From: Romain Bouqueau Date: Mon, 17 Nov 2025 12:22:14 -0400 Subject: [PATCH 7/7] dasher cues: support segmenting at switch frames --- src/filters/dasher.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/filters/dasher.c b/src/filters/dasher.c index 4a72b24ed..60ebe0ab3 100644 --- a/src/filters/dasher.c +++ b/src/filters/dasher.c @@ -9394,6 +9394,7 @@ static GF_Err dasher_process(GF_Filter *filter) } GF_FilterPacket *pck = gf_filter_pid_get_packet(ds->ipid); if (!pck) continue; + u64 ts = gf_filter_pck_get_cts(pck); if (ts != GF_FILTER_NO_TS) { //only adjust if delay is negative (skip), otherwise (delay) keep min ts as is. @@ -9452,6 +9453,7 @@ static GF_Err dasher_process(GF_Filter *filter) if (!ds->request_period_switch) { gf_assert(ds->period == ctx->current_period); pck = gf_filter_pid_get_packet(ds->ipid); + //we may change period after a packet fetch (reconfigure of input pid) if ((ds->period != ctx->current_period) || ds->request_period_switch) { //in closest mode, flush queue @@ -10022,8 +10024,9 @@ static GF_Err dasher_process(GF_Filter *filter) } if (is_cue_split) { - if (!sap_type) { - GF_LOG(ctx->strict_cues ? GF_LOG_ERROR : GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] cue found (sn %d - dts "LLD" - cts "LLD") for PID %s but packet %d is not RAP !\n", cue->sample_num, cue->dts, cue->cts, gf_filter_pid_get_name(ds->ipid), ds->nb_pck)); + Bool switch_frame = gf_filter_pck_get_switch_frame(pck); + if (!sap_type && !switch_frame) { + GF_LOG(ctx->strict_cues ? GF_LOG_ERROR : GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] cue found (sn %d - dts "LLD" - cts "LLD") for PID %s but packet %d is not a RAP nor a switch frame!\n", cue->sample_num, cue->dts, cue->cts, gf_filter_pid_get_name(ds->ipid), ds->nb_pck)); if (ctx->strict_cues) { gf_filter_pid_drop_packet(ds->ipid); gf_filter_pid_set_discard(ds->ipid, GF_TRUE); @@ -10054,7 +10057,6 @@ static GF_Err dasher_process(GF_Filter *filter) ds->set->starts_with_sap = sap_type; } - seg_over = GF_TRUE; if (ds == base_ds) { base_ds->adjusted_next_seg_start = cts;