[FFmpeg-devel] [PATCH v2] avcodec: add a get_encoder_buffer() callback to AVCodecContext
James Almer
jamrial at gmail.com
Mon Mar 8 22:31:55 EET 2021
On 2/22/2021 7:27 PM, James Almer wrote:
> On 2/21/2021 6:04 PM, James Almer wrote:
>> On 2/21/2021 5:29 PM, Mark Thompson wrote:
>>> On 21/02/2021 20:00, James Almer wrote:
>>>> On 2/21/2021 4:13 PM, Mark Thompson wrote:
>>>>> On 21/02/2021 17:35, James Almer wrote:
>>>>>> This callback is functionally the same as get_buffer2() is for
>>>>>> decoders, and
>>>>>> implements for the new encode API the functionality of the old
>>>>>> encode API had
>>>>>> where the user could provide their own buffers.
>>>>>>
>>>>>> Signed-off-by: James Almer <jamrial at gmail.com>
>>>>>> ---
>>>>>> Used the names Lynne suggested this time, plus a line about how
>>>>>> the callback
>>>>>> must be thread safe.
>>>>>>
>>>>>> libavcodec/avcodec.h | 45 ++++++++++++++++++++++++++++++++++++
>>>>>> libavcodec/codec.h | 8 ++++---
>>>>>> libavcodec/encode.c | 54
>>>>>> +++++++++++++++++++++++++++++++++++++++++++-
>>>>>> libavcodec/encode.h | 8 +++++++
>>>>>> libavcodec/options.c | 1 +
>>>>>> 5 files changed, 112 insertions(+), 4 deletions(-)
>>>>>>
>>>>>> diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
>>>>>> index 7dbf083a24..e60eb16ce1 100644
>>>>>> --- a/libavcodec/avcodec.h
>>>>>> +++ b/libavcodec/avcodec.h
>>>>>> @@ -513,6 +513,11 @@ typedef struct AVProducerReferenceTime {
>>>>>> */
>>>>>> #define AV_GET_BUFFER_FLAG_REF (1 << 0)
>>>>>> +/**
>>>>>> + * The encoder will keep a reference to the packet and may reuse
>>>>>> it later.
>>>>>> + */
>>>>>> +#define AV_GET_ENCODER_BUFFER_FLAG_REF (1 << 0)
>>>>>> +
>>>>>> struct AVCodecInternal;
>>>>>> /**
>>>>>> @@ -2346,6 +2351,39 @@ typedef struct AVCodecContext {
>>>>>> * - encoding: set by user
>>>>>> */
>>>>>> int export_side_data;
>>>>>> +
>>>>>> + /**
>>>>>> + * This callback is called at the beginning of each packet to
>>>>>> get a data
>>>>>> + * buffer for it.
>>>>>> + *
>>>>>> + * The following field will be set in the packet before this
>>>>>> callback is
>>>>>> + * called:
>>>>>> + * - size
>>>>>> + * This callback must use the above value to calculate the
>>>>>> required buffer size,
>>>>>> + * which must padded by at least AV_INPUT_BUFFER_PADDING_SIZE
>>>>>> bytes.
>>>>>> + *
>>>>>> + * This callback must fill the following fields in the packet:
>>>>>> + * - data
>>>>>
>>>>> Is the data pointer allowed to be in write-only memory?
>>>>
>>>> I'm not sure what the use case for this would be, so probably no?
>>>
>>> The two use-cases I see for this API are:
>>>
>>> * You want to avoid a copy when combining the output with something
>>> else. E.g. you pass a pointer to the block of memory following where
>>> you are going to put your header data (for something you are going to
>>> send over the network, say).
>>>
>>> * You want to avoid a copy when passing the output directly to
>>> something external. E.g. you pass a pointer to a memory-mapped
>>> device buffer (such as a V4L2 buffer, say).
>>>
>>> In the second case, write-only memory on an external device seems
>>> possible, as does memory which is, say, readable but uncached, so
>>> reading it is a really bad idea.
>>
>> Allowing the second case would depend on how encoders behave. Some may
>> attempt to read data already written to the output packet. It's not
>> like all of them allocate the packet, do a memcpy from an internal
>> buffer, then return.
>> There is also the flag meant to signal that the encoder will keep a
>> reference to the packet around, which more or less implies it will be
>> read later in the encoding process.
>>
>> The doxy for avcodec_encode_video2(), which allowed the user to
>> provide their own buffers in the output packet, does not mention any
>> kind of requirement for the data pointer, so I don't think we can say
>> it's an allowed scenario here either.
>>
>>>
>>>>> Does it have any alignment requirements?
>>>>
>>>> No, just padding. AVPacket doesn't require alignment for the payload.
>>>
>>> I think say that explicitly. avcodec_default_get_encoder_buffer()
>>> does give you aligned memory, even though it isn't needed.
>>
>> Would saying "There's no alignment requirement for the data pointer"
>> add anything of value to the doxy? If i don't mention any kind of
>> alignment requirement, it's because there isn't any, and it's implicit.
>> I listed the requirements the user needs to keep in mind, like the
>> padding and the need for an AVBufferRef. But if you think it's worth
>> adding, then sure.
>>
>>>
>>>>>> + * - buf must contain a pointer to an AVBufferRef structure.
>>>>>> The packet's
>>>>>> + * data pointer must be contained in it.
>>>>>> + * See: av_buffer_create(), av_buffer_alloc(), and
>>>>>> av_buffer_ref().
>>>>>> + *
>>>>>> + * If AV_CODEC_CAP_DR1 is not set then get_encoder_buffer()
>>>>>> must call
>>>>>> + * avcodec_default_get_encoder_buffer() instead of providing
>>>>>> a buffer allocated by
>>>>>> + * some other means.
>>>>>> + *
>>>>>> + * If AV_GET_ENCODER_BUFFER_FLAG_REF is set in flags then the
>>>>>> packet may be reused
>>>>>> + * (read and/or written to if it is writable) later by
>>>>>> libavcodec.
>>>>>> + *
>>>>>> + * This callback must be thread-safe, as when frame
>>>>>> multithreading is used, it may
>>>>>> + * be called from multiple threads simultaneously.
>>>>>
>>>>> Allowing simulatenous calls feels unexpectedly tricky. Is it
>>>>> really necessary?
>>>>
>>>> This was a suggestion by Lynne, i personally don't know. We support
>>>> frame threading encoding (For intra-only codecs), but currently
>>>> ff_alloc_packet2() does not seem to be thread safe, seeing it calls
>>>> av_fast_padded_malloc(), yet it's called by frame threaded encoders.
>>>> Should i remove this?
>>>
>>> I don't know, I was asking only because it sounds tricky. For cases
>>> with a limited number of buffers available (like memory-mapped
>>> devices) you are going to need locking anyway, so maybe rentrancy
>>> adds no additional inconvenience.
>>>
>>>>>> + *
>>>>>> + * @see avcodec_default_get_encoder_buffer()
>>>>>> + *
>>>>>> + * - encoding: Set by libavcodec, user can override.
>>>>>> + * - decoding: unused
>>>>>> + */
>>>>>> + int (*get_encoder_buffer)(struct AVCodecContext *s, AVPacket
>>>>>> *pkt, int flags);
>>>>>
>>>>> Can the encoder ask for arbitrarily many packets?
>>>>>
>>>>> Can the user return "not yet" somehow to this if they have a fixed
>>>>> output buffer pool but no buffer is currently available?
>>>>
>>>> No, as is it can't. Return values < 0 are considered errors.
>>>>
>>>>>
>>>>> I don't much like the idea of the user suspending the thread in the
>>>>> callback until they have some available, which might work in some
>>>>> cases but might also deadlock if an avcodec_receive_packet() call
>>>>> is blocked by it.
>>>>
>>>> Can we make what's in essence a malloc() call return something like
>>>> EAGAIN, and this in turn be propagated back to
>>>> encode_receive_packet_internal()?
>>>
>>> Maybe, or if it has many threads maybe it could wait for something
>>> else to finish first.
>>>
>>>> Couldn't this potentially end up in the forbidden scenario of
>>>> avcodec_send_frame() and avcodec_receive_packet() both returning
>>>> EAGAIN?
>>>
>>> Yes. If the forbidden case happens then the encoder is stuck anyway
>>> and can't make any forward progress so we need to error out properly,
>>> but the EAGAIN return isn't needed if there is something else to do
>>> on another thread.
>>
>> Ok, but I'm not familiar or knowledgeable enough with the frame thread
>> encoder code to implement this.
>
> Looked at bit into this. AVCodec->encode2() based encoders don't support
> returning EAGAIN at all, as it completely breaks the frame threading
> logic. It would require a considerable rewrite in order to re-add a task
> that didn't fail but also didn't succeed.
>
> Non frame threading encoders could probably support it with some minimal
> changes, but i don't think suddenly letting an scenario that was until
> now guaranteed to never happen start happening (avcodec_send_frame() and
> avcodec_receive_packet() both returning EAGAIN) is a good idea. It's an
> API break.
> Letting the user's custom get_encode_buffer() callback suspend the
> thread is IMO acceptable. In frame threading scenarios, the other
> threads are still working on their own packets (afaics none depends on
> the others, since it's intra only encoders only).
Ping. I'd like to get this in.
More information about the ffmpeg-devel
mailing list