diff mbox series

[FFmpeg-devel] avformat/riffenc: indicate storage of flipped RGB bitmaps

Message ID 20200708125121.5614-1-ffmpeg@gyani.pro
State Accepted
Commit 1ec2b3de5a074ccce555e33d7f282bbbb6d963d7
Headers show
Series [FFmpeg-devel] avformat/riffenc: indicate storage of flipped RGB bitmaps | expand

Checks

Context Check Description
andriy/default pending
andriy/make success Make finished
andriy/make_fate success Make fate finished

Commit Message

Gyan Doshi July 8, 2020, 12:51 p.m. UTC
Some legacy applications such as AVI2MVE expect raw RGB bitmaps
to be stored bottom-up, whereas our RIFF BITMAPINFOHEADER assumes
they are always stored top-down and thus write a negative value
for height. This can prevent reading of these files.

Option flipped_raw_rgb added to AVI and Matroska muxers
which will write positive value for height when enabled.

Note that the user has to flip the bitmaps beforehand using other
means such as the vflip filter.
---
 doc/muxers.texi           | 13 +++++++++++++
 libavformat/asfenc.c      |  2 +-
 libavformat/avienc.c      |  4 +++-
 libavformat/matroskaenc.c |  5 ++++-
 libavformat/riff.h        |  2 +-
 libavformat/riffenc.c     |  7 ++++---
 libavformat/wtvenc.c      |  2 +-
 7 files changed, 27 insertions(+), 8 deletions(-)

Comments

Gyan Doshi July 14, 2020, 7:59 a.m. UTC | #1
Plan to push in 24h.

On 08-07-2020 06:21 pm, Gyan Doshi wrote:
> Some legacy applications such as AVI2MVE expect raw RGB bitmaps
> to be stored bottom-up, whereas our RIFF BITMAPINFOHEADER assumes
> they are always stored top-down and thus write a negative value
> for height. This can prevent reading of these files.
>
> Option flipped_raw_rgb added to AVI and Matroska muxers
> which will write positive value for height when enabled.
>
> Note that the user has to flip the bitmaps beforehand using other
> means such as the vflip filter.
> ---
>   doc/muxers.texi           | 13 +++++++++++++
>   libavformat/asfenc.c      |  2 +-
>   libavformat/avienc.c      |  4 +++-
>   libavformat/matroskaenc.c |  5 ++++-
>   libavformat/riff.h        |  2 +-
>   libavformat/riffenc.c     |  7 ++++---
>   libavformat/wtvenc.c      |  2 +-
>   7 files changed, 27 insertions(+), 8 deletions(-)
>
> diff --git a/doc/muxers.texi b/doc/muxers.texi
> index b1389a3227..2f26494bfa 100644
> --- a/doc/muxers.texi
> +++ b/doc/muxers.texi
> @@ -89,6 +89,12 @@ specific scenarios, e.g. when merging multiple audio streams into one for
>   compatibility with software that only supports a single audio stream in AVI
>   (see @ref{amerge,,the "amerge" section in the ffmpeg-filters manual,ffmpeg-filters}).
>   
> +@item flipped_raw_rgb
> +If set to true, store positive height for raw RGB bitmaps, which indicates
> +bitmap is stored bottom-up. Note that this option does not flip the bitmap
> +which has to be done manually beforehand, e.g. by using the vflip filter.
> +Default is @var{false} and indicates bitmap is stored top down.
> +
>   @end table
>   
>   @anchor{chromaprint}
> @@ -1409,6 +1415,13 @@ disposition default exists, no subtitle track will be marked as default.
>   In this mode the FlagDefault is set if and only if the AV_DISPOSITION_DEFAULT
>   flag is set in the disposition of the corresponding stream.
>   @end table
> +
> +@item flipped_raw_rgb
> +If set to true, store positive height for raw RGB bitmaps, which indicates
> +bitmap is stored bottom-up. Note that this option does not flip the bitmap
> +which has to be done manually beforehand, e.g. by using the vflip filter.
> +Default is @var{false} and indicates bitmap is stored top down.
> +
>   @end table
>   
>   @anchor{md5}
> diff --git a/libavformat/asfenc.c b/libavformat/asfenc.c
> index 73afb13200..8b24264c94 100644
> --- a/libavformat/asfenc.c
> +++ b/libavformat/asfenc.c
> @@ -682,7 +682,7 @@ static int asf_write_header1(AVFormatContext *s, int64_t file_size,
>               avio_wl16(pb, 40 + par->extradata_size); /* size */
>   
>               /* BITMAPINFOHEADER header */
> -            ff_put_bmp_header(pb, par, 1, 0);
> +            ff_put_bmp_header(pb, par, 1, 0, 0);
>           }
>           end_header(pb, hpos);
>       }
> diff --git a/libavformat/avienc.c b/libavformat/avienc.c
> index 297d5b8964..1b2cb529b9 100644
> --- a/libavformat/avienc.c
> +++ b/libavformat/avienc.c
> @@ -72,6 +72,7 @@ typedef struct AVIContext {
>       int reserve_index_space;
>       int master_index_max_size;
>       int write_channel_mask;
> +    int flipped_raw_rgb;
>   } AVIContext;
>   
>   typedef struct AVIStream {
> @@ -449,7 +450,7 @@ static int avi_write_header(AVFormatContext *s)
>                       && par->bits_per_coded_sample == 15)
>                       par->bits_per_coded_sample = 16;
>                   avist->pal_offset = avio_tell(pb) + 40;
> -                ff_put_bmp_header(pb, par, 0, 0);
> +                ff_put_bmp_header(pb, par, 0, 0, avi->flipped_raw_rgb);
>                   pix_fmt = avpriv_find_pix_fmt(avpriv_pix_fmt_bps_avi,
>                                                 par->bits_per_coded_sample);
>                   if (   !par->codec_tag
> @@ -993,6 +994,7 @@ static void avi_deinit(AVFormatContext *s)
>   static const AVOption options[] = {
>       { "reserve_index_space", "reserve space (in bytes) at the beginning of the file for each stream index", OFFSET(reserve_index_space), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, ENC },
>       { "write_channel_mask", "write channel mask into wave format header", OFFSET(write_channel_mask), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, ENC },
> +    { "flipped_raw_rgb", "Raw RGB bitmaps are stored bottom-up", OFFSET(flipped_raw_rgb), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
>       { NULL },
>   };
>   
> diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c
> index 105ed5197e..233c472b8f 100644
> --- a/libavformat/matroskaenc.c
> +++ b/libavformat/matroskaenc.c
> @@ -154,6 +154,7 @@ typedef struct MatroskaMuxContext {
>       int                 is_dash;
>       int                 dash_track_number;
>       int                 allow_raw_vfw;
> +    int                 flipped_raw_rgb;
>       int                 default_mode;
>   
>       uint32_t            segment_uid[4];
> @@ -764,6 +765,7 @@ static int mkv_write_codecprivate(AVFormatContext *s, AVIOContext *pb,
>                                     int native_id, int qt_id)
>   {
>       AVIOContext *dyn_cp;
> +    MatroskaMuxContext *mkv = s->priv_data;
>       uint8_t *codecpriv;
>       int ret, codecpriv_size;
>   
> @@ -802,7 +804,7 @@ static int mkv_write_codecprivate(AVFormatContext *s, AVIOContext *pb,
>                   ret = AVERROR(EINVAL);
>               }
>   
> -            ff_put_bmp_header(dyn_cp, par, 0, 0);
> +            ff_put_bmp_header(dyn_cp, par, 0, 0, mkv->flipped_raw_rgb);
>           }
>       } else if (par->codec_type == AVMEDIA_TYPE_AUDIO) {
>           unsigned int tag;
> @@ -2787,6 +2789,7 @@ static const AVOption options[] = {
>       { "dash_track_number", "Track number for the DASH stream", OFFSET(dash_track_number), AV_OPT_TYPE_INT, { .i64 = 1 }, 1, INT_MAX, FLAGS },
>       { "live", "Write files assuming it is a live stream.", OFFSET(is_live), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS },
>       { "allow_raw_vfw", "allow RAW VFW mode", OFFSET(allow_raw_vfw), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS },
> +    { "flipped_raw_rgb", "Raw RGB bitmaps in VFW mode are stored bottom-up", OFFSET(flipped_raw_rgb), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS },
>       { "write_crc32", "write a CRC32 element inside every Level 1 element", OFFSET(write_crc), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, FLAGS },
>       { "default_mode", "Controls how a track's FlagDefault is inferred", OFFSET(default_mode), AV_OPT_TYPE_INT, { .i64 = DEFAULT_MODE_INFER }, DEFAULT_MODE_INFER, DEFAULT_MODE_PASSTHROUGH, FLAGS, "default_mode" },
>       { "infer", "For each track type, mark the first track of disposition default as default; if none exists, mark the first track as default.", 0, AV_OPT_TYPE_CONST, { .i64 = DEFAULT_MODE_INFER }, 0, 0, FLAGS, "default_mode" },
> diff --git a/libavformat/riff.h b/libavformat/riff.h
> index 21078b77c8..127138d2bc 100644
> --- a/libavformat/riff.h
> +++ b/libavformat/riff.h
> @@ -46,7 +46,7 @@ void ff_end_tag(AVIOContext *pb, int64_t start);
>    */
>   int ff_get_bmp_header(AVIOContext *pb, AVStream *st, uint32_t *size);
>   
> -void ff_put_bmp_header(AVIOContext *pb, AVCodecParameters *par, int for_asf, int ignore_extradata);
> +void ff_put_bmp_header(AVIOContext *pb, AVCodecParameters *par, int for_asf, int ignore_extradata, int rgb_frame_is_flipped);
>   
>   /**
>    * Tell ff_put_wav_header() to use WAVEFORMATEX even for PCM codecs.
> diff --git a/libavformat/riffenc.c b/libavformat/riffenc.c
> index c04d55c423..d0ee98bfcc 100644
> --- a/libavformat/riffenc.c
> +++ b/libavformat/riffenc.c
> @@ -207,10 +207,11 @@ int ff_put_wav_header(AVFormatContext *s, AVIOContext *pb,
>   
>   /* BITMAPINFOHEADER header */
>   void ff_put_bmp_header(AVIOContext *pb, AVCodecParameters *par,
> -                       int for_asf, int ignore_extradata)
> +                       int for_asf, int ignore_extradata, int rgb_frame_is_flipped)
>   {
> -    int keep_height = par->extradata_size >= 9 &&
> -                      !memcmp(par->extradata + par->extradata_size - 9, "BottomUp", 9);
> +    int keep_height = (par->extradata_size >= 9 &&
> +                      !memcmp(par->extradata + par->extradata_size - 9, "BottomUp", 9)) ||
> +                      rgb_frame_is_flipped;
>       int extradata_size = par->extradata_size - 9*keep_height;
>       enum AVPixelFormat pix_fmt = par->format;
>       int pal_avi;
> diff --git a/libavformat/wtvenc.c b/libavformat/wtvenc.c
> index 498bc64019..b53fdf9048 100644
> --- a/libavformat/wtvenc.c
> +++ b/libavformat/wtvenc.c
> @@ -241,7 +241,7 @@ static void put_videoinfoheader2(AVIOContext *pb, AVStream *st)
>       avio_wl32(pb, 0);
>       avio_wl32(pb, 0);
>   
> -    ff_put_bmp_header(pb, st->codecpar, 0, 1);
> +    ff_put_bmp_header(pb, st->codecpar, 0, 1, 0);
>   
>       if (st->codecpar->codec_id == AV_CODEC_ID_MPEG2VIDEO) {
>           int padding = (st->codecpar->extradata_size & 3) ? 4 - (st->codecpar->extradata_size & 3) : 0;
Paul B Mahol July 14, 2020, 8:24 a.m. UTC | #2
Why is option called rgb?
It have nothing to do with rgb.

Flipped can be any encoded file.

On 7/14/20, Gyan Doshi <ffmpeg@gyani.pro> wrote:
> Plan to push in 24h.
>
> On 08-07-2020 06:21 pm, Gyan Doshi wrote:
>> Some legacy applications such as AVI2MVE expect raw RGB bitmaps
>> to be stored bottom-up, whereas our RIFF BITMAPINFOHEADER assumes
>> they are always stored top-down and thus write a negative value
>> for height. This can prevent reading of these files.
>>
>> Option flipped_raw_rgb added to AVI and Matroska muxers
>> which will write positive value for height when enabled.
>>
>> Note that the user has to flip the bitmaps beforehand using other
>> means such as the vflip filter.
>> ---
>>   doc/muxers.texi           | 13 +++++++++++++
>>   libavformat/asfenc.c      |  2 +-
>>   libavformat/avienc.c      |  4 +++-
>>   libavformat/matroskaenc.c |  5 ++++-
>>   libavformat/riff.h        |  2 +-
>>   libavformat/riffenc.c     |  7 ++++---
>>   libavformat/wtvenc.c      |  2 +-
>>   7 files changed, 27 insertions(+), 8 deletions(-)
>>
>> diff --git a/doc/muxers.texi b/doc/muxers.texi
>> index b1389a3227..2f26494bfa 100644
>> --- a/doc/muxers.texi
>> +++ b/doc/muxers.texi
>> @@ -89,6 +89,12 @@ specific scenarios, e.g. when merging multiple audio
>> streams into one for
>>   compatibility with software that only supports a single audio stream in
>> AVI
>>   (see @ref{amerge,,the "amerge" section in the ffmpeg-filters
>> manual,ffmpeg-filters}).
>>
>> +@item flipped_raw_rgb
>> +If set to true, store positive height for raw RGB bitmaps, which
>> indicates
>> +bitmap is stored bottom-up. Note that this option does not flip the
>> bitmap
>> +which has to be done manually beforehand, e.g. by using the vflip filter.
>> +Default is @var{false} and indicates bitmap is stored top down.
>> +
>>   @end table
>>
>>   @anchor{chromaprint}
>> @@ -1409,6 +1415,13 @@ disposition default exists, no subtitle track will
>> be marked as default.
>>   In this mode the FlagDefault is set if and only if the
>> AV_DISPOSITION_DEFAULT
>>   flag is set in the disposition of the corresponding stream.
>>   @end table
>> +
>> +@item flipped_raw_rgb
>> +If set to true, store positive height for raw RGB bitmaps, which
>> indicates
>> +bitmap is stored bottom-up. Note that this option does not flip the
>> bitmap
>> +which has to be done manually beforehand, e.g. by using the vflip filter.
>> +Default is @var{false} and indicates bitmap is stored top down.
>> +
>>   @end table
>>
>>   @anchor{md5}
>> diff --git a/libavformat/asfenc.c b/libavformat/asfenc.c
>> index 73afb13200..8b24264c94 100644
>> --- a/libavformat/asfenc.c
>> +++ b/libavformat/asfenc.c
>> @@ -682,7 +682,7 @@ static int asf_write_header1(AVFormatContext *s,
>> int64_t file_size,
>>               avio_wl16(pb, 40 + par->extradata_size); /* size */
>>
>>               /* BITMAPINFOHEADER header */
>> -            ff_put_bmp_header(pb, par, 1, 0);
>> +            ff_put_bmp_header(pb, par, 1, 0, 0);
>>           }
>>           end_header(pb, hpos);
>>       }
>> diff --git a/libavformat/avienc.c b/libavformat/avienc.c
>> index 297d5b8964..1b2cb529b9 100644
>> --- a/libavformat/avienc.c
>> +++ b/libavformat/avienc.c
>> @@ -72,6 +72,7 @@ typedef struct AVIContext {
>>       int reserve_index_space;
>>       int master_index_max_size;
>>       int write_channel_mask;
>> +    int flipped_raw_rgb;
>>   } AVIContext;
>>
>>   typedef struct AVIStream {
>> @@ -449,7 +450,7 @@ static int avi_write_header(AVFormatContext *s)
>>                       && par->bits_per_coded_sample == 15)
>>                       par->bits_per_coded_sample = 16;
>>                   avist->pal_offset = avio_tell(pb) + 40;
>> -                ff_put_bmp_header(pb, par, 0, 0);
>> +                ff_put_bmp_header(pb, par, 0, 0, avi->flipped_raw_rgb);
>>                   pix_fmt = avpriv_find_pix_fmt(avpriv_pix_fmt_bps_avi,
>>
>> par->bits_per_coded_sample);
>>                   if (   !par->codec_tag
>> @@ -993,6 +994,7 @@ static void avi_deinit(AVFormatContext *s)
>>   static const AVOption options[] = {
>>       { "reserve_index_space", "reserve space (in bytes) at the beginning
>> of the file for each stream index", OFFSET(reserve_index_space),
>> AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, ENC },
>>       { "write_channel_mask", "write channel mask into wave format
>> header", OFFSET(write_channel_mask), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1,
>> ENC },
>> +    { "flipped_raw_rgb", "Raw RGB bitmaps are stored bottom-up",
>> OFFSET(flipped_raw_rgb), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
>>       { NULL },
>>   };
>>
>> diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c
>> index 105ed5197e..233c472b8f 100644
>> --- a/libavformat/matroskaenc.c
>> +++ b/libavformat/matroskaenc.c
>> @@ -154,6 +154,7 @@ typedef struct MatroskaMuxContext {
>>       int                 is_dash;
>>       int                 dash_track_number;
>>       int                 allow_raw_vfw;
>> +    int                 flipped_raw_rgb;
>>       int                 default_mode;
>>
>>       uint32_t            segment_uid[4];
>> @@ -764,6 +765,7 @@ static int mkv_write_codecprivate(AVFormatContext *s,
>> AVIOContext *pb,
>>                                     int native_id, int qt_id)
>>   {
>>       AVIOContext *dyn_cp;
>> +    MatroskaMuxContext *mkv = s->priv_data;
>>       uint8_t *codecpriv;
>>       int ret, codecpriv_size;
>>
>> @@ -802,7 +804,7 @@ static int mkv_write_codecprivate(AVFormatContext *s,
>> AVIOContext *pb,
>>                   ret = AVERROR(EINVAL);
>>               }
>>
>> -            ff_put_bmp_header(dyn_cp, par, 0, 0);
>> +            ff_put_bmp_header(dyn_cp, par, 0, 0, mkv->flipped_raw_rgb);
>>           }
>>       } else if (par->codec_type == AVMEDIA_TYPE_AUDIO) {
>>           unsigned int tag;
>> @@ -2787,6 +2789,7 @@ static const AVOption options[] = {
>>       { "dash_track_number", "Track number for the DASH stream",
>> OFFSET(dash_track_number), AV_OPT_TYPE_INT, { .i64 = 1 }, 1, INT_MAX,
>> FLAGS },
>>       { "live", "Write files assuming it is a live stream.",
>> OFFSET(is_live), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS },
>>       { "allow_raw_vfw", "allow RAW VFW mode", OFFSET(allow_raw_vfw),
>> AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS },
>> +    { "flipped_raw_rgb", "Raw RGB bitmaps in VFW mode are stored
>> bottom-up", OFFSET(flipped_raw_rgb), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1,
>> FLAGS },
>>       { "write_crc32", "write a CRC32 element inside every Level 1
>> element", OFFSET(write_crc), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, FLAGS
>> },
>>       { "default_mode", "Controls how a track's FlagDefault is inferred",
>> OFFSET(default_mode), AV_OPT_TYPE_INT, { .i64 = DEFAULT_MODE_INFER },
>> DEFAULT_MODE_INFER, DEFAULT_MODE_PASSTHROUGH, FLAGS, "default_mode" },
>>       { "infer", "For each track type, mark the first track of disposition
>> default as default; if none exists, mark the first track as default.", 0,
>> AV_OPT_TYPE_CONST, { .i64 = DEFAULT_MODE_INFER }, 0, 0, FLAGS,
>> "default_mode" },
>> diff --git a/libavformat/riff.h b/libavformat/riff.h
>> index 21078b77c8..127138d2bc 100644
>> --- a/libavformat/riff.h
>> +++ b/libavformat/riff.h
>> @@ -46,7 +46,7 @@ void ff_end_tag(AVIOContext *pb, int64_t start);
>>    */
>>   int ff_get_bmp_header(AVIOContext *pb, AVStream *st, uint32_t *size);
>>
>> -void ff_put_bmp_header(AVIOContext *pb, AVCodecParameters *par, int
>> for_asf, int ignore_extradata);
>> +void ff_put_bmp_header(AVIOContext *pb, AVCodecParameters *par, int
>> for_asf, int ignore_extradata, int rgb_frame_is_flipped);
>>
>>   /**
>>    * Tell ff_put_wav_header() to use WAVEFORMATEX even for PCM codecs.
>> diff --git a/libavformat/riffenc.c b/libavformat/riffenc.c
>> index c04d55c423..d0ee98bfcc 100644
>> --- a/libavformat/riffenc.c
>> +++ b/libavformat/riffenc.c
>> @@ -207,10 +207,11 @@ int ff_put_wav_header(AVFormatContext *s,
>> AVIOContext *pb,
>>
>>   /* BITMAPINFOHEADER header */
>>   void ff_put_bmp_header(AVIOContext *pb, AVCodecParameters *par,
>> -                       int for_asf, int ignore_extradata)
>> +                       int for_asf, int ignore_extradata, int
>> rgb_frame_is_flipped)
>>   {
>> -    int keep_height = par->extradata_size >= 9 &&
>> -                      !memcmp(par->extradata + par->extradata_size - 9,
>> "BottomUp", 9);
>> +    int keep_height = (par->extradata_size >= 9 &&
>> +                      !memcmp(par->extradata + par->extradata_size - 9,
>> "BottomUp", 9)) ||
>> +                      rgb_frame_is_flipped;
>>       int extradata_size = par->extradata_size - 9*keep_height;
>>       enum AVPixelFormat pix_fmt = par->format;
>>       int pal_avi;
>> diff --git a/libavformat/wtvenc.c b/libavformat/wtvenc.c
>> index 498bc64019..b53fdf9048 100644
>> --- a/libavformat/wtvenc.c
>> +++ b/libavformat/wtvenc.c
>> @@ -241,7 +241,7 @@ static void put_videoinfoheader2(AVIOContext *pb,
>> AVStream *st)
>>       avio_wl32(pb, 0);
>>       avio_wl32(pb, 0);
>>
>> -    ff_put_bmp_header(pb, st->codecpar, 0, 1);
>> +    ff_put_bmp_header(pb, st->codecpar, 0, 1, 0);
>>
>>       if (st->codecpar->codec_id == AV_CODEC_ID_MPEG2VIDEO) {
>>           int padding = (st->codecpar->extradata_size & 3) ? 4 -
>> (st->codecpar->extradata_size & 3) : 0;
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Gyan Doshi July 14, 2020, 8:38 a.m. UTC | #3
On 14-07-2020 01:54 pm, Paul B Mahol wrote:
> Why is option called rgb?
> It have nothing to do with rgb.
>
> Flipped can be any encoded file.

No, coded or uncoded streams with a codec tag or raw YUV bitmaps are 
always indicated as positive height. Only raw RGB bitmaps can be 
signaled as flipped.

See doc for |biHeight in 
https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapinfoheader#members

or the end of this page

https://docs.microsoft.com/en-us/windows/win32/directshow/top-down-vs--bottom-up-dibs

Regards,
Gyan
|
>
> On 7/14/20, Gyan Doshi <ffmpeg@gyani.pro> wrote:
>> Plan to push in 24h.
>>
>> On 08-07-2020 06:21 pm, Gyan Doshi wrote:
>>> Some legacy applications such as AVI2MVE expect raw RGB bitmaps
>>> to be stored bottom-up, whereas our RIFF BITMAPINFOHEADER assumes
>>> they are always stored top-down and thus write a negative value
>>> for height. This can prevent reading of these files.
>>>
>>> Option flipped_raw_rgb added to AVI and Matroska muxers
>>> which will write positive value for height when enabled.
>>>
>>> Note that the user has to flip the bitmaps beforehand using other
>>> means such as the vflip filter.
>>> ---
>>>    doc/muxers.texi           | 13 +++++++++++++
>>>    libavformat/asfenc.c      |  2 +-
>>>    libavformat/avienc.c      |  4 +++-
>>>    libavformat/matroskaenc.c |  5 ++++-
>>>    libavformat/riff.h        |  2 +-
>>>    libavformat/riffenc.c     |  7 ++++---
>>>    libavformat/wtvenc.c      |  2 +-
>>>    7 files changed, 27 insertions(+), 8 deletions(-)
>>>
>>> diff --git a/doc/muxers.texi b/doc/muxers.texi
>>> index b1389a3227..2f26494bfa 100644
>>> --- a/doc/muxers.texi
>>> +++ b/doc/muxers.texi
>>> @@ -89,6 +89,12 @@ specific scenarios, e.g. when merging multiple audio
>>> streams into one for
>>>    compatibility with software that only supports a single audio stream in
>>> AVI
>>>    (see @ref{amerge,,the "amerge" section in the ffmpeg-filters
>>> manual,ffmpeg-filters}).
>>>
>>> +@item flipped_raw_rgb
>>> +If set to true, store positive height for raw RGB bitmaps, which
>>> indicates
>>> +bitmap is stored bottom-up. Note that this option does not flip the
>>> bitmap
>>> +which has to be done manually beforehand, e.g. by using the vflip filter.
>>> +Default is @var{false} and indicates bitmap is stored top down.
>>> +
>>>    @end table
>>>
>>>    @anchor{chromaprint}
>>> @@ -1409,6 +1415,13 @@ disposition default exists, no subtitle track will
>>> be marked as default.
>>>    In this mode the FlagDefault is set if and only if the
>>> AV_DISPOSITION_DEFAULT
>>>    flag is set in the disposition of the corresponding stream.
>>>    @end table
>>> +
>>> +@item flipped_raw_rgb
>>> +If set to true, store positive height for raw RGB bitmaps, which
>>> indicates
>>> +bitmap is stored bottom-up. Note that this option does not flip the
>>> bitmap
>>> +which has to be done manually beforehand, e.g. by using the vflip filter.
>>> +Default is @var{false} and indicates bitmap is stored top down.
>>> +
>>>    @end table
>>>
>>>    @anchor{md5}
>>> diff --git a/libavformat/asfenc.c b/libavformat/asfenc.c
>>> index 73afb13200..8b24264c94 100644
>>> --- a/libavformat/asfenc.c
>>> +++ b/libavformat/asfenc.c
>>> @@ -682,7 +682,7 @@ static int asf_write_header1(AVFormatContext *s,
>>> int64_t file_size,
>>>                avio_wl16(pb, 40 + par->extradata_size); /* size */
>>>
>>>                /* BITMAPINFOHEADER header */
>>> -            ff_put_bmp_header(pb, par, 1, 0);
>>> +            ff_put_bmp_header(pb, par, 1, 0, 0);
>>>            }
>>>            end_header(pb, hpos);
>>>        }
>>> diff --git a/libavformat/avienc.c b/libavformat/avienc.c
>>> index 297d5b8964..1b2cb529b9 100644
>>> --- a/libavformat/avienc.c
>>> +++ b/libavformat/avienc.c
>>> @@ -72,6 +72,7 @@ typedef struct AVIContext {
>>>        int reserve_index_space;
>>>        int master_index_max_size;
>>>        int write_channel_mask;
>>> +    int flipped_raw_rgb;
>>>    } AVIContext;
>>>
>>>    typedef struct AVIStream {
>>> @@ -449,7 +450,7 @@ static int avi_write_header(AVFormatContext *s)
>>>                        && par->bits_per_coded_sample == 15)
>>>                        par->bits_per_coded_sample = 16;
>>>                    avist->pal_offset = avio_tell(pb) + 40;
>>> -                ff_put_bmp_header(pb, par, 0, 0);
>>> +                ff_put_bmp_header(pb, par, 0, 0, avi->flipped_raw_rgb);
>>>                    pix_fmt = avpriv_find_pix_fmt(avpriv_pix_fmt_bps_avi,
>>>
>>> par->bits_per_coded_sample);
>>>                    if (   !par->codec_tag
>>> @@ -993,6 +994,7 @@ static void avi_deinit(AVFormatContext *s)
>>>    static const AVOption options[] = {
>>>        { "reserve_index_space", "reserve space (in bytes) at the beginning
>>> of the file for each stream index", OFFSET(reserve_index_space),
>>> AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, ENC },
>>>        { "write_channel_mask", "write channel mask into wave format
>>> header", OFFSET(write_channel_mask), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1,
>>> ENC },
>>> +    { "flipped_raw_rgb", "Raw RGB bitmaps are stored bottom-up",
>>> OFFSET(flipped_raw_rgb), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
>>>        { NULL },
>>>    };
>>>
>>> diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c
>>> index 105ed5197e..233c472b8f 100644
>>> --- a/libavformat/matroskaenc.c
>>> +++ b/libavformat/matroskaenc.c
>>> @@ -154,6 +154,7 @@ typedef struct MatroskaMuxContext {
>>>        int                 is_dash;
>>>        int                 dash_track_number;
>>>        int                 allow_raw_vfw;
>>> +    int                 flipped_raw_rgb;
>>>        int                 default_mode;
>>>
>>>        uint32_t            segment_uid[4];
>>> @@ -764,6 +765,7 @@ static int mkv_write_codecprivate(AVFormatContext *s,
>>> AVIOContext *pb,
>>>                                      int native_id, int qt_id)
>>>    {
>>>        AVIOContext *dyn_cp;
>>> +    MatroskaMuxContext *mkv = s->priv_data;
>>>        uint8_t *codecpriv;
>>>        int ret, codecpriv_size;
>>>
>>> @@ -802,7 +804,7 @@ static int mkv_write_codecprivate(AVFormatContext *s,
>>> AVIOContext *pb,
>>>                    ret = AVERROR(EINVAL);
>>>                }
>>>
>>> -            ff_put_bmp_header(dyn_cp, par, 0, 0);
>>> +            ff_put_bmp_header(dyn_cp, par, 0, 0, mkv->flipped_raw_rgb);
>>>            }
>>>        } else if (par->codec_type == AVMEDIA_TYPE_AUDIO) {
>>>            unsigned int tag;
>>> @@ -2787,6 +2789,7 @@ static const AVOption options[] = {
>>>        { "dash_track_number", "Track number for the DASH stream",
>>> OFFSET(dash_track_number), AV_OPT_TYPE_INT, { .i64 = 1 }, 1, INT_MAX,
>>> FLAGS },
>>>        { "live", "Write files assuming it is a live stream.",
>>> OFFSET(is_live), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS },
>>>        { "allow_raw_vfw", "allow RAW VFW mode", OFFSET(allow_raw_vfw),
>>> AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS },
>>> +    { "flipped_raw_rgb", "Raw RGB bitmaps in VFW mode are stored
>>> bottom-up", OFFSET(flipped_raw_rgb), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1,
>>> FLAGS },
>>>        { "write_crc32", "write a CRC32 element inside every Level 1
>>> element", OFFSET(write_crc), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, FLAGS
>>> },
>>>        { "default_mode", "Controls how a track's FlagDefault is inferred",
>>> OFFSET(default_mode), AV_OPT_TYPE_INT, { .i64 = DEFAULT_MODE_INFER },
>>> DEFAULT_MODE_INFER, DEFAULT_MODE_PASSTHROUGH, FLAGS, "default_mode" },
>>>        { "infer", "For each track type, mark the first track of disposition
>>> default as default; if none exists, mark the first track as default.", 0,
>>> AV_OPT_TYPE_CONST, { .i64 = DEFAULT_MODE_INFER }, 0, 0, FLAGS,
>>> "default_mode" },
>>> diff --git a/libavformat/riff.h b/libavformat/riff.h
>>> index 21078b77c8..127138d2bc 100644
>>> --- a/libavformat/riff.h
>>> +++ b/libavformat/riff.h
>>> @@ -46,7 +46,7 @@ void ff_end_tag(AVIOContext *pb, int64_t start);
>>>     */
>>>    int ff_get_bmp_header(AVIOContext *pb, AVStream *st, uint32_t *size);
>>>
>>> -void ff_put_bmp_header(AVIOContext *pb, AVCodecParameters *par, int
>>> for_asf, int ignore_extradata);
>>> +void ff_put_bmp_header(AVIOContext *pb, AVCodecParameters *par, int
>>> for_asf, int ignore_extradata, int rgb_frame_is_flipped);
>>>
>>>    /**
>>>     * Tell ff_put_wav_header() to use WAVEFORMATEX even for PCM codecs.
>>> diff --git a/libavformat/riffenc.c b/libavformat/riffenc.c
>>> index c04d55c423..d0ee98bfcc 100644
>>> --- a/libavformat/riffenc.c
>>> +++ b/libavformat/riffenc.c
>>> @@ -207,10 +207,11 @@ int ff_put_wav_header(AVFormatContext *s,
>>> AVIOContext *pb,
>>>
>>>    /* BITMAPINFOHEADER header */
>>>    void ff_put_bmp_header(AVIOContext *pb, AVCodecParameters *par,
>>> -                       int for_asf, int ignore_extradata)
>>> +                       int for_asf, int ignore_extradata, int
>>> rgb_frame_is_flipped)
>>>    {
>>> -    int keep_height = par->extradata_size >= 9 &&
>>> -                      !memcmp(par->extradata + par->extradata_size - 9,
>>> "BottomUp", 9);
>>> +    int keep_height = (par->extradata_size >= 9 &&
>>> +                      !memcmp(par->extradata + par->extradata_size - 9,
>>> "BottomUp", 9)) ||
>>> +                      rgb_frame_is_flipped;
>>>        int extradata_size = par->extradata_size - 9*keep_height;
>>>        enum AVPixelFormat pix_fmt = par->format;
>>>        int pal_avi;
>>> diff --git a/libavformat/wtvenc.c b/libavformat/wtvenc.c
>>> index 498bc64019..b53fdf9048 100644
>>> --- a/libavformat/wtvenc.c
>>> +++ b/libavformat/wtvenc.c
>>> @@ -241,7 +241,7 @@ static void put_videoinfoheader2(AVIOContext *pb,
>>> AVStream *st)
>>>        avio_wl32(pb, 0);
>>>        avio_wl32(pb, 0);
>>>
>>> -    ff_put_bmp_header(pb, st->codecpar, 0, 1);
>>> +    ff_put_bmp_header(pb, st->codecpar, 0, 1, 0);
>>>
>>>        if (st->codecpar->codec_id == AV_CODEC_ID_MPEG2VIDEO) {
>>>            int padding = (st->codecpar->extradata_size & 3) ? 4 -
>>> (st->codecpar->extradata_size & 3) : 0;
>> _______________________________________________
>> ffmpeg-devel mailing list
>> ffmpeg-devel@ffmpeg.org
>> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>>
>> To unsubscribe, visit link above, or email
>> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
Andreas Rheinhardt July 14, 2020, 11:44 a.m. UTC | #4
Gyan Doshi:
> Some legacy applications such as AVI2MVE expect raw RGB bitmaps
> to be stored bottom-up, whereas our RIFF BITMAPINFOHEADER assumes
> they are always stored top-down and thus write a negative value
> for height. This can prevent reading of these files.
> 
> Option flipped_raw_rgb added to AVI and Matroska muxers
> which will write positive value for height when enabled.
> 
> Note that the user has to flip the bitmaps beforehand using other
> means such as the vflip filter.
> ---

1. The avi demuxer adds "BottomUp" to the extradata in case it demuxes
bottom up data. The Matroska demuxer currently doesn't and so rawvideo
stored in Matroska in vfw mode will always be treated as top-down. This
needs to be fixed, too.

2. But why don't we add an option to the raw encoder so that it adds (or
maybe switches?) "BottomUp" to the extradata to indicate that the output
is bottom-up? That seems like a better place for me. (E.g. it would
naturally allow to set the bottom-up/top-down property on a per-stream
basis.)

- Andreas
Gyan Doshi July 14, 2020, 12:16 p.m. UTC | #5
On 14-07-2020 05:14 pm, Andreas Rheinhardt wrote:
> Gyan Doshi:
>> Some legacy applications such as AVI2MVE expect raw RGB bitmaps
>> to be stored bottom-up, whereas our RIFF BITMAPINFOHEADER assumes
>> they are always stored top-down and thus write a negative value
>> for height. This can prevent reading of these files.
>>
>> Option flipped_raw_rgb added to AVI and Matroska muxers
>> which will write positive value for height when enabled.
>>
>> Note that the user has to flip the bitmaps beforehand using other
>> means such as the vflip filter.
>> ---
> 1. The avi demuxer adds "BottomUp" to the extradata in case it demuxes
> bottom up data. The Matroska demuxer currently doesn't and so rawvideo
> stored in Matroska in vfw mode will always be treated as top-down. This
> needs to be fixed, too.

Agreed. But that's a separate patch,

> 2. But why don't we add an option to the raw encoder so that it adds (or
> maybe switches?) "BottomUp" to the extradata to indicate that the output
> is bottom-up? That seems like a better place for me. (E.g. it would
> naturally allow to set the bottom-up/top-down property on a per-stream
> basis.)

This "feature" is a quirk of Microsoft / RIFF containers (or their 
accommodation in other containers viz. VFW in Matroska) rather than a 
generic property of raw video streams - the extradata string is an 
ffmpeg invention. Since this option is meant to pacify old legacy apps 
which wouldn't deal with multiple streams, per-stream assignment is a 
bonus but not necessary. I modified Matroska muxer for completeness 
rather than expecting anyone to actually use it.

Gyan
Gyan Doshi July 15, 2020, 3:24 p.m. UTC | #6
On 14-07-2020 01:29 pm, Gyan Doshi wrote:
> Plan to push in 24h.

Pushed as 1ec2b3de5a074ccce555e33d7f282bbbb6d963d7

Gyan
diff mbox series

Patch

diff --git a/doc/muxers.texi b/doc/muxers.texi
index b1389a3227..2f26494bfa 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -89,6 +89,12 @@  specific scenarios, e.g. when merging multiple audio streams into one for
 compatibility with software that only supports a single audio stream in AVI
 (see @ref{amerge,,the "amerge" section in the ffmpeg-filters manual,ffmpeg-filters}).
 
+@item flipped_raw_rgb
+If set to true, store positive height for raw RGB bitmaps, which indicates
+bitmap is stored bottom-up. Note that this option does not flip the bitmap
+which has to be done manually beforehand, e.g. by using the vflip filter.
+Default is @var{false} and indicates bitmap is stored top down.
+
 @end table
 
 @anchor{chromaprint}
@@ -1409,6 +1415,13 @@  disposition default exists, no subtitle track will be marked as default.
 In this mode the FlagDefault is set if and only if the AV_DISPOSITION_DEFAULT
 flag is set in the disposition of the corresponding stream.
 @end table
+
+@item flipped_raw_rgb
+If set to true, store positive height for raw RGB bitmaps, which indicates
+bitmap is stored bottom-up. Note that this option does not flip the bitmap
+which has to be done manually beforehand, e.g. by using the vflip filter.
+Default is @var{false} and indicates bitmap is stored top down.
+
 @end table
 
 @anchor{md5}
diff --git a/libavformat/asfenc.c b/libavformat/asfenc.c
index 73afb13200..8b24264c94 100644
--- a/libavformat/asfenc.c
+++ b/libavformat/asfenc.c
@@ -682,7 +682,7 @@  static int asf_write_header1(AVFormatContext *s, int64_t file_size,
             avio_wl16(pb, 40 + par->extradata_size); /* size */
 
             /* BITMAPINFOHEADER header */
-            ff_put_bmp_header(pb, par, 1, 0);
+            ff_put_bmp_header(pb, par, 1, 0, 0);
         }
         end_header(pb, hpos);
     }
diff --git a/libavformat/avienc.c b/libavformat/avienc.c
index 297d5b8964..1b2cb529b9 100644
--- a/libavformat/avienc.c
+++ b/libavformat/avienc.c
@@ -72,6 +72,7 @@  typedef struct AVIContext {
     int reserve_index_space;
     int master_index_max_size;
     int write_channel_mask;
+    int flipped_raw_rgb;
 } AVIContext;
 
 typedef struct AVIStream {
@@ -449,7 +450,7 @@  static int avi_write_header(AVFormatContext *s)
                     && par->bits_per_coded_sample == 15)
                     par->bits_per_coded_sample = 16;
                 avist->pal_offset = avio_tell(pb) + 40;
-                ff_put_bmp_header(pb, par, 0, 0);
+                ff_put_bmp_header(pb, par, 0, 0, avi->flipped_raw_rgb);
                 pix_fmt = avpriv_find_pix_fmt(avpriv_pix_fmt_bps_avi,
                                               par->bits_per_coded_sample);
                 if (   !par->codec_tag
@@ -993,6 +994,7 @@  static void avi_deinit(AVFormatContext *s)
 static const AVOption options[] = {
     { "reserve_index_space", "reserve space (in bytes) at the beginning of the file for each stream index", OFFSET(reserve_index_space), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, ENC },
     { "write_channel_mask", "write channel mask into wave format header", OFFSET(write_channel_mask), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, ENC },
+    { "flipped_raw_rgb", "Raw RGB bitmaps are stored bottom-up", OFFSET(flipped_raw_rgb), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC },
     { NULL },
 };
 
diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c
index 105ed5197e..233c472b8f 100644
--- a/libavformat/matroskaenc.c
+++ b/libavformat/matroskaenc.c
@@ -154,6 +154,7 @@  typedef struct MatroskaMuxContext {
     int                 is_dash;
     int                 dash_track_number;
     int                 allow_raw_vfw;
+    int                 flipped_raw_rgb;
     int                 default_mode;
 
     uint32_t            segment_uid[4];
@@ -764,6 +765,7 @@  static int mkv_write_codecprivate(AVFormatContext *s, AVIOContext *pb,
                                   int native_id, int qt_id)
 {
     AVIOContext *dyn_cp;
+    MatroskaMuxContext *mkv = s->priv_data;
     uint8_t *codecpriv;
     int ret, codecpriv_size;
 
@@ -802,7 +804,7 @@  static int mkv_write_codecprivate(AVFormatContext *s, AVIOContext *pb,
                 ret = AVERROR(EINVAL);
             }
 
-            ff_put_bmp_header(dyn_cp, par, 0, 0);
+            ff_put_bmp_header(dyn_cp, par, 0, 0, mkv->flipped_raw_rgb);
         }
     } else if (par->codec_type == AVMEDIA_TYPE_AUDIO) {
         unsigned int tag;
@@ -2787,6 +2789,7 @@  static const AVOption options[] = {
     { "dash_track_number", "Track number for the DASH stream", OFFSET(dash_track_number), AV_OPT_TYPE_INT, { .i64 = 1 }, 1, INT_MAX, FLAGS },
     { "live", "Write files assuming it is a live stream.", OFFSET(is_live), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS },
     { "allow_raw_vfw", "allow RAW VFW mode", OFFSET(allow_raw_vfw), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS },
+    { "flipped_raw_rgb", "Raw RGB bitmaps in VFW mode are stored bottom-up", OFFSET(flipped_raw_rgb), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS },
     { "write_crc32", "write a CRC32 element inside every Level 1 element", OFFSET(write_crc), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, FLAGS },
     { "default_mode", "Controls how a track's FlagDefault is inferred", OFFSET(default_mode), AV_OPT_TYPE_INT, { .i64 = DEFAULT_MODE_INFER }, DEFAULT_MODE_INFER, DEFAULT_MODE_PASSTHROUGH, FLAGS, "default_mode" },
     { "infer", "For each track type, mark the first track of disposition default as default; if none exists, mark the first track as default.", 0, AV_OPT_TYPE_CONST, { .i64 = DEFAULT_MODE_INFER }, 0, 0, FLAGS, "default_mode" },
diff --git a/libavformat/riff.h b/libavformat/riff.h
index 21078b77c8..127138d2bc 100644
--- a/libavformat/riff.h
+++ b/libavformat/riff.h
@@ -46,7 +46,7 @@  void ff_end_tag(AVIOContext *pb, int64_t start);
  */
 int ff_get_bmp_header(AVIOContext *pb, AVStream *st, uint32_t *size);
 
-void ff_put_bmp_header(AVIOContext *pb, AVCodecParameters *par, int for_asf, int ignore_extradata);
+void ff_put_bmp_header(AVIOContext *pb, AVCodecParameters *par, int for_asf, int ignore_extradata, int rgb_frame_is_flipped);
 
 /**
  * Tell ff_put_wav_header() to use WAVEFORMATEX even for PCM codecs.
diff --git a/libavformat/riffenc.c b/libavformat/riffenc.c
index c04d55c423..d0ee98bfcc 100644
--- a/libavformat/riffenc.c
+++ b/libavformat/riffenc.c
@@ -207,10 +207,11 @@  int ff_put_wav_header(AVFormatContext *s, AVIOContext *pb,
 
 /* BITMAPINFOHEADER header */
 void ff_put_bmp_header(AVIOContext *pb, AVCodecParameters *par,
-                       int for_asf, int ignore_extradata)
+                       int for_asf, int ignore_extradata, int rgb_frame_is_flipped)
 {
-    int keep_height = par->extradata_size >= 9 &&
-                      !memcmp(par->extradata + par->extradata_size - 9, "BottomUp", 9);
+    int keep_height = (par->extradata_size >= 9 &&
+                      !memcmp(par->extradata + par->extradata_size - 9, "BottomUp", 9)) ||
+                      rgb_frame_is_flipped;
     int extradata_size = par->extradata_size - 9*keep_height;
     enum AVPixelFormat pix_fmt = par->format;
     int pal_avi;
diff --git a/libavformat/wtvenc.c b/libavformat/wtvenc.c
index 498bc64019..b53fdf9048 100644
--- a/libavformat/wtvenc.c
+++ b/libavformat/wtvenc.c
@@ -241,7 +241,7 @@  static void put_videoinfoheader2(AVIOContext *pb, AVStream *st)
     avio_wl32(pb, 0);
     avio_wl32(pb, 0);
 
-    ff_put_bmp_header(pb, st->codecpar, 0, 1);
+    ff_put_bmp_header(pb, st->codecpar, 0, 1, 0);
 
     if (st->codecpar->codec_id == AV_CODEC_ID_MPEG2VIDEO) {
         int padding = (st->codecpar->extradata_size & 3) ? 4 - (st->codecpar->extradata_size & 3) : 0;