[vlc-commits] [Git][videolan/vlc][master] 9 commits: codec: videotoolbox: account fields in pic_pacer
Steve Lhomme (@robUx4)
gitlab at videolan.org
Sun Dec 3 08:16:54 UTC 2023
Steve Lhomme pushed to branch master at VideoLAN / VLC
Commits:
deedd6ce by François Cartegnie at 2023-12-03T07:48:23+00:00
codec: videotoolbox: account fields in pic_pacer
- - - - -
b5a9d953 by François Cartegnie at 2023-12-03T07:48:23+00:00
codec: videotoolbox: rename DPB vars
- - - - -
6413b9af by François Cartegnie at 2023-12-03T07:48:23+00:00
codec: videotoolbox: move dpb variables in their own struct
- - - - -
33992951 by François Cartegnie at 2023-12-03T07:48:23+00:00
codec: videotoolbox: move DPB grow out of output loop
minimal foc was already on list head
- - - - -
b860a7be by François Cartegnie at 2023-12-03T07:48:23+00:00
codec: videotoolbox: only use DPB context for DPB management
- - - - -
65ad721a by François Cartegnie at 2023-12-03T07:48:23+00:00
codec: videotoolbox: split DPB code
- - - - -
18b46467 by François Cartegnie at 2023-12-03T07:48:23+00:00
codec: videotoolbox: add DPB test
- - - - -
44c0ccc2 by François Cartegnie at 2023-12-03T07:48:23+00:00
codec: videotoolbox: output only single field from DPB
- - - - -
4464af83 by François Cartegnie at 2023-12-03T07:48:23+00:00
codec: videotoolbox: manage DPB as dual field buffers
- - - - -
7 changed files:
- modules/codec/Makefile.am
- modules/codec/videotoolbox/decoder.c
- + modules/codec/videotoolbox/dpb.c
- + modules/codec/videotoolbox/dpb.h
- + modules/codec/videotoolbox/dpb_test.c
- modules/codec/videotoolbox/pacer.c
- modules/codec/videotoolbox/pacer.h
Changes:
=====================================
modules/codec/Makefile.am
=====================================
@@ -351,6 +351,8 @@ EXTRA_LTLIBRARIES += liboggspots_plugin.la
codec_LTLIBRARIES += $(LTLIBoggspots)
libvideotoolbox_plugin_la_SOURCES = codec/videotoolbox/decoder.c \
+ codec/videotoolbox/dpb.c \
+ codec/videotoolbox/dpb.h \
codec/videotoolbox/pacer.c \
codec/videotoolbox/pacer.h
libvideotoolbox_plugin_la_LIBADD = libchroma_copy.la libvlc_hxxxhelper.la libvlc_vtutils.la
@@ -359,6 +361,14 @@ if HAVE_DARWIN
codec_LTLIBRARIES += libvideotoolbox_plugin.la
endif
+videotoolbox_dpb_test_SOURCES = codec/videotoolbox/dpb_test.c \
+ codec/videotoolbox/dpb.c \
+ codec/videotoolbox/dpb.h
+videotoolbox_dpb_test_CFLAGS = $(AM_CFLAGS) -DDPB_DEBUG
+
+check_PROGRAMS += videotoolbox_dpb_test
+TESTS += videotoolbox_dpb_test
+
libvideotoolbox_enc_plugin_la_SOURCES = codec/videotoolbox/encoder.c
libvideotoolbox_enc_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) # Trigger MODULE_NAME declaration
libvideotoolbox_enc_plugin_la_LIBADD = libvlc_hxxxhelper.la libvlc_vtutils.la
=====================================
modules/codec/videotoolbox/decoder.c
=====================================
@@ -38,6 +38,7 @@
#import "../../packetizer/hxxx_nal.h"
#import "../../packetizer/hxxx_sei.h"
#import "pacer.h"
+#import "dpb.h"
#import <VideoToolbox/VideoToolbox.h>
#import <VideoToolbox/VTErrors.h>
@@ -78,33 +79,8 @@ static bool deviceSupports42010bitRendering();
static Boolean deviceSupportsAdvancedProfiles();
static Boolean deviceSupportsAdvancedLevels();
-typedef struct frame_info_t frame_info_t;
-
-struct frame_info_t
-{
- picture_t *p_picture;
- int i_poc;
- int i_foc;
- vlc_tick_t pts;
- vlc_tick_t dts;
- unsigned field_rate_num;
- unsigned field_rate_den;
- bool b_flush;
- bool b_eos;
- bool b_keyframe;
- bool b_leading;
- bool b_field;
- bool b_progressive;
- bool b_top_field_first;
- uint8_t i_num_ts;
- uint8_t i_max_reorder;
- unsigned i_length;
- frame_info_t *p_next;
-};
-
#pragma mark - decoder structure
-#define H264_MAX_DPB 16
#define VT_MAX_SEI_COUNT 16
#define DEFAULT_FRAME_RATE_NUM 30000
@@ -149,12 +125,7 @@ typedef struct decoder_sys_t
vlc_mutex_t lock;
bool b_discard_decoder_output;
- frame_info_t *p_pic_reorder;
- uint8_t i_pic_reorder;
- uint8_t i_pic_reorder_max;
- bool b_strict_reorder;
- bool b_invalid_pic_reorder_max;
- bool b_poc_based_reorder;
+ struct dpb_s dpb;
bool b_format_propagated;
@@ -316,7 +287,7 @@ static bool FillReorderInfoH264(decoder_t *p_dec, const block_t *p_block,
p_info->i_num_ts = h264_get_num_ts(p_sps, &slice, sei.i_pic_struct,
p_info->i_foc, bFOC);
unsigned dummy;
- h264_get_dpb_values(p_sps, &p_info->i_max_reorder, &dummy);
+ h264_get_dpb_values(p_sps, &p_info->i_max_pics_buffering, &dummy);
if (!p_info->b_progressive)
p_info->b_top_field_first = (sei.i_pic_struct % 2 == 1);
@@ -377,6 +348,7 @@ static bool InitH264(decoder_t *p_dec)
return false;
}
p_sys->p_codec_context = ctx;
+ p_sys->dpb.i_fields_per_buffer = 2;
return true;
}
@@ -706,9 +678,8 @@ static bool FillReorderInfoHEVC(decoder_t *p_dec, const block_t *p_block,
p_info->i_foc = POC; /* clearly looks wrong :/ */
p_info->i_num_ts = hevc_get_num_clock_ts(p_sps, sei.p_timing);
uint8_t dummy;
- hevc_get_dpb_values(p_sps, &p_info->i_max_reorder, &dummy, &dummy);
+ hevc_get_dpb_values(p_sps, &p_info->i_max_pics_buffering, &dummy, &dummy);
VLC_UNUSED(dummy);
-
p_info->b_flush = (POC == 0) ||
(i_nal_type >= HEVC_NAL_IDR_N_LP &&
i_nal_type <= HEVC_NAL_IRAP_VCL23);
@@ -814,131 +785,18 @@ static CFDictionaryRef CopyDecoderExtradataMPEG4(decoder_t *p_dec)
/* !Codec Specific */
-static void InsertIntoDPB(decoder_sys_t *p_sys, frame_info_t *p_info)
-{
- frame_info_t **pp_lead_in = &p_sys->p_pic_reorder;
-
- for ( ;; pp_lead_in = & ((*pp_lead_in)->p_next))
- {
- bool b_insert;
- if (*pp_lead_in == NULL)
- b_insert = true;
- else if (p_sys->b_poc_based_reorder)
- b_insert = ((*pp_lead_in)->i_foc > p_info->i_foc);
- else
- b_insert = ((*pp_lead_in)->pts >= p_info->pts);
-
- if (b_insert)
- {
- p_info->p_next = *pp_lead_in;
- *pp_lead_in = p_info;
- p_sys->i_pic_reorder++;
- break;
- }
- }
-#if 0
- for (frame_info_t *p_in=p_sys->p_pic_reorder; p_in; p_in = p_in->p_next)
- printf(" %d", p_in->i_foc);
- printf("\n");
-#endif
-}
-
-static int RemoveOneFrameFromDPB(decoder_sys_t *p_sys, picture_t **pp_ret)
-{
- frame_info_t *p_info = p_sys->p_pic_reorder;
- if (p_info == NULL)
- {
- *pp_ret = NULL;
- return VLC_EGENERIC;
- }
-
- const int i_framepoc = p_info->i_poc;
- const vlc_tick_t i_framepts = p_info->pts;
-
- picture_t **pp_ret_last = pp_ret;
- bool b_dequeue;
-
- do
- {
- /* Asynchronous fallback time init */
- if(date_Get(&p_sys->pts) == VLC_TICK_INVALID)
- {
- date_Set(&p_sys->pts, p_info->pts != VLC_TICK_INVALID ?
- p_info->pts : p_info->dts );
- }
-
- /* Compute time from output if missing */
- if (p_info->pts == VLC_TICK_INVALID)
- p_info->pts = date_Get(&p_sys->pts);
- else
- date_Set(&p_sys->pts, p_info->pts);
-
- /* Update frame rate (used on interpolation) */
- if(p_info->field_rate_num != p_sys->pts.i_divider_num ||
- p_info->field_rate_den != p_sys->pts.i_divider_den)
- {
- /* no date_Change due to possible invalid num */
- date_Init(&p_sys->pts, p_info->field_rate_num,
- p_info->field_rate_den);
- date_Set(&p_sys->pts, p_info->pts);
- }
-
- /* Set next picture time, in case it is missing */
- if (p_info->i_length)
- date_Set(&p_sys->pts, p_info->pts + p_info->i_length);
- else
- date_Increment(&p_sys->pts, p_info->i_num_ts);
-
- if( p_info->p_picture ) /* Can have no picture attached to entry on error */
- {
- if( p_info->p_picture->date == VLC_TICK_INVALID )
- p_info->p_picture->date = p_info->pts;
-
- /* Extract attached field to output list */
- *pp_ret_last = p_info->p_picture;
- pp_ret_last = &p_info->p_picture->p_next;
- }
-
- p_sys->i_pic_reorder--;
-
- p_sys->p_pic_reorder = p_info->p_next;
- free(p_info);
- p_info = p_sys->p_pic_reorder;
-
- if (p_info)
- {
- if (p_sys->b_poc_based_reorder)
- b_dequeue = (p_info->i_poc == i_framepoc);
- else
- b_dequeue = (p_info->pts == i_framepts);
- }
- else
- {
- b_dequeue = false;
- }
-
- } while(b_dequeue);
-
- return VLC_SUCCESS;
-}
-
static void DrainDPBLocked(decoder_t *p_dec, bool flush)
{
decoder_sys_t *p_sys = p_dec->p_sys;
- for ( ;; )
+ while (p_sys->dpb.i_size > 0)
{
- picture_t *p_fields;
- if(RemoveOneFrameFromDPB(p_sys, &p_fields) != VLC_SUCCESS)
- break;
- for ( ; p_fields; )
+ picture_t *p_output = OutputNextFrameFromDPB(&p_sys->dpb, &p_sys->pts);
+ if(p_output)
{
- picture_t *p_next = p_fields->p_next;
- p_fields->p_next = NULL;
if (flush)
- picture_Release(p_fields);
+ picture_Release(p_output);
else
- decoder_QueueVideo(p_dec, p_fields);
- p_fields = p_next;
+ decoder_QueueVideo(p_dec, p_output);
}
}
}
@@ -991,52 +849,45 @@ static void OnDecodedFrame(decoder_t *p_dec, frame_info_t *p_info)
{
decoder_sys_t *p_sys = p_dec->p_sys;
- if(!p_sys->b_invalid_pic_reorder_max &&
- p_info->i_max_reorder != p_sys->i_pic_reorder_max)
+ if(!p_sys->dpb.b_invalid_pic_reorder_max &&
+ p_info->i_max_pics_buffering != p_sys->dpb.i_max_pics)
{
- p_sys->i_pic_reorder_max = p_info->i_max_reorder;
- pic_pacer_UpdateMaxBuffering(p_sys->pic_pacer, p_sys->i_pic_reorder_max);
+ p_sys->dpb.i_max_pics = p_info->i_max_pics_buffering;
+ pic_pacer_UpdateMaxBuffering(p_sys->pic_pacer, p_sys->dpb.i_max_pics);
}
- while(p_info->b_flush || p_sys->i_pic_reorder >= p_sys->i_pic_reorder_max)
+ /* First check if DPB sizing was correct before removing one frame */
+ if (!p_sys->dpb.b_strict_reorder && !p_info->b_flush &&
+ p_sys->dpb.i_size == p_sys->dpb.i_max_pics &&
+ p_sys->dpb.i_size && p_sys->dpb.i_max_pics < DPB_MAX_PICS)
{
- /* First check if DPB sizing was correct before removing one frame */
- if (p_sys->p_pic_reorder && !p_sys->b_strict_reorder && !p_info->b_flush &&
- p_sys->i_pic_reorder_max < H264_MAX_DPB)
+ if (p_sys->dpb.b_poc_based_reorder && p_sys->dpb.p_entries->i_foc > p_info->i_foc)
{
- if (p_sys->b_poc_based_reorder && p_sys->p_pic_reorder->i_foc > p_info->i_foc)
- {
- p_sys->b_invalid_pic_reorder_max = true;
- p_sys->i_pic_reorder_max++;
- pic_pacer_UpdateMaxBuffering(p_sys->pic_pacer, p_sys->i_pic_reorder_max);
- msg_Dbg(p_dec, "Raising max DPB to %"PRIu8, p_sys->i_pic_reorder_max);
- break;
- }
- else if (!p_sys->b_poc_based_reorder &&
- p_info->pts > VLC_TICK_INVALID &&
- p_sys->p_pic_reorder->pts > p_info->pts)
- {
- p_sys->b_invalid_pic_reorder_max = true;
- p_sys->i_pic_reorder_max++;
- pic_pacer_UpdateMaxBuffering(p_sys->pic_pacer, p_sys->i_pic_reorder_max);
- msg_Dbg(p_dec, "Raising max DPB to %"PRIu8, p_sys->i_pic_reorder_max);
- break;
- }
+ p_sys->dpb.b_invalid_pic_reorder_max = true;
+ p_sys->dpb.i_max_pics++;
+ pic_pacer_UpdateMaxBuffering(p_sys->pic_pacer, p_sys->dpb.i_max_pics);
+ msg_Dbg(p_dec, "Raising max DPB to %"PRIu8, p_sys->dpb.i_max_pics);
}
-
- picture_t *p_fields;
- if(RemoveOneFrameFromDPB(p_sys, &p_fields) != VLC_SUCCESS)
- break;
- for(; p_fields;)
+ else if (!p_sys->dpb.b_poc_based_reorder &&
+ p_info->pts > VLC_TICK_INVALID &&
+ p_sys->dpb.p_entries->pts > p_info->pts)
{
- picture_t *p_next = p_fields->p_next;
- p_fields->p_next = NULL;
- decoder_QueueVideo(p_dec, p_fields);
- p_fields = p_next;
+ p_sys->dpb.b_invalid_pic_reorder_max = true;
+ p_sys->dpb.i_max_pics++;
+ pic_pacer_UpdateMaxBuffering(p_sys->pic_pacer, p_sys->dpb.i_max_pics);
+ msg_Dbg(p_dec, "Raising max DPB to %"PRIu8, p_sys->dpb.i_max_pics);
}
}
- InsertIntoDPB(p_sys, p_info);
+ while(p_info->b_flush || p_sys->dpb.i_size >= p_sys->dpb.i_max_pics)
+ {
+ picture_t *p_output = OutputNextFrameFromDPB(&p_sys->dpb, &p_sys->pts);
+ if(!p_output)
+ break;
+ decoder_QueueVideo(p_dec, p_output);
+ }
+
+ InsertIntoDPB(&p_sys->dpb, p_info);
}
static CMVideoCodecType CodecPrecheck(decoder_t *p_dec)
@@ -1464,7 +1315,8 @@ static int OpenDecoder(vlc_object_t *p_this)
p_sys->session = NULL;
p_sys->codec = codec;
p_sys->videoFormatDescription = NULL;
- p_sys->i_pic_reorder_max = 4;
+ p_sys->dpb.i_max_pics = 4;
+ p_sys->dpb.i_fields_per_buffer = 1;
p_sys->vtsession_status = VTSESSION_STATUS_OK;
p_sys->b_cvpx_format_forced = false;
/* will be fixed later */
@@ -1521,8 +1373,8 @@ static int OpenDecoder(vlc_object_t *p_this)
p_sys->pf_configure_vout = ConfigureVoutH264;
p_sys->pf_copy_extradata = CopyDecoderExtradataH264;
p_sys->pf_fill_reorder_info = FillReorderInfoH264;
- p_sys->b_strict_reorder = false;
- p_sys->b_poc_based_reorder = true;
+ p_sys->dpb.b_strict_reorder = false;
+ p_sys->dpb.b_poc_based_reorder = true;
p_sys->start_sync_state = STATE_BITSTREAM_WAITING_RAP;
break;
@@ -1537,8 +1389,8 @@ static int OpenDecoder(vlc_object_t *p_this)
p_sys->pf_configure_vout = ConfigureVoutHEVC;
p_sys->pf_copy_extradata = CopyDecoderExtradataHEVC;
p_sys->pf_fill_reorder_info = FillReorderInfoHEVC;
- p_sys->b_strict_reorder = true;
- p_sys->b_poc_based_reorder = true;
+ p_sys->dpb.b_strict_reorder = true;
+ p_sys->dpb.b_poc_based_reorder = true;
p_sys->start_sync_state = STATE_BITSTREAM_WAITING_RAP;
break;
@@ -1744,7 +1596,7 @@ static CMSampleBufferRef VTSampleBufferCreate(decoder_t *p_dec,
CMBlockBufferRef block_buf = NULL;
CMSampleBufferRef sample_buf = NULL;
CMTime pts;
- if (!p_sys->b_poc_based_reorder && p_block->i_pts == VLC_TICK_INVALID)
+ if (!p_sys->dpb.b_poc_based_reorder && p_block->i_pts == VLC_TICK_INVALID)
pts = CMTimeMake(p_block->i_dts, CLOCK_FREQ);
else
pts = CMTimeMake(p_block->i_pts, CLOCK_FREQ);
@@ -1906,8 +1758,8 @@ static void Drain(decoder_t *p_dec, bool flush)
vlc_mutex_lock(&p_sys->lock);
DrainDPBLocked(p_dec, flush);
- picture_t *p_output;
- assert(RemoveOneFrameFromDPB(p_sys, &p_output) == VLC_EGENERIC);
+ assert(p_sys->dpb.i_size == 0);
+ assert(p_sys->dpb.p_entries == NULL);
p_sys->b_discard_decoder_output = false;
p_sys->sync_state = p_sys->start_sync_state;
vlc_mutex_unlock(&p_sys->lock);
@@ -2094,7 +1946,7 @@ static int DecodeBlock(decoder_t *p_dec, block_t *p_block)
goto skip;
}
- pic_pacer_WaitAllocatableSlot(p_sys->pic_pacer);
+ pic_pacer_WaitAllocatableSlot(p_sys->pic_pacer, p_info->b_field);
VTDecodeInfoFlags flagOut;
VTDecodeFrameFlags decoderFlags = kVTDecodeFrame_EnableAsynchronousDecompression;
@@ -2106,7 +1958,7 @@ static int DecodeBlock(decoder_t *p_dec, block_t *p_block)
enum vtsession_status vtsession_status;
if (HandleVTStatus(p_dec, status, &vtsession_status) == VLC_SUCCESS)
{
- pic_pacer_AccountScheduledDecode(p_sys->pic_pacer);
+ pic_pacer_AccountScheduledDecode(p_sys->pic_pacer, p_info->b_field);
if(p_sys->decoder_state != STATE_DECODER_STARTED)
{
@@ -2216,9 +2068,8 @@ video_context_OnPicReleased(vlc_video_context *vctx, unsigned nb_fields)
{
struct pic_pacer *pic_pacer =
vlc_video_context_GetCVPXPrivate(vctx, CVPX_VIDEO_CONTEXT_VIDEOTOOLBOX);
- VLC_UNUSED(nb_fields);
- pic_pacer_AccountDeallocation(pic_pacer);
+ pic_pacer_AccountDeallocation(pic_pacer, nb_fields == 1);
}
static void DecoderCallback(void *decompressionOutputRefCon,
@@ -2233,6 +2084,7 @@ static void DecoderCallback(void *decompressionOutputRefCon,
decoder_t *p_dec = (decoder_t *)decompressionOutputRefCon;
decoder_sys_t *p_sys = p_dec->p_sys;
frame_info_t *p_info = (frame_info_t *) sourceFrameRefCon;
+ const bool b_field = p_info->b_field;
vlc_mutex_lock(&p_sys->lock);
if (p_sys->b_discard_decoder_output)
@@ -2305,7 +2157,7 @@ static void DecoderCallback(void *decompressionOutputRefCon,
* allocating way too many frames. This can be problematic for 4K
* 10bits. To fix this issue, we ensure that we don't have too many
* output frames allocated by waiting for the vout to release them. */
- pic_pacer_AccountAllocation(p_sys->pic_pacer);
+ pic_pacer_AccountAllocation(p_sys->pic_pacer, p_info->b_field);
}
}
@@ -2322,7 +2174,7 @@ static void DecoderCallback(void *decompressionOutputRefCon,
end:
free(p_info);
vlc_mutex_unlock(&p_sys->lock);
- pic_pacer_AccountFinishedDecode(p_sys->pic_pacer);
+ pic_pacer_AccountFinishedDecode(p_sys->pic_pacer, b_field);
return;
}
=====================================
modules/codec/videotoolbox/dpb.c
=====================================
@@ -0,0 +1,116 @@
+/*****************************************************************************
+ * dpb.c: decoder picture output pacing
+ *****************************************************************************
+ * Copyright © 2015-2023 VideoLabs, VideoLAN and VLC authors
+ *
+ * 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.
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "dpb.h"
+
+#include <assert.h>
+
+//#define DPB_DEBUG
+
+void InsertIntoDPB(struct dpb_s *dpb, frame_info_t *p_info)
+{
+ frame_info_t **pp_lead_in = &dpb->p_entries;
+
+ for ( ;; pp_lead_in = & ((*pp_lead_in)->p_next))
+ {
+ bool b_insert;
+ if (*pp_lead_in == NULL)
+ b_insert = true;
+ else if (dpb->b_poc_based_reorder)
+ b_insert = ((*pp_lead_in)->i_foc > p_info->i_foc);
+ else
+ b_insert = ((*pp_lead_in)->pts >= p_info->pts);
+
+ if (b_insert)
+ {
+ p_info->p_next = *pp_lead_in;
+ *pp_lead_in = p_info;
+ dpb->i_stored_fields += (p_info->b_field ? 1 : 2);
+ if(dpb->i_fields_per_buffer == 2)
+ dpb->i_size = (dpb->i_stored_fields + 1)/2;
+ else
+ dpb->i_size++;
+ break;
+ }
+ }
+#ifdef DPB_DEBUG
+ for (frame_info_t *p_in=dpb->p_entries; p_in; p_in = p_in->p_next)
+ printf(" %d", p_in->i_foc);
+ printf("\n");
+#endif
+}
+
+picture_t * OutputNextFrameFromDPB(struct dpb_s *dpb, date_t *ptsdate)
+{
+ frame_info_t *p_info = dpb->p_entries;
+ if (p_info == NULL)
+ return NULL;
+
+ /* Asynchronous fallback time init */
+ if(date_Get(ptsdate) == VLC_TICK_INVALID)
+ {
+ date_Set(ptsdate, p_info->pts != VLC_TICK_INVALID ?
+ p_info->pts : p_info->dts );
+ }
+
+ /* Compute time from output if missing */
+ if (p_info->pts == VLC_TICK_INVALID)
+ p_info->pts = date_Get(ptsdate);
+ else
+ date_Set(ptsdate, p_info->pts);
+
+ /* Update frame rate (used on interpolation) */
+ if(p_info->field_rate_num != ptsdate->i_divider_num ||
+ p_info->field_rate_den != ptsdate->i_divider_den)
+ {
+ /* no date_Change due to possible invalid num */
+ date_Init(ptsdate, p_info->field_rate_num,
+ p_info->field_rate_den);
+ date_Set(ptsdate, p_info->pts);
+ }
+
+ /* Set next picture time, in case it is missing */
+ if (p_info->i_length)
+ date_Set(ptsdate, p_info->pts + p_info->i_length);
+ else
+ date_Increment(ptsdate, p_info->i_num_ts);
+
+ /* Extract attached field to output */
+ picture_t *p_output = p_info->p_picture;
+ if( p_info->p_picture ) /* Can have no picture attached to entry on error */
+ {
+ if( p_info->p_picture->date == VLC_TICK_INVALID )
+ p_info->p_picture->date = p_info->pts;
+ }
+
+ dpb->i_stored_fields -= (p_info->b_field ? 1 : 2);
+ if(dpb->i_fields_per_buffer == 2)
+ dpb->i_size = (dpb->i_stored_fields + 1)/2;
+ else
+ dpb->i_size--;
+
+ dpb->p_entries = p_info->p_next;
+ free(p_info);
+
+ return p_output;
+}
=====================================
modules/codec/videotoolbox/dpb.h
=====================================
@@ -0,0 +1,69 @@
+/*****************************************************************************
+ * dpb.h: decoder picture output pacing
+ *****************************************************************************
+ * Copyright © 2015-2023 VideoLabs, VideoLAN and VLC authors
+ *
+ * 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.
+ *****************************************************************************/
+#ifndef VIDEOTOOLBOX_DPB_H
+#define VIDEOTOOLBOX_DPB_H
+
+#define DPB_MAX_PICS 16
+
+#include <vlc_common.h>
+#include <vlc_tick.h>
+#include <vlc_picture.h>
+
+typedef struct frame_info_t frame_info_t;
+
+struct frame_info_t
+{
+ picture_t *p_picture;
+ int i_poc;
+ int i_foc;
+ vlc_tick_t pts;
+ vlc_tick_t dts;
+ unsigned field_rate_num;
+ unsigned field_rate_den;
+ bool b_flush;
+ bool b_eos;
+ bool b_keyframe;
+ bool b_leading;
+ bool b_field;
+ bool b_progressive;
+ bool b_top_field_first;
+ uint8_t i_num_ts;
+ uint8_t i_max_pics_buffering;
+ unsigned i_length;
+ frame_info_t *p_next;
+};
+
+struct dpb_s
+{
+ frame_info_t *p_entries;
+ uint8_t i_size; /* number of virtual buffers used (depends on i_fields_per_buffer) */
+ uint8_t i_stored_fields;
+ uint8_t i_max_pics;
+ uint8_t i_fields_per_buffer; /* stores 2 fields or 1 field/frame per buffer */
+ bool b_strict_reorder;
+ bool b_invalid_pic_reorder_max;
+ bool b_poc_based_reorder;
+};
+
+void InsertIntoDPB(struct dpb_s *, frame_info_t *);
+
+picture_t * OutputNextFrameFromDPB(struct dpb_s *, date_t *);
+
+#endif // VIDEOTOOLBOX_DPB_H
=====================================
modules/codec/videotoolbox/dpb_test.c
=====================================
@@ -0,0 +1,358 @@
+/*****************************************************************************
+ * dpb_test.c:
+ *****************************************************************************
+ * Copyright © 2023 VideoLabs, VideoLAN and VLC authors
+ *
+ * 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.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#undef NDEBUG
+
+#ifndef DPB_DEBUG
+# warning "DPB_DEBUG not defined, no useful test info"
+#endif
+
+#include "dpb.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+static void pic_release(picture_t *p)
+{
+ free(p);
+}
+
+static inline frame_info_t *withpic(frame_info_t *info, int poc)
+{
+ info->p_picture = calloc(1, sizeof(*info->p_picture));
+ if(!info->p_picture)
+ abort();
+ info->p_picture->p_sys = (void *)(uint64_t)poc;
+ return info;
+}
+
+static inline frame_info_t *infocopy(const frame_info_t *ref)
+{
+ frame_info_t *frame;
+ if(ref)
+ frame = malloc(2 * sizeof(*frame));
+ else
+ frame = calloc(2, sizeof(*frame));
+ if(!frame)
+ abort();
+ if(ref)
+ {
+ *frame = *ref;
+ frame->p_picture = NULL;
+ frame->p_next = NULL;
+ }
+ return frame;
+}
+
+static void VaCheckOutput(picture_t *output, va_list ap)
+{
+ for(;;)
+ {
+ int poc = va_arg(ap, int);
+ if(poc == -1)
+ {
+ fprintf(stderr, "no output expected\n");
+ assert(output == NULL);
+ break;
+ }
+ if(output == NULL)
+ {
+ fprintf(stderr, "no output, was expected %d", poc);
+ abort();
+ }
+ int outpoc = ((uint64_t)output->p_sys);
+ fprintf(stderr, "output %d, expected %d\n", outpoc, poc);
+ assert(outpoc == poc);
+ picture_t *next = output->p_next;
+ output->p_next = NULL;
+ pic_release(output);
+ output = next;
+ };
+ assert(output == NULL);
+}
+
+static void CheckDrain(struct dpb_s *dpb, date_t *ptsdate, ...)
+{
+ fprintf(stderr,"drain\n");
+ picture_t *output = NULL;
+ picture_t **next = &output;
+ while(dpb->i_size)
+ {
+ *next = OutputNextFrameFromDPB(dpb, ptsdate);
+ if(!*next)
+ break;
+ next = &((*next)->p_next);
+ }
+ va_list args;
+ va_start(args, ptsdate);
+ VaCheckOutput(output, args);
+ va_end(args);
+}
+
+static void CheckOutput(struct dpb_s *dpb, date_t *ptsdate, frame_info_t *info, ...)
+{
+ fprintf(stderr, "enqueing foc %d flush %d dpb sz %d\n", info->i_foc,
+ info->b_flush, dpb->i_size);
+ dpb->i_max_pics = info->i_max_pics_buffering;
+ picture_t *output = NULL;
+ picture_t **next = &output;
+ while(info->b_flush || dpb->i_size >= dpb->i_max_pics)
+ {
+ *next = OutputNextFrameFromDPB(dpb, ptsdate);
+ if(!*next)
+ break;
+ next = &((*next)->p_next);
+ }
+ assert(dpb->i_size < DPB_MAX_PICS);
+ va_list args;
+ va_start(args, info);
+ VaCheckOutput(output, args);
+ va_end(args);
+ InsertIntoDPB(dpb, info);
+}
+
+static void CheckDPBWithFramesTest()
+{
+ struct dpb_s dpb = {0};
+ dpb.b_strict_reorder = true;
+ dpb.b_poc_based_reorder = true;
+ dpb.i_fields_per_buffer = 2;
+
+ frame_info_t info = {0};
+ info.field_rate_num = 30000;
+ info.field_rate_den = 1000;
+ info.b_progressive = true;
+ info.b_top_field_first = true;
+ info.i_num_ts = 2;
+ info.i_max_pics_buffering = 4;
+
+ date_t pts;
+ date_Init(&pts, info.field_rate_num, info.field_rate_den);
+ date_Set(&pts, VLC_TICK_0);
+
+ info.i_foc = 0;
+ info.i_poc = info.i_foc & ~1;
+ info.b_flush = (info.i_foc == 0);
+ CheckOutput(&dpb, &pts, withpic(infocopy(&info), info.i_foc), -1);
+
+ info.i_foc = 4;
+ info.i_poc = info.i_foc & ~1;
+ info.b_flush = (info.i_foc == 0);
+ CheckOutput(&dpb, &pts, withpic(infocopy(&info), info.i_foc), -1);
+
+ info.i_foc = 2;
+ info.i_poc = info.i_foc & ~1;
+ info.b_flush = (info.i_foc == 0);
+ CheckOutput(&dpb, &pts, withpic(infocopy(&info), info.i_foc), -1);
+
+ info.i_foc = 0;
+ info.i_poc = info.i_foc & ~1;
+ info.b_flush = (info.i_foc == 0);
+ CheckOutput(&dpb, &pts, withpic(infocopy(&info), info.i_foc), 0, 2, 4, -1);
+
+ info.i_foc = 8;
+ info.i_poc = info.i_foc & ~1;
+ info.b_flush = (info.i_foc == 0);
+ CheckOutput(&dpb, &pts, withpic(infocopy(&info), info.i_foc), -1);
+
+ info.i_foc = 2;
+ info.i_poc = info.i_foc & ~1;
+ info.b_flush = (info.i_foc == 0);
+ CheckOutput(&dpb, &pts, withpic(infocopy(&info), info.i_foc), -1);
+
+ info.i_foc = 6;
+ info.i_poc = info.i_foc & ~1;
+ info.b_flush = (info.i_foc == 0);
+ CheckOutput(&dpb, &pts, withpic(infocopy(&info), info.i_foc), -1);
+
+ info.i_foc = 4;
+ info.i_poc = info.i_foc & ~1;
+ info.b_flush = (info.i_foc == 0);
+ CheckOutput(&dpb, &pts, withpic(infocopy(&info), info.i_foc), 0, -1);
+
+ /* depth reduction */
+ info.i_max_pics_buffering = 2;
+
+ info.i_foc = 10;
+ info.i_poc = info.i_foc & ~1;
+ info.b_flush = (info.i_foc == 0);
+ CheckOutput(&dpb, &pts, withpic(infocopy(&info), info.i_foc), 2, 4, 6, -1);
+
+ info.i_foc = 0;
+ info.i_poc = info.i_foc & ~1;
+ info.b_flush = (info.i_foc == 0);
+ CheckOutput(&dpb, &pts, withpic(infocopy(&info), info.i_foc), 8, 10, -1);
+ assert(dpb.i_size == 1);
+
+ CheckDrain(&dpb, &pts, 0, -1);
+
+ assert(dpb.i_size == 0);
+}
+
+static void CheckDPBWithFieldsTest()
+{
+ struct dpb_s dpb = {0};
+ dpb.b_strict_reorder = true;
+ dpb.b_poc_based_reorder = true;
+ dpb.i_fields_per_buffer = 1;
+
+ frame_info_t info = {0};
+ info.field_rate_num = 30000;
+ info.field_rate_den = 1000;
+ info.b_progressive = true;
+ info.b_top_field_first = true;
+ info.i_num_ts = 1;
+ info.i_max_pics_buffering = 2;
+
+ /* Codec stores 1 field per buffer */
+ date_t pts;
+ date_Init(&pts, info.field_rate_num, info.field_rate_den);
+ date_Set(&pts, VLC_TICK_0);
+
+ info.b_field = true;
+
+ info.i_foc = 0;
+ info.i_poc = info.i_foc & ~1;
+ info.b_flush = (info.i_foc == 0);
+ CheckOutput(&dpb, &pts, withpic(infocopy(&info), info.i_foc), -1);
+
+ info.i_foc = 2;
+ info.i_poc = info.i_foc & ~1;
+ info.b_flush = (info.i_foc == 0);
+ CheckOutput(&dpb, &pts, withpic(infocopy(&info), info.i_foc), -1);
+
+ assert(dpb.i_stored_fields == 2);
+ assert(dpb.i_size == 2);
+
+ info.i_foc = 1;
+ info.i_poc = info.i_foc & ~1;
+ info.b_flush = (info.i_foc == 0);
+ CheckOutput(&dpb, &pts, withpic(infocopy(&info), info.i_foc), 0, -1);
+
+ CheckDrain(&dpb, &pts, 1, 2, -1);
+
+ assert(dpb.i_stored_fields == 0);
+ assert(dpb.i_size == 0);
+
+ /* Codec stores 2 fields per buffer */
+ dpb.i_fields_per_buffer = 2;
+
+ info.i_foc = 0;
+ info.i_poc = info.i_foc & ~1;
+ info.b_flush = (info.i_foc == 0);
+ CheckOutput(&dpb, &pts, withpic(infocopy(&info), info.i_foc), -1);
+
+ info.i_foc = 2;
+ info.i_poc = info.i_foc & ~1;
+ info.b_flush = (info.i_foc == 0);
+ CheckOutput(&dpb, &pts, withpic(infocopy(&info), info.i_foc), -1);
+
+ assert(dpb.i_stored_fields == 2);
+ assert(dpb.i_size == 1);
+
+ info.i_foc = 1;
+ info.i_poc = info.i_foc & ~1;
+ info.b_flush = (info.i_foc == 0);
+ CheckOutput(&dpb, &pts, withpic(infocopy(&info), info.i_foc), -1);
+
+ assert(dpb.i_stored_fields == 3);
+ assert(dpb.i_size == 2);
+
+ CheckDrain(&dpb, &pts, 0, 1, 2, -1);
+
+ assert(dpb.i_stored_fields == 0);
+ assert(dpb.i_size == 0);
+
+ /* progressive/mbaff/field mix for fun 1 field per buffer */
+ dpb.i_fields_per_buffer = 1;
+ info.i_max_pics_buffering = 3;
+
+ info.b_field = false;
+ info.i_foc = 0;
+ info.i_poc = info.i_foc & ~1;
+ info.b_flush = (info.i_foc == 0);
+ CheckOutput(&dpb, &pts, withpic(infocopy(&info), info.i_foc), -1);
+
+ info.b_field = true;
+ info.i_foc = 3;
+ info.i_poc = info.i_foc & ~1;
+ info.b_flush = (info.i_foc == 0);
+ CheckOutput(&dpb, &pts, withpic(infocopy(&info), info.i_foc), -1);
+
+ assert(dpb.i_stored_fields == 3);
+ assert(dpb.i_size == 2);
+
+ info.i_foc = 2;
+ info.i_poc = info.i_foc & ~1;
+ info.b_flush = (info.i_foc == 0);
+ CheckOutput(&dpb, &pts, withpic(infocopy(&info), info.i_foc), -1);
+
+ assert(dpb.i_stored_fields == 4);
+ assert(dpb.i_size == 3);
+
+ CheckDrain(&dpb, &pts, 0, 2, 3, -1);
+
+ assert(dpb.i_stored_fields == 0);
+ assert(dpb.i_size == 0);
+
+ /* progressive/mbaff/field mix for fun 2 fields per buffer */
+ dpb.i_fields_per_buffer = 2;
+ info.i_max_pics_buffering = 3;
+
+ info.b_field = false;
+ info.i_foc = 0;
+ info.i_poc = info.i_foc & ~1;
+ info.b_flush = (info.i_foc == 0);
+ CheckOutput(&dpb, &pts, withpic(infocopy(&info), info.i_foc), -1);
+
+ info.b_field = true;
+ info.i_foc = 3;
+ info.i_poc = info.i_foc & ~1;
+ info.b_flush = (info.i_foc == 0);
+ CheckOutput(&dpb, &pts, withpic(infocopy(&info), info.i_foc), -1);
+
+ assert(dpb.i_stored_fields == 3);
+ assert(dpb.i_size == 2);
+
+ info.i_foc = 2;
+ info.i_poc = info.i_foc & ~1;
+ info.b_flush = (info.i_foc == 0);
+ CheckOutput(&dpb, &pts, withpic(infocopy(&info), info.i_foc), -1);
+
+ assert(dpb.i_stored_fields == 4);
+ assert(dpb.i_size == 2);
+
+ CheckDrain(&dpb, &pts, 0, 2, 3, -1);
+
+ assert(dpb.i_stored_fields == 0);
+ assert(dpb.i_size == 0);
+}
+
+int main(void)
+{
+ CheckDPBWithFramesTest();
+ CheckDPBWithFieldsTest();
+ return 0;
+}
=====================================
modules/codec/videotoolbox/pacer.c
=====================================
@@ -41,62 +41,63 @@ void pic_pacer_Init(struct pic_pacer *pic_pacer)
{
vlc_mutex_init(&pic_pacer->lock);
vlc_cond_init(&pic_pacer->wait);
- pic_pacer->nb_out = 0;
- pic_pacer->allocated_max = 6;
- pic_pacer->allocated_next = pic_pacer->allocated_max;
- pic_pacer->queued_for_decode = 0;
+ pic_pacer->nb_fields_out = 0;
+ pic_pacer->allocated_fields_max = 6 /* pics */ * 2;
+ pic_pacer->allocated_fields_next = pic_pacer->allocated_fields_max;
+ pic_pacer->queued_fields_for_decode = 0;
}
-void pic_pacer_AccountAllocation(struct pic_pacer *pic_pacer)
+void pic_pacer_AccountAllocation(struct pic_pacer *pic_pacer, bool b_field)
{
vlc_mutex_lock(&pic_pacer->lock);
- pic_pacer->nb_out += 1;
+ pic_pacer->nb_fields_out += b_field ? 1 : 2;
vlc_mutex_unlock(&pic_pacer->lock);
}
-void pic_pacer_AccountScheduledDecode(struct pic_pacer *pic_pacer)
+void pic_pacer_AccountScheduledDecode(struct pic_pacer *pic_pacer, bool b_field)
{
vlc_mutex_lock(&pic_pacer->lock);
- pic_pacer->queued_for_decode += 1;
+ pic_pacer->queued_fields_for_decode += b_field ? 1 : 2;
vlc_mutex_unlock(&pic_pacer->lock);
}
-void pic_pacer_AccountFinishedDecode(struct pic_pacer *pic_pacer)
+void pic_pacer_AccountFinishedDecode(struct pic_pacer *pic_pacer, bool b_field)
{
vlc_mutex_lock(&pic_pacer->lock);
- pic_pacer->queued_for_decode -= 1;
+ pic_pacer->queued_fields_for_decode -= b_field ? 1 : 2;
vlc_cond_signal(&pic_pacer->wait);
vlc_mutex_unlock(&pic_pacer->lock);
}
-void pic_pacer_WaitAllocatableSlot(struct pic_pacer *pic_pacer)
+void pic_pacer_WaitAllocatableSlot(struct pic_pacer *pic_pacer, bool b_field)
{
vlc_mutex_lock(&pic_pacer->lock);
- uint8_t allocatable_total = pic_pacer->allocated_max + PIC_PACER_DECODE_QUEUE;
+ uint8_t allocatable_fields_total = pic_pacer->allocated_fields_max + PIC_PACER_DECODE_QUEUE;
- while( pic_pacer->queued_for_decode + pic_pacer->nb_out >= allocatable_total )
+ while( pic_pacer->queued_fields_for_decode +
+ pic_pacer->nb_fields_out + (1 - !!b_field) >= allocatable_fields_total )
{
#ifdef PIC_PACER_DEBUG
fprintf(stderr, "input pacing %d+%d >= %d\n",
- pic_pacer->queued_for_decode, pic_pacer->nb_out, allocatable_total);
+ pic_pacer->queued_fields_for_decode, pic_pacer->nb_fields_out, allocatable_fields_total);
#endif
vlc_cond_wait(&pic_pacer->wait, &pic_pacer->lock);
/*update*/
- allocatable_total = pic_pacer->allocated_max + PIC_PACER_DECODE_QUEUE;
+ allocatable_fields_total = pic_pacer->allocated_fields_max + PIC_PACER_DECODE_QUEUE;
}
vlc_mutex_unlock(&pic_pacer->lock);
}
-void pic_pacer_AccountDeallocation(struct pic_pacer *pic_pacer)
+void pic_pacer_AccountDeallocation(struct pic_pacer *pic_pacer, bool b_field)
{
vlc_mutex_lock(&pic_pacer->lock);
- assert(pic_pacer->nb_out > 0);
- pic_pacer->nb_out -= 1;
+ assert(pic_pacer->nb_fields_out > 0);
+ pic_pacer->nb_fields_out -= (b_field ? 1 : 2);
/* our shrink condition */
- if(pic_pacer->allocated_next < pic_pacer->allocated_max &&
- pic_pacer->nb_out <= pic_pacer->allocated_next)
- pic_pacer->allocated_max = pic_pacer->allocated_next;
+ if(pic_pacer->allocated_fields_next < pic_pacer->allocated_fields_max &&
+ pic_pacer->nb_fields_out <= pic_pacer->allocated_fields_next)
+ pic_pacer->allocated_fields_max = pic_pacer->allocated_fields_next;
vlc_cond_signal(&pic_pacer->wait);
@@ -107,21 +108,21 @@ void pic_pacer_UpdateMaxBuffering(struct pic_pacer *pic_pacer, uint8_t pic_max)
{
vlc_mutex_lock(&pic_pacer->lock);
- pic_max += PIC_PACER_ALLOCATABLE_MAX;
- bool b_growing = pic_max > pic_pacer->allocated_max;
+ const uint8_t fields_max = (pic_max + PIC_PACER_ALLOCATABLE_MAX) * 2;
+ bool b_growing = fields_max > pic_pacer->allocated_fields_max;
#ifdef PIC_PACER_DEBUG
fprintf(stderr, "updating pacer max %d/%d to %d\n",
- pic_pacer->nb_out, pic_pacer->allocated_max, pic_reorder_max);
+ pic_pacer->nb_fields_out, pic_pacer->allocated_fields_max / 2, pic_max);
#endif
if(b_growing)
{
- pic_pacer->allocated_max = pic_max;
- pic_pacer->allocated_next = pic_max;
+ pic_pacer->allocated_fields_max = fields_max;
+ pic_pacer->allocated_fields_next = fields_max;
vlc_cond_signal(&pic_pacer->wait);
}
else
{
- pic_pacer->allocated_next = pic_max;
+ pic_pacer->allocated_fields_next = fields_max;
}
vlc_mutex_unlock(&pic_pacer->lock);
=====================================
modules/codec/videotoolbox/pacer.h
=====================================
@@ -24,25 +24,25 @@ struct pic_pacer
{
vlc_mutex_t lock;
vlc_cond_t wait;
- uint8_t nb_out;
- uint8_t allocated_max;
- uint8_t allocated_next;
- uint8_t queued_for_decode;
+ uint8_t nb_fields_out;
+ uint8_t allocated_fields_max;
+ uint8_t allocated_fields_next;
+ uint8_t queued_fields_for_decode;
};
void pic_pacer_Clean(struct pic_pacer *);
void pic_pacer_Init(struct pic_pacer *);
-void pic_pacer_AccountAllocation(struct pic_pacer *);
+void pic_pacer_AccountAllocation(struct pic_pacer *, bool b_field);
-void pic_pacer_AccountScheduledDecode(struct pic_pacer *);
+void pic_pacer_AccountScheduledDecode(struct pic_pacer *, bool b_field);
-void pic_pacer_AccountFinishedDecode(struct pic_pacer *);
+void pic_pacer_AccountFinishedDecode(struct pic_pacer *, bool b_field);
-void pic_pacer_WaitAllocatableSlot(struct pic_pacer *);
+void pic_pacer_WaitAllocatableSlot(struct pic_pacer *, bool b_field);
-void pic_pacer_AccountDeallocation(struct pic_pacer *);
+void pic_pacer_AccountDeallocation(struct pic_pacer *, bool b_field);
void pic_pacer_UpdateMaxBuffering(struct pic_pacer *, uint8_t);
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/f5f2eb803d6b5cd90219c71dba2a78491ac10650...4464af834ec0617ff6d6f4b39438c8112ce8d273
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/f5f2eb803d6b5cd90219c71dba2a78491ac10650...4464af834ec0617ff6d6f4b39438c8112ce8d273
You're receiving this email because of your account on code.videolan.org.
VideoLAN code repository instance
More information about the vlc-commits
mailing list