[FFmpeg-devel] [PATCH 1/2] avcodec/bsf: use simplified algorithm for bsf_list chained filtering

Marton Balint cus at passwd.hu
Fri Apr 17 23:26:02 EEST 2020



On Fri, 17 Apr 2020, Marton Balint wrote:

>
>
> On Fri, 10 Apr 2020, Andreas Rheinhardt wrote:
>
>> Marton Balint:
>>> 
>>> 
>>> On Thu, 9 Apr 2020, Andreas Rheinhardt wrote:
>>> 
>>>> Marton Balint:
>>>>> Based on the one in ffmpeg.c and it is not using an extra flush_idx
>>>>> variable.
>>>>>
>>>>> Signed-off-by: Marton Balint <cus at passwd.hu>
>>>>> ---
>>>>>  libavcodec/bsf.c | 64
>>>>> ++++++++++++++++++++++----------------------------------
>>>>>  1 file changed, 25 insertions(+), 39 deletions(-)
>>>>>
>>>>> diff --git a/libavcodec/bsf.c b/libavcodec/bsf.c
>>>>> index 7b96183e64..b9fc771a88 100644
>>>>> --- a/libavcodec/bsf.c
>>>>> +++ b/libavcodec/bsf.c
>>>>> @@ -18,6 +18,7 @@
>>>>>
>>>>>  #include <string.h>
>>>>>
>>>>> +#include "libavutil/avassert.h"
>>>>>  #include "libavutil/log.h"
>>>>>  #include "libavutil/mem.h"
>>>>>  #include "libavutil/opt.h"
>>>>> @@ -266,7 +267,6 @@ typedef struct BSFListContext {
>>>>>      int nb_bsfs;
>>>>>
>>>>>      unsigned idx;           // index of currently processed BSF
>>>>> -    unsigned flushed_idx;   // index of BSF being flushed
>>>>>
>>>>>      char * item_name;
>>>>>  } BSFListContext;
>>>>> @@ -304,57 +304,43 @@ fail:
>>>>>  static int bsf_list_filter(AVBSFContext *bsf, AVPacket *out)
>>>>>  {
>>>>>      BSFListContext *lst = bsf->priv_data;
>>>>> -    int ret;
>>>>> +    int ret, eof = 0;
>>>>>
>>>>>      if (!lst->nb_bsfs)
>>>>>          return ff_bsf_get_packet_ref(bsf, out);
>>>>>
>>>>>      while (1) {
>>>>> -        if (lst->idx > lst->flushed_idx) {
>>>>> +        /* get a packet from the previous filter up the chain */
>>>>> +        if (lst->idx)
>>>>>              ret = av_bsf_receive_packet(lst->bsfs[lst->idx-1], out);
>>>>> -            if (ret == AVERROR(EAGAIN)) {
>>>>> -                /* no more packets from idx-1, try with previous */
>>>>> -                lst->idx--;
>>>>> -                continue;
>>>>> -            } else if (ret == AVERROR_EOF) {
>>>>> -                /* filter idx-1 is done, continue with idx...nb_bsfs */
>>>>> -                lst->flushed_idx = lst->idx;
>>>>> -                continue;
>>>>
>>>> Is it just me or did this continue make no sense at all? It claims to
>>>> continue with idx...nb_bsf, yet it actually tried to get a new packet
>>>> via ff_bsf_get_packet_ref().
>>> 
>>> Agreed, seems like a bug of the old code.
>>> 
>>>>
>>>>> -            }else if (ret < 0) {
>>>>> -                /* filtering error */
>>>>> -                break;
>>>>> -            }
>>>>> -        } else {
>>>>> +        else
>>>>>              ret = ff_bsf_get_packet_ref(bsf, out);
>>>>> -            if (ret == AVERROR_EOF) {
>>>>> -                lst->idx = lst->flushed_idx;
>>>>> -            } else if (ret < 0)
>>>>> -                break;
>>>>> -        }
>>>>> +        if (ret == AVERROR(EAGAIN)) {
>>>>> +            if (!lst->idx)
>>>>> +                return ret;
>>>>> +            lst->idx--;
>>>>> +            continue;
>>>>> +        } else if (ret == AVERROR_EOF) {
>>>>> +            eof = 1;
>>>>> +        } else if (ret < 0)
>>>>> +            return ret;
>>>>
>>>> If you return here, the chain may not be completely drained (e.g. bsf 0
>>>> may still have packets ready to be output even if bsf 1 returned an
>>>> error while filtering a packet obtained from bsf 0) and despite this
>>>> error, the caller is responsible (according to the doc of
>>>> av_bsf_send/receive_packet) to drain the chain completely before sending
>>>> new packets.
>>> 
>>> I don't see an issue, bsf 0 will be drained on a subsequent call to
>>> bsf_list_filter.
>>> 
>>>> In particular, if you use this in mux.c, you will have to keep calling
>>>> av_bsf_receive_packet() until the chain is drained even when the bsf
>>>> returns an error or when a write error occurs and you have to do this
>>>> even on AV_EF_EXPLODE. (Otherwise there might be a situation where the
>>>> next packet to be written can't even be sent to the chain because it is
>>>> not drained.)
>>> 
>>> How else could it work? You call av_bsf_receive_packet() until you get
>>> AVERROR(EAGAIN) or AVERROR_EOF, as the doc of av_bsf_receive_packet()
>>> says. If you get an other error, it is up to you to decice, either
>>> ignore it and retry draining or report it to the user (it seems we only
>>> do this if AV_EF_EXPLODE is set).
>>
>> I wanted to stress this point because your earlier code [1] was
>> different than what I have just sketched. It did not completely drain
>> the bsf list on errors and therefore had the possibility to completely
>> ignore a packet: Consider the following scenario: One has a list of two
>> bsfs and the first packet can create multiple output packets out of the
>> first input packets. Imagine the second bsf returns an error when it is
>> fed the first of these. Then write_packets_common() would exit without
>> having drained the first filter (if it returns an error or not depends
>> upon AV_EF_EXPLODE). When the caller sends the next packet,
>> auto_bsf_receive_packet() would first try to drain the first bsf. If the
>> second bsf now errors out again, write_packets_common() returns without
>> having tried to send the new packet.
>>
>>> 
>>>>
>>>> (The code in ffmpeg.c is buggy in this regard.)
>>> 
>>> I see one problem only, that you can't differentiate between a permanent
>>> and a temporary av_bsf_receive_packet() error, so a badly written bsf
>>> can get you and infinite loop by its filter call always returning an
>>> error, if you decide to ignore BSF errors.
>>> 
>> Yes. Hopefully we don't have such rogue bitstream filters.
>>
>> - Andreas
>>
>> PS: I forgot to mention something in the first answer: Good job at
>> simplifying this function.
>
> I plan to apply this patch soon.

Pushed.

Regards,
Marton


More information about the ffmpeg-devel mailing list