[FFmpeg-devel] [PATCH 2/2] avcodec/bsf: add av_bsf_join() to chain bitstream filters

Andreas Rheinhardt andreas.rheinhardt at gmail.com
Fri Apr 10 16:21:25 EEST 2020


Marton Balint:
> 
> 
> On Fri, 10 Apr 2020, Andreas Rheinhardt wrote:
> 
>> Marton Balint:
>>>
>>>
>>> On Thu, 9 Apr 2020, James Almer wrote:
>>>
>>>> On 4/9/2020 9:11 PM, Marton Balint wrote:
>>>>>
>>>>>
>>>>> On Fri, 10 Apr 2020, Andreas Rheinhardt wrote:
>>>>>
>>>>>> Marton Balint:
>>>>>>> Signed-off-by: Marton Balint <cus at passwd.hu>
>>>>>>> ---
>>>>>>>  doc/APIchanges       |  3 +++
>>>>>>>  libavcodec/avcodec.h | 19 ++++++++++++++++
>>>>>>>  libavcodec/bsf.c     | 62
>>>>>>> ++++++++++++++++++++++++++++++++++++++++++++++++++++
>>>>>>>  libavcodec/version.h |  2 +-
>>>>>>>  4 files changed, 85 insertions(+), 1 deletion(-)
>>>>>>>
>>>>>>> diff --git a/doc/APIchanges b/doc/APIchanges
>>>>>>> index f1d7eac2ee..1473742139 100644
>>>>>>> --- a/doc/APIchanges
>>>>>>> +++ b/doc/APIchanges
>>>>>>> @@ -15,6 +15,9 @@ libavutil:     2017-10-21
>>>>>>>
>>>>>>>  API changes, most recent first:
>>>>>>>
>>>>>>> +2020-04-xx - xxxxxxxxxx - lavc 58.77.102 - avcodec.h
>>>>>>> +  Add av_bsf_join() to chain bitstream filters.
>>>>>>> +
>>>>>>>  2020-03-29 - xxxxxxxxxx - lavf 58.42.100 - avformat.h
>>>>>>>    av_read_frame() now guarantees to handle uninitialized input
>>>>>>> packets
>>>>>>>    and to return refcounted packets on success.
>>>>>>> diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
>>>>>>> index 8fc0ad92c9..2812055e8a 100644
>>>>>>> --- a/libavcodec/avcodec.h
>>>>>>> +++ b/libavcodec/avcodec.h
>>>>>>> @@ -6056,6 +6056,25 @@ int av_bsf_receive_packet(AVBSFContext *ctx,
>>>>>>> AVPacket *pkt);
>>>>>>>   */
>>>>>>>  void av_bsf_flush(AVBSFContext *ctx);
>>>>>>>
>>>>>>> +/**
>>>>>>> + * Join a new bitstream filter to an already operating one.
>>>>>>> + *
>>>>>>> + * @param[in,out] out_bsf *out_bsf contains the existing, already
>>>>>>> initialized bitstream
>>>>>>> + *                        filter, the new filter will be joined to
>>>>>>> the output of this.
>>>>>>> + *                        It can be NULL, in which case an empty
>>>>>>> filter is assumed.
>>>>>>> + *                        Upon successful return *out_bsf will
>>>>>>> contain the new, combined
>>>>>>> + *                        bitstream filter which will be
>>>>>>> initialized.
>>>>>>> + * @param[in]     bsf     the bitstream filter to be joined to the
>>>>>>> output of *out_bsf.
>>>>>>> + *                        This filter must be uninitialized but it
>>>>>>> must be ready to be
>>>>>>> + *                        initalized, so input codec parameters and
>>>>>>> time base must be
>>>>>>> + *                        set if *out_bsf is NULL, otherwise the
>>>>>>> function will set these
>>>>>>> + *                        automatically based on the output
>>>>>>> parameters of *out_bsf.
>>>>>>> + *                        Upon successful return the bsf will be
>>>>>>> initialized.
>>>>>>> + *
>>>>>>> + * @return 0 on success, negative on error.
>>>>>>
>>>>>> One needs to be more explicit about what happens on error: bsf may be
>>>>>> in a partially initialized state and is essentially only good for
>>>>>> freeing (maybe the bsf parameter should be AVBSFContext **, too, and
>>>>>> automatically free bsf on error?). But IMO we should aim to not
>>>>>> cripple
>>>>>> *out_bsf and document this.
>>>>>>
>>>>>>> + */
>>>>>>> +int av_bsf_join(AVBSFContext **out_bsf, AVBSFContext *bsf);
>>>>>>> +
>>>>>>>  /**
>>>>>>>   * Free a bitstream filter context and everything associated with
>>>>>>> it; write NULL
>>>>>>>   * into the supplied pointer.
>>>>>>> diff --git a/libavcodec/bsf.c b/libavcodec/bsf.c
>>>>>>> index b9fc771a88..1bca28d1ae 100644
>>>>>>> --- a/libavcodec/bsf.c
>>>>>>> +++ b/libavcodec/bsf.c
>>>>>>> @@ -487,6 +487,68 @@ end:
>>>>>>>      return ret;
>>>>>>>  }
>>>>>>>
>>>>>>> +int av_bsf_join(AVBSFContext **out_bsf, AVBSFContext *bsf)
>>>>>>> +{
>>>>>>> +    BSFListContext *lst;
>>>>>>> +    AVBSFContext *obsf = *out_bsf;
>>>>>>> +    AVBSFContext *new_list_bsf = NULL;
>>>>>>> +    int ret;
>>>>>>> +
>>>>>>> +    if (!obsf) {
>>>>>>> +        ret = av_bsf_init(bsf);
>>>>>>> +        if (ret < 0)
>>>>>>> +            return ret;
>>>>>>> +        *out_bsf = bsf;
>>>>>>> +        return 0;
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    if (obsf->filter != &ff_list_bsf) {
>>>>>>> +        ret = av_bsf_alloc(&ff_list_bsf, &new_list_bsf);
>>>>>>> +        if (ret < 0)
>>>>>>> +            return ret;
>>>>>>> +        lst = new_list_bsf->priv_data;
>>>>>>> +        ret = av_dynarray_add_nofree(&lst->bsfs, &lst->nb_bsfs,
>>>>>>> obsf);
>>>>>>> +        if (ret < 0)
>>>>>>> +            goto fail;
>>>>>>> +        ret = avcodec_parameters_copy(new_list_bsf->par_in,
>>>>>>> obsf->par_in);
>>>>>>> +        if (ret < 0)
>>>>>>> +            goto fail;
>>>>>>> +        new_list_bsf->time_base_in = obsf->time_base_in;
>>>>>>> +        obsf = new_list_bsf;
>>>>>>> +    } else {
>>>>>>> +        lst = obsf->priv_data;
>>>>>>> +    }
>>>>>>> +
>>>>>>> +    ret = avcodec_parameters_copy(bsf->par_in,
>>>>>>> lst->bsfs[lst->nb_bsfs-1]->par_out);
>>>>>>> +    if (ret < 0)
>>>>>>> +        goto fail;
>>>>>>> +    bsf->time_base_in = lst->bsfs[lst->nb_bsfs-1]->time_base_out;
>>>>>>> +
>>>>>>> +    ret = av_bsf_init(&bsf);
>>>>>>> +    if (ret < 0)
>>>>>>> +        goto fail;
>>>>>>> +
>>>>>>> +    ret = avcodec_parameters_copy(obsf->par_out, bsf->par_out);
>>>>>>
>>>>>> This will change obsf even on failure (when *out_bsf was already of
>>>>>> type
>>>>>> ff_list_bsf). This is something that ff_stream_add_bitstream_filter()
>>>>>> currently doesn't do. I think we should leave obsf in a mostly
>>>>>> unchanged
>>>>>> state even if it implies an unnecessary allocation (it is really
>>>>>> only a
>>>>>> single allocation). This can be achieved by taking obsf's par_out in
>>>>>> the
>>>>>> else branch above and replacing it with fresh AVCodecParameters. On
>>>>>> success, the old par_out is freed; on failure it is restored.
>>>>>>
>>>>>>> +    if (ret < 0)
>>>>>>> +        goto fail;
>>>>>>> +    obsf->time_base_out = bsf->time_base_out;
>>>>>>> +
>>>>>>> +    ret = av_dynarray_add_nofree(&lst->bsfs, &lst->nb_bsfs, bsf);
>>>>>>> +    if (ret < 0)
>>>>>>> +        goto fail;
>>>>>>> +
>>>>>>> +    *out_bsf = obsf;
>>>>>>> +    return 0;
>>>>>>> +
>>>>>>> +fail:
>>>>>>> +    if (new_list_bsf) {
>>>>>>> +        if (lst->nb_bsfs)
>>>>>>> +            lst->bsfs[0] = NULL;
>>>>>>> +        av_bsf_free(&new_list_bsf);
>>>>>>> +    }
>>>>>>> +    return ret;
>>>>>>> +}
>>>>>>> +
>>>>>>>  static int bsf_parse_single(const char *str, AVBSFList *bsf_lst)
>>>>>>>  {
>>>>>>>      char *bsf_name, *bsf_options_str, *buf;
>>>>>>> diff --git a/libavcodec/version.h b/libavcodec/version.h
>>>>>>> index f4d1d4de21..dadca75430 100644
>>>>>>> --- a/libavcodec/version.h
>>>>>>> +++ b/libavcodec/version.h
>>>>>>> @@ -29,7 +29,7 @@
>>>>>>>
>>>>>>>  #define LIBAVCODEC_VERSION_MAJOR  58
>>>>>>>  #define LIBAVCODEC_VERSION_MINOR  77
>>>>>>> -#define LIBAVCODEC_VERSION_MICRO 101
>>>>>>> +#define LIBAVCODEC_VERSION_MICRO 102
>>>>>>
>>>>>> Adding a new public function always requires a minor bump.
>>>>>>>
>>>>>>>  #define LIBAVCODEC_VERSION_INT 
>>>>>>> AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
>>>>>>>                                                
>>>>>>> LIBAVCODEC_VERSION_MINOR, \
>>>>>>>
>>>>>>
>>>>>> Finally, (I feel bad having to tell you this given that you have
>>>>>> clearly
>>>>>> invested a lot of time in this) we currently have no case where we
>>>>>> need
>>>>>> more than one automatically inserted bitstream filter per stream.
>>>>>> We do
>>>>>> not know whether the current code does the right thing in case more
>>>>>> than
>>>>>> one packet needs to be analyzed to determine whether to insert
>>>>>> bitstream
>>>>>> filters (maybe the packets should instead be cached so that no bsf
>>>>>> misses out on the initial packets?); of course what is the right
>>>>>> thing
>>>>>> might be format and codec-dependent. So I don't know whether we
>>>>>> should
>>>>>> add this function now (given that public API is hard to remove).
>>>>>>
>>>>>> This does not mean that we should not use a bsf_list bsf to simplify
>>>>>> automatic bitstream filtering in mux.c (it really simplifies this
>>>>>> alot
>>>>>> and gets rid of code duplication). But I think we should leave the
>>>>>> topic
>>>>>> of adding a bsf to an already existing bsf open so that it can be
>>>>>> determined when the need indeed arises.
>>>>>
>>>>> I see no sane way of sharing list bsf code other than adding public
>>>>> API,
>>>>> because mux.c is libavformat, bsf.c is libavcodec. This at least
>>>>> allows
>>>>> you to replace AVStreamInternal->nb_bsfcs and AVStreamInternal->bsfcs
>>>>> with a single bitstream filter to which you join additional ones.
>>>>>
>>>>> Unless you want to remove the existing (admittedly unused, and
>>>>> potentially inadequate) support for multiple bitstream filters in
>>>>> AVStreamInternal, I really don't see a way which postpones adding
>>>>> this API.
>>>>
>> When I wrote the above, I thought that we might restrict the code in
>> mux.c to a single bsf by use of the list bsf and that we postpone the
>> exact details of how to add a bsf to an already existing list to the
>> time when it is really needed; in particular, we should not strive to
>> mimic what the current code does in this case because we do not know
>> whether this is indeed the right thing.
>>
>> Thinking about this a bit further has made me realize that there is
>> another problem: It might be that a muxer might need the output of the
>> first bitstream filter in order to determine whether to add another
>> bitstream filter to the bsf chain. This is a usecase that can't be
>> fulfilled with av_bsf_join() and I think it can't be fulfilled with a
>> single list-bsf at all.
>> In this case, one would essentially put what you have put into
>> need_auto_bsf() into the loop to be called at the end of the current
>> filter chain* and if it resulted in a new filter being appended, the
>> packet would of course need to be sent to said new filter.
>> This scenario can definitely not be solved with av_bsf_join() (it's not
>> designed for partially filtered packets). And in this case the code in
>> mux.c will have to deal with multiple bsfs anyway (the old one as well
>> as the new one), so that no simplifications from using a single bsf will
>> be achievable.
>>
>> So my current stance is: Keep support for manual bsf lists in mux.c, but
>> leave the details of how to actually add multiple bsf to the future: The
>> only supported case right now is adding multiple bsf in the same
>> check_packet() call and this should be kept, but there is no reason to
>> already move need_auto_bsf() into the loop. Fixing this is not realistic
>> right now because we don't have a real usecase. And furthermore, it
>> would delay your patchset even further.
>> It also means that the first patch of this patchset should be applied,
>> but not the second.
> 
> But how do you share code then?
> 
> Also I don't follow your logic, if you say that no new API should be
> added until it is used, then why not remove the unused multiple bsf
> lists from AVStreamInternal and re-add it when it is actually needed?
> That is the only way I see to not add duplicated list code.
> 
I reconsidered again. My earlier objection was based on the belief that
there is no way to add an already initialized filter to a bsf(-list).
But there actually is: One just has to set the list's idx so that it
tries to drain the new filter first. This is safe.

So if a muxer ever needs the output of earlier filters to decide whether
to append new filters, this could be solved using the bsf-list API as
follows: check_bitstream() initializes the new bsf and already sends the
packet it received to the new bsf. Then it adds the new bsf to the old
bsf(-list) (this will have to be done in some form of av_bsf_join() and
said function would have to set the internal idx of the list to make it
drain the new bsf first; if the new bsf doesn't output anything yet, it
will automatically try the earlier filters again). Of course, not every
check_bitstream() function would have to do this itself as this part can
be factored out into a common function, just like adding a bsf is now
factored out into ff_stream_add_bitstream_filter().
(It should go without saying that check_bitstream() would no longer
receive a const AVPacket * and that the naming would also have to be
adapted if the above would be implemented.)

So I am now confident that we can do anything that a manual bsf list can
do with the bsf-list API as well.* So I see no reason any more to keep
bsf-lists in mux.c; it only needs to support a single bsf.
But given that we don't have a usecase for multiple automatically
inserted bsf at all at the moment, I also don't see a reason why we
should add av_bsf_join() now.

- Andreas


More information about the ffmpeg-devel mailing list