[vlc-commits] packetizer: add AV1 packetizer

Francois Cartegnie git at videolan.org
Mon Oct 1 16:59:59 CEST 2018


vlc | branch: master | Francois Cartegnie <fcvlcdev at free.fr> | Wed Sep 26 14:37:47 2018 +0200| [489384ea50ebe57b5e4d2524c1b5cba2bbe806c4] | committer: Francois Cartegnie

packetizer: add AV1 packetizer

> http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=489384ea50ebe57b5e4d2524c1b5cba2bbe806c4
---

 modules/MODULES_LIST           |   1 +
 modules/packetizer/Makefile.am |   5 +
 modules/packetizer/av1.c       | 551 +++++++++++++++++++++++++++++++++++++++++
 po/POTFILES.in                 |   1 +
 4 files changed, 558 insertions(+)

diff --git a/modules/MODULES_LIST b/modules/MODULES_LIST
index 73e73ed545..79161ae3ed 100644
--- a/modules/MODULES_LIST
+++ b/modules/MODULES_LIST
@@ -58,6 +58,7 @@ $Id$
  * avcodec: libavcodec audio/video decoder
  * avformat: libavformat demuxer
  * avi: AVI file stream demuxer
+ * av1: AV1 packetizer
  * avio: Access and Stream output module using libavformat network
  * ball: Augmented reality ball video filter module
  * bandlimited_resampler: Bandlimited interpolation audio resampler
diff --git a/modules/packetizer/Makefile.am b/modules/packetizer/Makefile.am
index 0551c4d0a7..d54152b33b 100644
--- a/modules/packetizer/Makefile.am
+++ b/modules/packetizer/Makefile.am
@@ -1,5 +1,9 @@
 packetizerdir = $(pluginsdir)/packetizer
 
+libpacketizer_av1_plugin_la_SOURCES = packetizer/av1.c \
+                                      packetizer/av1.h \
+                                      packetizer/av1_obu.c \
+                                      packetizer/av1_obu.h
 libpacketizer_copy_plugin_la_SOURCES = packetizer/copy.c
 libpacketizer_mpegvideo_plugin_la_SOURCES = packetizer/mpegvideo.c \
                                             packetizer/iso_color_tables.h
@@ -42,6 +46,7 @@ libpacketizer_avparser_plugin_la_LIBADD = $(AVCODEC_LIBS) $(AVUTIL_LIBS) $(LIBM)
 noinst_HEADERS += packetizer/packetizer_helper.h packetizer/startcode_helper.h
 
 packetizer_LTLIBRARIES = \
+	libpacketizer_av1_plugin.la \
 	libpacketizer_mpegvideo_plugin.la \
 	libpacketizer_mpeg4video_plugin.la \
 	libpacketizer_mjpeg_plugin.la \
diff --git a/modules/packetizer/av1.c b/modules/packetizer/av1.c
new file mode 100644
index 0000000000..d303277c3b
--- /dev/null
+++ b/modules/packetizer/av1.c
@@ -0,0 +1,551 @@
+/*****************************************************************************
+ * av1.c: AV1 video packetizer
+ *****************************************************************************
+ * Copyright (C) 2018 VideoLabs, VLC authors and VideoLAN
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_codec.h>
+#include <vlc_block.h>
+#include <vlc_bits.h>
+
+#include <vlc_block_helper.h>
+
+#include "av1.h"
+#include "av1_obu.h"
+
+#include <limits.h>
+
+//#define DEBUG_AV1_PACKETIZER
+
+/****************************************************************************
+ * Local prototypes
+ ****************************************************************************/
+typedef struct
+{
+    struct
+    {
+        block_t *p_chain;
+        block_t **pp_chain_last;
+    } obus;
+
+    block_t *p_sequence_header_block;
+    av1_OBU_sequence_header_t *p_sequence_header;
+    struct
+    {
+        bool b_has_visible_frame;
+        struct
+        {
+            block_t *p_chain;
+            block_t **pp_chain_last;
+        } pre, frame, post;
+        vlc_tick_t dts;
+        vlc_tick_t pts;
+    } tu;
+    uint32_t i_seen;
+    int i_next_block_flags;
+
+} av1_sys_t;
+
+#define BLOCK_FLAG_DROP (1 << BLOCK_FLAG_PRIVATE_SHIFT)
+
+/****************************************************************************
+ * Helpers
+ ****************************************************************************/
+static inline void InitQueue(block_t **pp_head, block_t ***ppp_tail)
+{
+    *pp_head = NULL;
+    *ppp_tail = pp_head;
+}
+
+static bool block_Differs(const block_t *a, const block_t *b)
+{
+    return (a->i_buffer != b->i_buffer ||
+            memcmp(a->p_buffer, b->p_buffer, a->i_buffer));
+}
+
+#define INITQ(name) InitQueue(&p_sys->name.p_chain, &p_sys->name.pp_chain_last)
+#define PUSHQ(name,b) \
+{\
+    block_ChainLastAppend(&p_sys->name.pp_chain_last, b);\
+    if(p_sys->tu.dts == VLC_TICK_INVALID)\
+        p_sys->tu.dts = b->i_dts; p_sys->tu.pts = b->i_pts;\
+}
+
+static void UpdateDecoderFormat(decoder_t *p_dec)
+{
+    av1_sys_t *p_sys = p_dec->p_sys;
+    if(!p_sys->p_sequence_header)
+        return;
+
+    if(p_dec->fmt_in.i_profile < AV1_PROFILE_MAIN)
+    {
+        int val[3];
+        AV1_get_profile_level(p_sys->p_sequence_header, &val[0], &val[1], &val[2]);
+        if(p_dec->fmt_out.i_profile != val[0] || p_dec->fmt_out.i_level != val[1])
+        {
+            p_dec->fmt_out.i_profile = val[0];
+            p_dec->fmt_out.i_level = val[1];
+        }
+    }
+
+    unsigned wnum, hden;
+    AV1_get_frame_max_dimensions(p_sys->p_sequence_header, &wnum, &hden);
+    if((!p_dec->fmt_in.video.i_visible_height ||
+        !p_dec->fmt_in.video.i_visible_width) &&
+       (p_dec->fmt_out.video.i_visible_width != wnum ||
+        p_dec->fmt_out.video.i_visible_width != hden))
+    {
+        p_dec->fmt_out.video.i_width =
+        p_dec->fmt_out.video.i_visible_width = wnum;
+        p_dec->fmt_out.video.i_height =
+        p_dec->fmt_out.video.i_visible_height = hden;
+    }
+
+    if((!p_dec->fmt_in.video.i_frame_rate ||
+        !p_dec->fmt_in.video.i_frame_rate_base) &&
+        AV1_get_frame_rate(p_sys->p_sequence_header, &wnum, &hden) &&
+        (p_dec->fmt_out.video.i_frame_rate != wnum ||
+         p_dec->fmt_out.video.i_frame_rate_base != hden))
+    {
+        p_dec->fmt_out.video.i_frame_rate = wnum;
+        p_dec->fmt_out.video.i_frame_rate_base = hden;
+    }
+
+    video_color_primaries_t prim;
+    video_color_space_t space;
+    video_transfer_func_t xfer;
+    bool full;
+    if(p_dec->fmt_in.video.primaries == COLOR_PRIMARIES_UNDEF &&
+       AV1_get_colorimetry(p_sys->p_sequence_header, &prim, &xfer, &space, &full) &&
+       prim != COLOR_PRIMARIES_UNDEF &&
+       (p_dec->fmt_out.video.primaries != prim ||
+        p_dec->fmt_out.video.transfer != xfer ||
+        p_dec->fmt_out.video.space != space))
+    {
+        p_dec->fmt_out.video.primaries = prim;
+        p_dec->fmt_out.video.transfer = xfer;
+        p_dec->fmt_out.video.space = space;
+        p_dec->fmt_out.video.b_color_range_full = full;
+    }
+
+    if(!p_dec->fmt_in.i_extra && !p_dec->fmt_out.i_extra)
+    {
+        p_dec->fmt_out.i_extra =
+                AV1_create_DecoderConfigurationRecord((uint8_t **)&p_dec->fmt_out.p_extra,
+                                                      p_sys->p_sequence_header,
+                                                      1,
+                                                      (const uint8_t **)&p_sys->p_sequence_header_block->p_buffer,
+                                                      &p_sys->p_sequence_header_block->i_buffer);
+    }
+}
+
+static block_t * OutputQueues(decoder_t *p_dec, bool b_valid)
+{
+    av1_sys_t *p_sys = p_dec->p_sys;
+    block_t *p_output = NULL;
+    block_t **pp_output_last = &p_output;
+    uint32_t i_flags = 0; /* Because block_ChainGather does not merge flags or times */
+
+    if(p_sys->tu.pre.p_chain)
+    {
+        block_ChainLastAppend(&pp_output_last, p_sys->tu.pre.p_chain);
+        INITQ(tu.pre);
+    }
+
+    if(p_sys->tu.frame.p_chain)
+    {
+        i_flags |= p_sys->tu.frame.p_chain->i_flags;
+        block_ChainLastAppend(&pp_output_last, p_sys->tu.frame.p_chain);
+        INITQ(tu.frame);
+    }
+
+    if(p_sys->tu.post.p_chain)
+    {
+        block_ChainLastAppend(&pp_output_last, p_sys->tu.post.p_chain);
+        INITQ(tu.post);
+    }
+
+    if(p_output)
+    {
+        p_output->i_dts = p_sys->tu.dts;
+        p_output->i_pts = p_sys->tu.pts;
+        p_output->i_flags |= i_flags;
+        if(!b_valid)
+            p_output->i_flags |= BLOCK_FLAG_DROP;
+        else
+        {
+            p_output->i_flags |= p_sys->i_next_block_flags;
+            p_sys->i_next_block_flags = 0;
+        }
+    }
+
+    p_sys->tu.b_has_visible_frame = false;
+    p_sys->tu.dts = VLC_TICK_INVALID;
+    p_sys->tu.pts = VLC_TICK_INVALID;
+    p_sys->i_seen = 0;
+
+    return p_output;
+}
+
+/****************************************************************************
+ * Packetizer Helpers
+ ****************************************************************************/
+static block_t *GatherAndValidateChain(decoder_t *p_dec, block_t *p_outputchain)
+{
+    block_t *p_output = NULL;
+    av1_sys_t *p_sys = p_dec->p_sys;
+    VLC_UNUSED(p_sys);
+
+    if(p_outputchain)
+    {
+#ifdef DEBUG_AV1_PACKETIZER
+        msg_Dbg(p_dec, "TU output %ld", p_outputchain->i_dts);
+        for(block_t *p = p_outputchain; p; p=p->p_next)
+        {
+            enum av1_obu_type_e OBUtype = AV1_OBUGetType(p->p_buffer);
+            if(OBUtype == AV1_OBU_FRAME || OBUtype == AV1_OBU_FRAME_HEADER)
+            {
+                av1_OBU_frame_header_t *p_fh = NULL;
+                if(AV1_OBUIsBaseLayer(p->p_buffer, p->i_buffer) && p_sys->p_sequence_header)
+                {
+                    p_fh = AV1_OBU_parse_frame_header(p->p_buffer, p->i_buffer,
+                                                      p_sys->p_sequence_header);
+                    if(p_fh)
+                    {
+                        msg_Dbg(p_dec,"OBU TYPE %d sz %ld dts %ld type %d %d",
+                                OBUtype, p->i_buffer, p->i_dts,
+                                AV1_get_frame_type(p_fh),
+                                AV1_get_frame_visibility(p_fh));
+                    }
+                    AV1_release_frame_header(p_fh);
+                }
+            }
+            else msg_Dbg(p_dec, "OBU TYPE %d sz %ld dts %ld", OBUtype, p->i_buffer, p->i_dts);
+        }
+#endif
+        if(p_outputchain->i_flags & BLOCK_FLAG_DROP)
+            p_output = p_outputchain; /* Avoid useless gather */
+        else
+            p_output = block_ChainGather(p_outputchain);
+    }
+
+    if(p_output && (p_output->i_flags & BLOCK_FLAG_DROP))
+    {
+        block_ChainRelease(p_output); /* Chain! see above */
+        p_output = NULL;
+    }
+
+    return p_output;
+}
+
+static block_t *ParseOBUBlock(decoder_t *p_dec, block_t *p_obu)
+{
+    av1_sys_t *p_sys = p_dec->p_sys;
+
+    block_t * p_output = NULL;
+    enum av1_obu_type_e OBUtype = AV1_OBUGetType(p_obu->p_buffer);
+    const bool b_base_layer = AV1_OBUIsBaseLayer(p_obu->p_buffer, p_obu->i_buffer);
+
+    switch(OBUtype)
+    {
+        case AV1_OBU_SEQUENCE_HEADER:
+        {
+            if(p_sys->tu.frame.p_chain || p_sys->tu.post.p_chain)
+                p_output = OutputQueues(p_dec, p_sys->p_sequence_header != NULL);
+
+            if(b_base_layer)
+            {
+                /* Save a copy for Extradata */
+                if(!p_sys->p_sequence_header_block ||
+                   block_Differs(p_sys->p_sequence_header_block, p_obu))
+                {
+                    if(p_sys->p_sequence_header_block)
+                        block_Release(p_sys->p_sequence_header_block);
+                    p_sys->p_sequence_header_block = block_Duplicate(p_obu);
+                }
+
+                if(p_sys->p_sequence_header)
+                    AV1_release_sequence_header(p_sys->p_sequence_header);
+                p_sys->p_sequence_header = AV1_OBU_parse_sequence_header(p_obu->p_buffer, p_obu->i_buffer);
+            }
+            PUSHQ(tu.pre, p_obu);
+        } break;
+
+        case AV1_OBU_TEMPORAL_DELIMITER:
+        {
+            p_output = OutputQueues(p_dec, p_sys->p_sequence_header_block != NULL);
+            PUSHQ(tu.pre, p_obu);
+        } break;
+
+        case AV1_OBU_FRAME:
+        case AV1_OBU_FRAME_HEADER:
+        {
+            if(b_base_layer)
+            {
+                av1_OBU_frame_header_t *p_fh = NULL;
+                if(p_sys->p_sequence_header)
+                {
+                    p_fh = AV1_OBU_parse_frame_header(p_obu->p_buffer, p_obu->i_buffer,
+                                                      p_sys->p_sequence_header);
+                    if(p_fh)
+                    {
+                        if((p_sys->i_seen & AV1_OBU_TEMPORAL_DELIMITER) && p_sys->tu.b_has_visible_frame)
+                            p_output = OutputQueues(p_dec, p_sys->p_sequence_header != NULL);
+
+                        if(AV1_get_frame_type(p_fh) == AV1_KEY_FRAME)
+                            p_obu->i_flags |= BLOCK_FLAG_TYPE_I;
+
+                        p_sys->tu.b_has_visible_frame |= AV1_get_frame_visibility(p_fh);
+                        AV1_release_frame_header(p_fh);
+                    }
+                    else msg_Warn(p_dec, "could not parse frame header");
+                }
+            }
+
+            if(!p_output && p_sys->tu.post.p_chain)
+                p_output = OutputQueues(p_dec, p_sys->p_sequence_header != NULL);
+
+            PUSHQ(tu.frame, p_obu);
+        } break;
+
+        case AV1_OBU_METADATA:
+        {
+            if(p_sys->tu.frame.p_chain || p_sys->tu.post.p_chain)
+                p_output = OutputQueues(p_dec, p_sys->p_sequence_header != NULL);
+            PUSHQ(tu.pre, p_obu);
+        } break;
+
+        case AV1_OBU_TILE_GROUP:
+        case AV1_OBU_TILE_LIST:
+            if(p_sys->tu.post.p_chain)
+                p_output = OutputQueues(p_dec, p_sys->p_sequence_header != NULL);
+            PUSHQ(tu.frame, p_obu);
+            break;
+
+        case AV1_OBU_REDUNDANT_FRAME_HEADER:
+        case AV1_OBU_PADDING:
+        default:
+            block_Release(p_obu);
+            break;
+    }
+
+    if(b_base_layer)
+        p_sys->i_seen |= 1 << OBUtype;
+
+    return p_output;
+}
+
+/****************************************************************************
+ * Flush
+ ****************************************************************************/
+static void PacketizeFlush(decoder_t *p_dec)
+{
+    av1_sys_t *p_sys = p_dec->p_sys;
+
+    block_ChainRelease(OutputQueues(p_dec, false));
+
+    if(p_sys->p_sequence_header)
+    {
+        AV1_release_sequence_header(p_sys->p_sequence_header);
+        p_sys->p_sequence_header = NULL;
+    }
+    if(p_sys->p_sequence_header_block)
+    {
+        block_Release(p_sys->p_sequence_header_block);
+        p_sys->p_sequence_header_block = NULL;
+    }
+
+    block_ChainRelease(p_sys->obus.p_chain);
+    INITQ(obus);
+
+    p_sys->tu.dts = VLC_TICK_INVALID;
+    p_sys->tu.pts = VLC_TICK_INVALID;
+    p_sys->tu.b_has_visible_frame = false;
+    p_sys->i_seen = 0;
+    p_sys->i_next_block_flags = BLOCK_FLAG_DISCONTINUITY;
+}
+
+/****************************************************************************
+ * Packetize
+ ****************************************************************************/
+static block_t *PacketizeOBU(decoder_t *p_dec, block_t **pp_block)
+{
+    av1_sys_t *p_sys = p_dec->p_sys;
+
+    block_t *p_block = pp_block ? *pp_block : NULL;
+    if(p_block)
+    {
+        if( p_block->i_flags & (BLOCK_FLAG_DISCONTINUITY | BLOCK_FLAG_CORRUPTED) )
+        {
+            /* First always drain complete blocks before discontinuity */
+            block_t *p_drain = PacketizeOBU( p_dec, NULL );
+            if(p_drain)
+                return p_drain;
+
+            PacketizeFlush( p_dec );
+
+            if( p_block->i_flags & BLOCK_FLAG_CORRUPTED )
+            {
+                block_Release( p_block );
+                return NULL;
+            }
+        }
+
+        if(!AV1_OBUIsValid(p_block->p_buffer, p_block->i_buffer))
+        {
+            msg_Warn(p_dec,"fed with invalid OBU");
+            block_Release(p_block);
+            return NULL;
+        }
+        *pp_block = NULL;
+        block_ChainLastAppend(&p_sys->obus.pp_chain_last, p_block);
+    }
+
+    block_t *p_output = NULL;
+    while(p_sys->obus.p_chain)
+    {
+        block_t *p_frag = p_sys->obus.p_chain;
+
+        AV1_OBU_iterator_ctx_t it;
+        AV1_OBU_iterator_init(&it, p_frag->p_buffer, p_frag->i_buffer);
+        const uint8_t *p_obu; size_t i_obu;
+
+        if(!AV1_OBU_iterate_next(&it, &p_obu, &i_obu))
+        {
+            msg_Warn(p_dec,"Invalid OBU header in sequence, discarding");
+            /* frag is not OBU, drop */
+            if(p_frag->p_next == NULL)
+                p_sys->obus.pp_chain_last = &p_sys->obus.p_chain;
+            else
+                p_frag->p_next = NULL;
+            block_Release(p_frag);
+            continue;
+        }
+
+        block_t *p_obublock;
+        if(i_obu == p_frag->i_buffer)
+        {
+            p_sys->obus.p_chain = p_frag->p_next;
+            if(p_frag->p_next == NULL)
+                p_sys->obus.pp_chain_last = &p_sys->obus.p_chain;
+            else
+                p_frag->p_next = NULL;
+            p_obublock = p_frag;
+        }
+        else
+        {
+            p_obublock = block_Alloc(i_obu);
+            memcpy(p_obublock->p_buffer, p_frag->p_buffer, i_obu);
+            p_frag->i_buffer -= i_obu;
+            p_frag->p_buffer += i_obu;
+            p_obublock->i_dts = p_frag->i_dts;
+            p_obublock->i_pts = p_frag->i_pts;
+            p_obublock->i_flags = p_frag->i_flags;
+            p_frag->i_flags = 0;
+            p_frag->i_dts = VLC_TICK_INVALID;
+            p_frag->i_pts = VLC_TICK_INVALID;
+        }
+
+        p_output = ParseOBUBlock(p_dec, p_obublock);
+        if(p_output)
+            break;
+    }
+
+
+    if(!p_output && pp_block == NULL)
+        p_output = OutputQueues(p_dec, p_sys->p_sequence_header_block != NULL);
+
+    if(p_output)
+    {
+        p_output = GatherAndValidateChain(p_dec, p_output);
+        UpdateDecoderFormat(p_dec);
+    }
+
+    return p_output;
+}
+
+/*****************************************************************************
+ * Close
+ *****************************************************************************/
+static void Close(vlc_object_t *p_this)
+{
+    decoder_t *p_dec = (decoder_t*)p_this;
+    av1_sys_t *p_sys = p_dec->p_sys;
+
+    PacketizeFlush(p_dec);
+
+    free(p_sys);
+}
+
+/*****************************************************************************
+ * Open
+ *****************************************************************************/
+static int Open(vlc_object_t *p_this)
+{
+    decoder_t *p_dec = (decoder_t*)p_this;
+    av1_sys_t *p_sys;
+
+    if (p_dec->fmt_in.i_codec != VLC_CODEC_AV1)
+        return VLC_EGENERIC;
+
+    p_dec->p_sys = p_sys = calloc(1, sizeof(av1_sys_t));
+    if (!p_dec->p_sys)
+        return VLC_ENOMEM;
+
+    INITQ(obus);
+    p_sys->p_sequence_header_block = NULL;
+    p_sys->p_sequence_header = NULL;
+    p_sys->tu.b_has_visible_frame = false;
+    p_sys->tu.dts = VLC_TICK_INVALID;
+    p_sys->tu.pts = VLC_TICK_INVALID;
+    p_sys->i_seen = 0;
+    p_sys->i_next_block_flags = 0;
+    INITQ(tu.pre);
+    INITQ(tu.frame);
+    INITQ(tu.post);
+
+    /* Copy properties */
+    es_format_Copy(&p_dec->fmt_out, &p_dec->fmt_in);
+    p_dec->fmt_out.b_packetized = true;
+
+    p_dec->pf_packetize = PacketizeOBU;
+    p_dec->pf_flush = PacketizeFlush;
+
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * Module descriptor
+ *****************************************************************************/
+
+vlc_module_begin ()
+    set_category(CAT_SOUT)
+    set_subcategory(SUBCAT_SOUT_PACKETIZER)
+    set_description(N_("AV1 video packetizer"))
+    set_capability("packetizer", 50)
+    set_callbacks(Open, Close)
+vlc_module_end ()
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 0ab45843b2..504e3ffd4b 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1013,6 +1013,7 @@ modules/notify/osx_notifications.m
 modules/notify/notify.c
 modules/packetizer/a52.c
 modules/packetizer/avparser.h
+modules/packetizer/av1.c
 modules/packetizer/copy.c
 modules/packetizer/dts.c
 modules/packetizer/flac.c



More information about the vlc-commits mailing list