diff mbox

[FFmpeg-devel,2/5] avcodec: add an AV1 frame merge bitstream filter

Message ID 20191111133615.543-3-jamrial@gmail.com
State Superseded
Headers show

Commit Message

James Almer Nov. 11, 2019, 1:36 p.m. UTC
This BSF takes Temporal Units split across different AVPackets and merges them
by looking for Temporal Delimiter OBUs.

Signed-off-by: James Almer <jamrial@gmail.com>
---
 configure                        |   1 +
 libavcodec/Makefile              |   1 +
 libavcodec/av1_frame_merge_bsf.c | 153 +++++++++++++++++++++++++++++++
 libavcodec/bitstream_filters.c   |   1 +
 4 files changed, 156 insertions(+)
 create mode 100644 libavcodec/av1_frame_merge_bsf.c

Comments

Andreas Rheinhardt Nov. 11, 2019, 3:01 p.m. UTC | #1
James Almer:
> This BSF takes Temporal Units split across different AVPackets and merges them
> by looking for Temporal Delimiter OBUs.
> 
> Signed-off-by: James Almer <jamrial@gmail.com>
> ---
>  configure                        |   1 +
>  libavcodec/Makefile              |   1 +
>  libavcodec/av1_frame_merge_bsf.c | 153 +++++++++++++++++++++++++++++++
>  libavcodec/bitstream_filters.c   |   1 +
>  4 files changed, 156 insertions(+)
>  create mode 100644 libavcodec/av1_frame_merge_bsf.c
> 
> diff --git a/configure b/configure
> index 1de90e93fd..70f60997c1 100755
> --- a/configure
> +++ b/configure
> @@ -3115,6 +3115,7 @@ vc1_parser_select="vc1dsp"
>  
>  # bitstream_filters
>  aac_adtstoasc_bsf_select="adts_header"
> +av1_frame_merge_bsf_select="cbs_av1"
>  av1_frame_split_bsf_select="cbs_av1"
>  av1_metadata_bsf_select="cbs_av1"
>  eac3_core_bsf_select="ac3_parser"
> diff --git a/libavcodec/Makefile b/libavcodec/Makefile
> index b990c6ba87..006a472a6d 100644
> --- a/libavcodec/Makefile
> +++ b/libavcodec/Makefile
> @@ -1075,6 +1075,7 @@ OBJS-$(CONFIG_XMA_PARSER)              += xma_parser.o
>  # bitstream filters
>  OBJS-$(CONFIG_AAC_ADTSTOASC_BSF)          += aac_adtstoasc_bsf.o mpeg4audio.o
>  OBJS-$(CONFIG_AV1_METADATA_BSF)           += av1_metadata_bsf.o
> +OBJS-$(CONFIG_AV1_FRAME_MERGE_BSF)        += av1_frame_merge_bsf.o
>  OBJS-$(CONFIG_AV1_FRAME_SPLIT_BSF)        += av1_frame_split_bsf.o
>  OBJS-$(CONFIG_CHOMP_BSF)                  += chomp_bsf.o
>  OBJS-$(CONFIG_DUMP_EXTRADATA_BSF)         += dump_extradata_bsf.o
> diff --git a/libavcodec/av1_frame_merge_bsf.c b/libavcodec/av1_frame_merge_bsf.c
> new file mode 100644
> index 0000000000..943cfcb426
> --- /dev/null
> +++ b/libavcodec/av1_frame_merge_bsf.c
> @@ -0,0 +1,153 @@
> +/*
> + * Copyright (c) 2019 James Almer <jamrial@gmail.com>
> + *
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +
> +#include "avcodec.h"
> +#include "bsf.h"
> +#include "cbs.h"
> +#include "cbs_av1.h"
> +
> +typedef struct AV1FMergeContext {
> +    CodedBitstreamContext *cbc;
> +    CodedBitstreamFragment temporal_unit;
> +    CodedBitstreamFragment frag;
> +} AV1FMergeContext;
> +
> +static int av1_frame_merge_filter(AVBSFContext *bsf, AVPacket *pkt)
> +{
> +    AV1FMergeContext *ctx = bsf->priv_data;
> +    CodedBitstreamFragment *frag = &ctx->frag, *tu = &ctx->temporal_unit;
> +    int err, i;
> +
> +    err = ff_bsf_get_packet_ref(bsf, pkt);
> +    if (err < 0) {
> +        if (err == AVERROR_EOF && tu->nb_units > 0)

Right now ffmpeg (the cli) does not flush bsf at the end when doing
streamcopy. Can you test whether the following patch works with this bsf?
https://github.com/mkver/FFmpeg/commit/340a091f5f6b474de096a8b64dd0ce3a8c7f6c24

> +            goto eof;
> +        return err;
> +    }
> +
> +    err = ff_cbs_read_packet(ctx->cbc, frag, pkt);
> +    if (err < 0) {
> +        av_log(bsf, AV_LOG_ERROR, "Failed to read packet.\n");
> +        goto fail;
> +    }
> +
> +    if (frag->nb_units == 0) {
> +        av_log(bsf, AV_LOG_ERROR, "No OBU in packet.\n");
> +        err = AVERROR_INVALIDDATA;
> +        goto fail;
> +    }
> +
> +    if (tu->nb_units == 0 && frag->units[0].type != AV1_OBU_TEMPORAL_DELIMITER) {
> +        av_log(bsf, AV_LOG_ERROR, "Missing Temporal Delimiter.\n");
> +        err = AVERROR_INVALIDDATA;
> +        goto fail;
> +    }
> +
> +    if (tu->nb_units > 0 && frag->units[0].type == AV1_OBU_TEMPORAL_DELIMITER) {
> +eof:
> +        err = ff_cbs_write_packet(ctx->cbc, pkt, tu);
> +        if (err < 0) {
> +            av_log(bsf, AV_LOG_ERROR, "Failed to write packet.\n");
> +            goto fail;
> +        }
> +        ff_cbs_fragment_reset(ctx->cbc, tu);
> +
> +        for (i = 0; i < frag->nb_units; i++) {
> +            if (i && frag->units[i].type == AV1_OBU_TEMPORAL_DELIMITER) {
> +                av_log(bsf, AV_LOG_ERROR, "Temporal Delimiter in the middle of a packet.\n");
> +                err = AVERROR_INVALIDDATA;
> +                goto fail;
> +            }
> +            err = ff_cbs_insert_unit_content(ctx->cbc, tu, -1, frag->units[i].type,
> +                                             frag->units[i].content, frag->units[i].content_ref);
> +            if (err < 0)
> +                goto fail;
> +        }
> +        ff_cbs_fragment_reset(ctx->cbc, frag);
> +
> +        return err;
> +    }
> +
> +    for (i = 0; i < frag->nb_units; i++) {
> +        if (i && frag->units[i].type == AV1_OBU_TEMPORAL_DELIMITER) {
> +            av_log(bsf, AV_LOG_ERROR, "Temporal Delimiter in the middle of a packet.\n");
> +            err = AVERROR_INVALIDDATA;
> +            goto fail;
> +        }
> +        err = ff_cbs_insert_unit_content(ctx->cbc, tu, -1, frag->units[i].type,
> +                                         frag->units[i].content, frag->units[i].content_ref);
> +        if (err < 0)
> +            goto fail;
> +    }
> +    ff_cbs_fragment_reset(ctx->cbc, frag);
> +    av_packet_unref(pkt);
> +
> +    return err < 0 ? err : AVERROR(EAGAIN);
> +
> +fail:
> +    ff_cbs_fragment_reset(ctx->cbc, tu);
> +    ff_cbs_fragment_reset(ctx->cbc, frag);
> +    av_packet_unref(pkt);
> +
> +    return err;
> +}
> +
> +static int av1_frame_merge_init(AVBSFContext *bsf)
> +{
> +    AV1FMergeContext *ctx = bsf->priv_data;
> +    int ret;
> +
> +    ret = ff_cbs_init(&ctx->cbc, AV_CODEC_ID_AV1, bsf);
> +    if (ret < 0)
> +        return ret;
> +
> +    return 0;
> +}
> +
> +static void av1_frame_merge_flush(AVBSFContext *bsf)
> +{
> +    AV1FMergeContext *ctx = bsf->priv_data;
> +
> +    ff_cbs_fragment_reset(ctx->cbc, &ctx->temporal_unit);
> +    ff_cbs_fragment_reset(ctx->cbc, &ctx->frag);
> +}
> +
> +static void av1_frame_merge_close(AVBSFContext *bsf)
> +{
> +    AV1FMergeContext *ctx = bsf->priv_data;
> +
> +    ff_cbs_fragment_free(ctx->cbc, &ctx->temporal_unit);
> +    ff_cbs_fragment_free(ctx->cbc, &ctx->frag);
> +    ff_cbs_close(&ctx->cbc);
> +}
> +
> +static const enum AVCodecID av1_frame_merge_codec_ids[] = {
> +    AV_CODEC_ID_AV1, AV_CODEC_ID_NONE,
> +};
> +
> +const AVBitStreamFilter ff_av1_frame_merge_bsf = {
> +    .name           = "av1_frame_merge",
> +    .priv_data_size = sizeof(AV1FMergeContext),
> +    .init           = av1_frame_merge_init,
> +    .flush          = av1_frame_merge_flush,
> +    .close          = av1_frame_merge_close,
> +    .filter         = av1_frame_merge_filter,
> +    .codec_ids      = av1_frame_merge_codec_ids,
> +};
> diff --git a/libavcodec/bitstream_filters.c b/libavcodec/bitstream_filters.c
> index 463003966a..6b5ffe4d70 100644
> --- a/libavcodec/bitstream_filters.c
> +++ b/libavcodec/bitstream_filters.c
> @@ -25,6 +25,7 @@
>  #include "bsf.h"
>  
>  extern const AVBitStreamFilter ff_aac_adtstoasc_bsf;
> +extern const AVBitStreamFilter ff_av1_frame_merge_bsf;
>  extern const AVBitStreamFilter ff_av1_frame_split_bsf;
>  extern const AVBitStreamFilter ff_av1_metadata_bsf;
>  extern const AVBitStreamFilter ff_chomp_bsf;
>
James Almer Nov. 11, 2019, 6:58 p.m. UTC | #2
On 11/11/2019 12:01 PM, Andreas Rheinhardt wrote:
> James Almer:
>> This BSF takes Temporal Units split across different AVPackets and merges them
>> by looking for Temporal Delimiter OBUs.
>>
>> Signed-off-by: James Almer <jamrial@gmail.com>
>> ---
>>  configure                        |   1 +
>>  libavcodec/Makefile              |   1 +
>>  libavcodec/av1_frame_merge_bsf.c | 153 +++++++++++++++++++++++++++++++
>>  libavcodec/bitstream_filters.c   |   1 +
>>  4 files changed, 156 insertions(+)
>>  create mode 100644 libavcodec/av1_frame_merge_bsf.c
>>
>> diff --git a/configure b/configure
>> index 1de90e93fd..70f60997c1 100755
>> --- a/configure
>> +++ b/configure
>> @@ -3115,6 +3115,7 @@ vc1_parser_select="vc1dsp"
>>  
>>  # bitstream_filters
>>  aac_adtstoasc_bsf_select="adts_header"
>> +av1_frame_merge_bsf_select="cbs_av1"
>>  av1_frame_split_bsf_select="cbs_av1"
>>  av1_metadata_bsf_select="cbs_av1"
>>  eac3_core_bsf_select="ac3_parser"
>> diff --git a/libavcodec/Makefile b/libavcodec/Makefile
>> index b990c6ba87..006a472a6d 100644
>> --- a/libavcodec/Makefile
>> +++ b/libavcodec/Makefile
>> @@ -1075,6 +1075,7 @@ OBJS-$(CONFIG_XMA_PARSER)              += xma_parser.o
>>  # bitstream filters
>>  OBJS-$(CONFIG_AAC_ADTSTOASC_BSF)          += aac_adtstoasc_bsf.o mpeg4audio.o
>>  OBJS-$(CONFIG_AV1_METADATA_BSF)           += av1_metadata_bsf.o
>> +OBJS-$(CONFIG_AV1_FRAME_MERGE_BSF)        += av1_frame_merge_bsf.o
>>  OBJS-$(CONFIG_AV1_FRAME_SPLIT_BSF)        += av1_frame_split_bsf.o
>>  OBJS-$(CONFIG_CHOMP_BSF)                  += chomp_bsf.o
>>  OBJS-$(CONFIG_DUMP_EXTRADATA_BSF)         += dump_extradata_bsf.o
>> diff --git a/libavcodec/av1_frame_merge_bsf.c b/libavcodec/av1_frame_merge_bsf.c
>> new file mode 100644
>> index 0000000000..943cfcb426
>> --- /dev/null
>> +++ b/libavcodec/av1_frame_merge_bsf.c
>> @@ -0,0 +1,153 @@
>> +/*
>> + * Copyright (c) 2019 James Almer <jamrial@gmail.com>
>> + *
>> + * This file is part of FFmpeg.
>> + *
>> + * FFmpeg is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU Lesser General Public
>> + * License as published by the Free Software Foundation; either
>> + * version 2.1 of the License, or (at your option) any later version.
>> + *
>> + * FFmpeg is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>> + * Lesser General Public License for more details.
>> + *
>> + * You should have received a copy of the GNU Lesser General Public
>> + * License along with FFmpeg; if not, write to the Free Software
>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
>> + */
>> +
>> +#include "avcodec.h"
>> +#include "bsf.h"
>> +#include "cbs.h"
>> +#include "cbs_av1.h"
>> +
>> +typedef struct AV1FMergeContext {
>> +    CodedBitstreamContext *cbc;
>> +    CodedBitstreamFragment temporal_unit;
>> +    CodedBitstreamFragment frag;
>> +} AV1FMergeContext;
>> +
>> +static int av1_frame_merge_filter(AVBSFContext *bsf, AVPacket *pkt)
>> +{
>> +    AV1FMergeContext *ctx = bsf->priv_data;
>> +    CodedBitstreamFragment *frag = &ctx->frag, *tu = &ctx->temporal_unit;
>> +    int err, i;
>> +
>> +    err = ff_bsf_get_packet_ref(bsf, pkt);
>> +    if (err < 0) {
>> +        if (err == AVERROR_EOF && tu->nb_units > 0)
> 
> Right now ffmpeg (the cli) does not flush bsf at the end when doing
> streamcopy.

Yeah, i noticed that when trying to do an av1_frame_split ->
av1_frame_merge test run to compare input and output.

Also, you just made me realize I'm not copying packet props after the
merge. The AV1 parser was doing part of that job for me by flagging key
frames within the annexb demuxer, but a cli usage of this bsf wouldn't
get that treatment. I fixed that in the V2 i just sent.

> Can you test whether the following patch works with this bsf?
> https://github.com/mkver/FFmpeg/commit/340a091f5f6b474de096a8b64dd0ce3a8c7f6c24

It does flush on EOF after applying that patch, but there's another
issue in do_streamcopy(), where because the first packet (a key frame)
is sent to the bsf but not returned until a second packet has been
submitted, by the time do_streamcopy() is called that second time, the
"!ost->frame_number && !(pkt->flags & AV_PKT_FLAG_KEY)" check will fail
because ost->frame_number was never bumped to 1, and the second packet
is of course not a key frame.

You can check that with V2 by trying to do a codec copy with and without
-copyinkf.
Andreas Rheinhardt Nov. 11, 2019, 7:57 p.m. UTC | #3
James Almer:
> On 11/11/2019 12:01 PM, Andreas Rheinhardt wrote:
>> James Almer:
>>> This BSF takes Temporal Units split across different AVPackets and merges them
>>> by looking for Temporal Delimiter OBUs.
>>>
>>> Signed-off-by: James Almer <jamrial@gmail.com>
>>> ---
>>>  configure                        |   1 +
>>>  libavcodec/Makefile              |   1 +
>>>  libavcodec/av1_frame_merge_bsf.c | 153 +++++++++++++++++++++++++++++++
>>>  libavcodec/bitstream_filters.c   |   1 +
>>>  4 files changed, 156 insertions(+)
>>>  create mode 100644 libavcodec/av1_frame_merge_bsf.c
>>>
>>> diff --git a/configure b/configure
>>> index 1de90e93fd..70f60997c1 100755
>>> --- a/configure
>>> +++ b/configure
>>> @@ -3115,6 +3115,7 @@ vc1_parser_select="vc1dsp"
>>>  
>>>  # bitstream_filters
>>>  aac_adtstoasc_bsf_select="adts_header"
>>> +av1_frame_merge_bsf_select="cbs_av1"
>>>  av1_frame_split_bsf_select="cbs_av1"
>>>  av1_metadata_bsf_select="cbs_av1"
>>>  eac3_core_bsf_select="ac3_parser"
>>> diff --git a/libavcodec/Makefile b/libavcodec/Makefile
>>> index b990c6ba87..006a472a6d 100644
>>> --- a/libavcodec/Makefile
>>> +++ b/libavcodec/Makefile
>>> @@ -1075,6 +1075,7 @@ OBJS-$(CONFIG_XMA_PARSER)              += xma_parser.o
>>>  # bitstream filters
>>>  OBJS-$(CONFIG_AAC_ADTSTOASC_BSF)          += aac_adtstoasc_bsf.o mpeg4audio.o
>>>  OBJS-$(CONFIG_AV1_METADATA_BSF)           += av1_metadata_bsf.o
>>> +OBJS-$(CONFIG_AV1_FRAME_MERGE_BSF)        += av1_frame_merge_bsf.o
>>>  OBJS-$(CONFIG_AV1_FRAME_SPLIT_BSF)        += av1_frame_split_bsf.o
>>>  OBJS-$(CONFIG_CHOMP_BSF)                  += chomp_bsf.o
>>>  OBJS-$(CONFIG_DUMP_EXTRADATA_BSF)         += dump_extradata_bsf.o
>>> diff --git a/libavcodec/av1_frame_merge_bsf.c b/libavcodec/av1_frame_merge_bsf.c
>>> new file mode 100644
>>> index 0000000000..943cfcb426
>>> --- /dev/null
>>> +++ b/libavcodec/av1_frame_merge_bsf.c
>>> @@ -0,0 +1,153 @@
>>> +/*
>>> + * Copyright (c) 2019 James Almer <jamrial@gmail.com>
>>> + *
>>> + * This file is part of FFmpeg.
>>> + *
>>> + * FFmpeg is free software; you can redistribute it and/or
>>> + * modify it under the terms of the GNU Lesser General Public
>>> + * License as published by the Free Software Foundation; either
>>> + * version 2.1 of the License, or (at your option) any later version.
>>> + *
>>> + * FFmpeg is distributed in the hope that it will be useful,
>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>>> + * Lesser General Public License for more details.
>>> + *
>>> + * You should have received a copy of the GNU Lesser General Public
>>> + * License along with FFmpeg; if not, write to the Free Software
>>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
>>> + */
>>> +
>>> +#include "avcodec.h"
>>> +#include "bsf.h"
>>> +#include "cbs.h"
>>> +#include "cbs_av1.h"
>>> +
>>> +typedef struct AV1FMergeContext {
>>> +    CodedBitstreamContext *cbc;
>>> +    CodedBitstreamFragment temporal_unit;
>>> +    CodedBitstreamFragment frag;
>>> +} AV1FMergeContext;
>>> +
>>> +static int av1_frame_merge_filter(AVBSFContext *bsf, AVPacket *pkt)
>>> +{
>>> +    AV1FMergeContext *ctx = bsf->priv_data;
>>> +    CodedBitstreamFragment *frag = &ctx->frag, *tu = &ctx->temporal_unit;
>>> +    int err, i;
>>> +
>>> +    err = ff_bsf_get_packet_ref(bsf, pkt);
>>> +    if (err < 0) {
>>> +        if (err == AVERROR_EOF && tu->nb_units > 0)
>>
>> Right now ffmpeg (the cli) does not flush bsf at the end when doing
>> streamcopy.
> 
> Yeah, i noticed that when trying to do an av1_frame_split ->
> av1_frame_merge test run to compare input and output.
> 
> Also, you just made me realize I'm not copying packet props after the
> merge. The AV1 parser was doing part of that job for me by flagging key
> frames within the annexb demuxer, but a cli usage of this bsf wouldn't
> get that treatment. I fixed that in the V2 i just sent.
> 
>> Can you test whether the following patch works with this bsf?
>> https://github.com/mkver/FFmpeg/commit/340a091f5f6b474de096a8b64dd0ce3a8c7f6c24
> 
> It does flush on EOF after applying that patch, but there's another
> issue in do_streamcopy(), where because the first packet (a key frame)
> is sent to the bsf but not returned until a second packet has been
> submitted, by the time do_streamcopy() is called that second time, the
> "!ost->frame_number && !(pkt->flags & AV_PKT_FLAG_KEY)" check will fail
> because ost->frame_number was never bumped to 1, and the second packet
> is of course not a key frame.
> 
> You can check that with V2 by trying to do a codec copy with and without
> -copyinkf.

I am well aware of that:
https://github.com/mkver/FFmpeg/commit/f94e1b062a125e7de2a9f12c09e9a3894263b309
contains a workaround for this for vp9_raw_reorder.
Will look into your new version soon.

- Andreas

PS: Even with this patch ffmpeg cli does not flush when it stops
because of the limits (e.g. -t or -to time has been reached). I once
pondered writing a bitstream filter that updates the bitstream and the
flac streaminfo (e.g. the md5) at the end to make it possible to trim
flac files without reencoding, but this won't work (in the cli) unless
this is fixed.
diff mbox

Patch

diff --git a/configure b/configure
index 1de90e93fd..70f60997c1 100755
--- a/configure
+++ b/configure
@@ -3115,6 +3115,7 @@  vc1_parser_select="vc1dsp"
 
 # bitstream_filters
 aac_adtstoasc_bsf_select="adts_header"
+av1_frame_merge_bsf_select="cbs_av1"
 av1_frame_split_bsf_select="cbs_av1"
 av1_metadata_bsf_select="cbs_av1"
 eac3_core_bsf_select="ac3_parser"
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index b990c6ba87..006a472a6d 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -1075,6 +1075,7 @@  OBJS-$(CONFIG_XMA_PARSER)              += xma_parser.o
 # bitstream filters
 OBJS-$(CONFIG_AAC_ADTSTOASC_BSF)          += aac_adtstoasc_bsf.o mpeg4audio.o
 OBJS-$(CONFIG_AV1_METADATA_BSF)           += av1_metadata_bsf.o
+OBJS-$(CONFIG_AV1_FRAME_MERGE_BSF)        += av1_frame_merge_bsf.o
 OBJS-$(CONFIG_AV1_FRAME_SPLIT_BSF)        += av1_frame_split_bsf.o
 OBJS-$(CONFIG_CHOMP_BSF)                  += chomp_bsf.o
 OBJS-$(CONFIG_DUMP_EXTRADATA_BSF)         += dump_extradata_bsf.o
diff --git a/libavcodec/av1_frame_merge_bsf.c b/libavcodec/av1_frame_merge_bsf.c
new file mode 100644
index 0000000000..943cfcb426
--- /dev/null
+++ b/libavcodec/av1_frame_merge_bsf.c
@@ -0,0 +1,153 @@ 
+/*
+ * Copyright (c) 2019 James Almer <jamrial@gmail.com>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "avcodec.h"
+#include "bsf.h"
+#include "cbs.h"
+#include "cbs_av1.h"
+
+typedef struct AV1FMergeContext {
+    CodedBitstreamContext *cbc;
+    CodedBitstreamFragment temporal_unit;
+    CodedBitstreamFragment frag;
+} AV1FMergeContext;
+
+static int av1_frame_merge_filter(AVBSFContext *bsf, AVPacket *pkt)
+{
+    AV1FMergeContext *ctx = bsf->priv_data;
+    CodedBitstreamFragment *frag = &ctx->frag, *tu = &ctx->temporal_unit;
+    int err, i;
+
+    err = ff_bsf_get_packet_ref(bsf, pkt);
+    if (err < 0) {
+        if (err == AVERROR_EOF && tu->nb_units > 0)
+            goto eof;
+        return err;
+    }
+
+    err = ff_cbs_read_packet(ctx->cbc, frag, pkt);
+    if (err < 0) {
+        av_log(bsf, AV_LOG_ERROR, "Failed to read packet.\n");
+        goto fail;
+    }
+
+    if (frag->nb_units == 0) {
+        av_log(bsf, AV_LOG_ERROR, "No OBU in packet.\n");
+        err = AVERROR_INVALIDDATA;
+        goto fail;
+    }
+
+    if (tu->nb_units == 0 && frag->units[0].type != AV1_OBU_TEMPORAL_DELIMITER) {
+        av_log(bsf, AV_LOG_ERROR, "Missing Temporal Delimiter.\n");
+        err = AVERROR_INVALIDDATA;
+        goto fail;
+    }
+
+    if (tu->nb_units > 0 && frag->units[0].type == AV1_OBU_TEMPORAL_DELIMITER) {
+eof:
+        err = ff_cbs_write_packet(ctx->cbc, pkt, tu);
+        if (err < 0) {
+            av_log(bsf, AV_LOG_ERROR, "Failed to write packet.\n");
+            goto fail;
+        }
+        ff_cbs_fragment_reset(ctx->cbc, tu);
+
+        for (i = 0; i < frag->nb_units; i++) {
+            if (i && frag->units[i].type == AV1_OBU_TEMPORAL_DELIMITER) {
+                av_log(bsf, AV_LOG_ERROR, "Temporal Delimiter in the middle of a packet.\n");
+                err = AVERROR_INVALIDDATA;
+                goto fail;
+            }
+            err = ff_cbs_insert_unit_content(ctx->cbc, tu, -1, frag->units[i].type,
+                                             frag->units[i].content, frag->units[i].content_ref);
+            if (err < 0)
+                goto fail;
+        }
+        ff_cbs_fragment_reset(ctx->cbc, frag);
+
+        return err;
+    }
+
+    for (i = 0; i < frag->nb_units; i++) {
+        if (i && frag->units[i].type == AV1_OBU_TEMPORAL_DELIMITER) {
+            av_log(bsf, AV_LOG_ERROR, "Temporal Delimiter in the middle of a packet.\n");
+            err = AVERROR_INVALIDDATA;
+            goto fail;
+        }
+        err = ff_cbs_insert_unit_content(ctx->cbc, tu, -1, frag->units[i].type,
+                                         frag->units[i].content, frag->units[i].content_ref);
+        if (err < 0)
+            goto fail;
+    }
+    ff_cbs_fragment_reset(ctx->cbc, frag);
+    av_packet_unref(pkt);
+
+    return err < 0 ? err : AVERROR(EAGAIN);
+
+fail:
+    ff_cbs_fragment_reset(ctx->cbc, tu);
+    ff_cbs_fragment_reset(ctx->cbc, frag);
+    av_packet_unref(pkt);
+
+    return err;
+}
+
+static int av1_frame_merge_init(AVBSFContext *bsf)
+{
+    AV1FMergeContext *ctx = bsf->priv_data;
+    int ret;
+
+    ret = ff_cbs_init(&ctx->cbc, AV_CODEC_ID_AV1, bsf);
+    if (ret < 0)
+        return ret;
+
+    return 0;
+}
+
+static void av1_frame_merge_flush(AVBSFContext *bsf)
+{
+    AV1FMergeContext *ctx = bsf->priv_data;
+
+    ff_cbs_fragment_reset(ctx->cbc, &ctx->temporal_unit);
+    ff_cbs_fragment_reset(ctx->cbc, &ctx->frag);
+}
+
+static void av1_frame_merge_close(AVBSFContext *bsf)
+{
+    AV1FMergeContext *ctx = bsf->priv_data;
+
+    ff_cbs_fragment_free(ctx->cbc, &ctx->temporal_unit);
+    ff_cbs_fragment_free(ctx->cbc, &ctx->frag);
+    ff_cbs_close(&ctx->cbc);
+}
+
+static const enum AVCodecID av1_frame_merge_codec_ids[] = {
+    AV_CODEC_ID_AV1, AV_CODEC_ID_NONE,
+};
+
+const AVBitStreamFilter ff_av1_frame_merge_bsf = {
+    .name           = "av1_frame_merge",
+    .priv_data_size = sizeof(AV1FMergeContext),
+    .init           = av1_frame_merge_init,
+    .flush          = av1_frame_merge_flush,
+    .close          = av1_frame_merge_close,
+    .filter         = av1_frame_merge_filter,
+    .codec_ids      = av1_frame_merge_codec_ids,
+};
diff --git a/libavcodec/bitstream_filters.c b/libavcodec/bitstream_filters.c
index 463003966a..6b5ffe4d70 100644
--- a/libavcodec/bitstream_filters.c
+++ b/libavcodec/bitstream_filters.c
@@ -25,6 +25,7 @@ 
 #include "bsf.h"
 
 extern const AVBitStreamFilter ff_aac_adtstoasc_bsf;
+extern const AVBitStreamFilter ff_av1_frame_merge_bsf;
 extern const AVBitStreamFilter ff_av1_frame_split_bsf;
 extern const AVBitStreamFilter ff_av1_metadata_bsf;
 extern const AVBitStreamFilter ff_chomp_bsf;