[vlc-commits] [Git][videolan/vlc][master] 4 commits: demux: ps: change mapping range option for an enum

François Cartegnie (@fcartegnie) gitlab at videolan.org
Wed Oct 21 08:42:46 UTC 2026



François Cartegnie pushed to branch master at VideoLAN / VLC


Commits:
f42c50e0 by François Cartegnie at 2026-10-21T10:32:30+02:00
demux: ps: change mapping range option for an enum

- - - - -
2acf0e46 by François Cartegnie at 2026-10-21T10:32:30+02:00
demux: add PSMF

- - - - -
8fdac044 by François Cartegnie at 2026-10-21T10:32:30+02:00
demux: ps: delegate handling of PSMF es to es_out

only the PSMF demux can map ES properly and
handle ATRAC3P fragmentation

- - - - -
3c71cf34 by François Cartegnie at 2026-10-21T10:32:30+02:00
demux: ps: remove legacy PSMF code

- - - - -


7 changed files:

- modules/access/dvdnav.c
- modules/access/dvdread.c
- modules/demux/Makefile.am
- modules/demux/meson.build
- modules/demux/mpeg/ps.c
- modules/demux/mpeg/ps.h
- + modules/demux/psmf.c


Changes:

=====================================
modules/access/dvdnav.c
=====================================
@@ -1781,7 +1781,7 @@ static void ESNew( demux_t *p_demux, int i_id )
 
     if( tk->b_configured ) return;
 
-    if( ps_track_fill( tk, NULL, i_id, NULL, 0, true ) )
+    if( ps_track_fill( tk, NULL, i_id, NULL, 0, PS_MAP_OVERRIDE_MPEG2_ONLY ) )
     {
         msg_Warn( p_demux, "unknown codec for id=0x%x", i_id );
         return;


=====================================
modules/access/dvdread.c
=====================================
@@ -923,7 +923,7 @@ void DvdReadESNew( demux_t *p_demux, int i_id, int i_lang, int i_code_ext )
 
     if( tk->b_configured ) return;
 
-    if( ps_track_fill( tk, NULL, i_id, NULL, 0, true ) )
+    if( ps_track_fill( tk, NULL, i_id, NULL, 0, PS_MAP_OVERRIDE_MPEG2_ONLY ) )
     {
         msg_Warn( p_demux, "unknown codec for id=0x%x", i_id );
         return;


=====================================
modules/demux/Makefile.am
=====================================
@@ -63,6 +63,9 @@ demux_PLUGINS += libhx_plugin.la
 libps_plugin_la_SOURCES = demux/mpeg/ps.c demux/mpeg/ps.h demux/mpeg/pes.h
 demux_PLUGINS += libps_plugin.la
 
+libpsmf_plugin_la_SOURCES = demux/psmf.c
+demux_PLUGINS += libpsmf_plugin.la
+
 libmod_plugin_la_SOURCES = demux/mod.c
 libmod_plugin_la_CFLAGS = $(AM_CFLAGS) $(CFLAGS_mod)
 libmod_plugin_la_LDFLAGS = $(AM_LDFLAGS) $(demux_RPATH)


=====================================
modules/demux/meson.build
=====================================
@@ -117,6 +117,12 @@ vlc_modules += {
     'plugin_dir' : 'demux',
 }
 
+vlc_modules += {
+    'name' : 'psmf',
+    'sources' : files('psmf.c'),
+    'plugin_dir' : 'demux',
+}
+
 # libmodplug
 libmodplug_dep = dependency('libmodplug', version: ['>= 0.8.4', '!= 0.8.8'],
     required: get_option('libmodplug'))


=====================================
modules/demux/mpeg/ps.c
=====================================
@@ -128,7 +128,7 @@ static void CreateOrUpdateES( demux_t*p_demux )
         if( tk->es )
             es_out_Del( p_demux->out, tk->es );
 
-        if( tk->fmt.i_cat != UNKNOWN_ES )
+        if( tk->fmt.i_cat != UNKNOWN_ES || p_sys->format == PSMF_PS )
             tk->es = es_out_Add( p_demux->out, &tk->fmt );
         tk->b_updated = false;
     }
@@ -155,19 +155,8 @@ static int Open( vlc_object_t *p_this )
     if( i_peek < 16 )
         return VLC_EGENERIC;
 
-    if( !memcmp( p_peek, "PSMF", 4 ) &&
-        (GetDWBE( &p_peek[4] ) & 0x30303030) == 0x30303030 )
-    {
-        i_peek = vlc_stream_Peek( p_demux->s, &p_peek, 100 );
-        if( i_peek < 100 )
-            return VLC_EGENERIC;
-        i_skip = i_offset = GetWBE( &p_peek[10] );
+    if(p_demux->psz_url && !strcmp(p_demux->psz_url, "psmf://")) // trigger PSMF override mode
         format = PSMF_PS;
-        msg_Info( p_demux, "Detected PSMF-PS header");
-        i_mux_rate = GetDWBE( &p_peek[96] );
-        if( GetDWBE( &p_peek[86] ) > 0 )
-            i_length = vlc_tick_from_samples( GetDWBE( &p_peek[92] ), GetDWBE( &p_peek[86] ));
-    }
     else if( !memcmp( p_peek, "IMKH", 4 ) )
     {
         msg_Info( p_demux, "Detected Hikvision PVA header");
@@ -545,8 +534,10 @@ static int Demux( demux_t *p_demux )
 
             if( !tk->b_configured )
             {
+                enum ps_mapping_override_e opt = ( p_sys->format == PSMF_PS ) ? PS_MAP_OVERRIDE_PSMF
+                                                                              : PS_MAP_OVERRIDE_NONE;
                 if( !ps_track_fill( tk, &p_sys->psm, i_id,
-                                    p_pkt->p_buffer, p_pkt->i_buffer, false ) )
+                                    p_pkt->p_buffer, p_pkt->i_buffer, opt ) )
                 {
                     if( p_sys->format == IMKH_PS )
                     {
@@ -557,23 +548,6 @@ static int Demux( demux_t *p_demux )
                             tk->fmt.audio.i_rate = 8000;
                         }
                     }
-                    else
-                    /* No PSM and no probing yet */
-                    if( p_sys->format == PSMF_PS )
-                    {
-                        if( tk->fmt.i_cat == VIDEO_ES )
-                            tk->fmt.i_codec = VLC_CODEC_H264;
-#if 0
-                        if( i_stream_id == PS_STREAM_ID_PRIVATE_STREAM1 )
-                        {
-                            es_format_Change( &tk->fmt, AUDIO_ES, VLC_CODEC_ATRAC3P );
-                            tk->fmt.audio.i_blockalign = 376;
-                            tk->fmt.audio.i_channels = 2;
-                            tk->fmt.audio.i_rate = 44100;
-                        }
-#endif
-                    }
-
                     b_new = true;
                     tk->b_configured = true;
                     tk->b_updated = true;
@@ -674,13 +648,7 @@ static int Demux( demux_t *p_demux )
                     p_pkt->i_flags = tk->i_next_block_flags;
                     tk->i_next_block_flags = 0;
                 }
-#if 0
-                if( tk->fmt.i_codec == VLC_CODEC_ATRAC3P )
-                {
-                    p_pkt->p_buffer += 14;
-                    p_pkt->i_buffer -= 14;
-                }
-#endif
+
                 es_out_Send( p_demux->out, tk->es, p_pkt );
             }
             else


=====================================
modules/demux/mpeg/ps.h
=====================================
@@ -141,14 +141,29 @@ static inline bool ps_is_EAC3( const uint8_t *p_data, size_t i_data )
 }
 
 /* From id fill i_skip and es_format_t */
+enum ps_mapping_override_e
+{
+    PS_MAP_OVERRIDE_NONE,
+    PS_MAP_OVERRIDE_MPEG2_ONLY,
+    PS_MAP_OVERRIDE_PSMF,
+};
+
 static inline int ps_track_fill( ps_track_t *tk, ps_psm_t *p_psm,
                                  int i_id,
                                  const uint8_t *p_pkt, size_t i_pkt,
-                                 bool b_mpeg2only )
+                                 enum ps_mapping_override_e map_opt )
 {
+    const bool b_mpeg2only = map_opt == PS_MAP_OVERRIDE_MPEG2_ONLY;
     tk->i_skip = 0;
     tk->i_id = i_id;
 
+    if( map_opt == PS_MAP_OVERRIDE_PSMF )
+    {
+        es_format_Change( &tk->fmt, UNKNOWN_ES, 0 );
+        tk->fmt.i_id = i_id;
+        return VLC_SUCCESS;
+    }
+
     if( ( i_id&0xff00 ) == PS_PACKET_ID_MASK_VOB ) /* 0xBD00 -> 0xBDFF, VOB Private Stream 1 */
     {
         if( ( i_id&0xf8 ) == 0x88 || /* 0x88 -> 0x8f - Can be DTS-HD primary audio in evob */
@@ -518,7 +533,7 @@ static inline int ps_pkt_parse_system( const uint8_t *p_pkt, size_t i_pkt,
 
         unsigned i_tk = ps_id_to_tk( i_id );
         if( !tk[i_tk].b_configured )
-            ps_track_fill( &tk[i_tk], p_psm, i_id, NULL, 0, false );
+            ps_track_fill( &tk[i_tk], p_psm, i_id, NULL, 0, PS_MAP_OVERRIDE_NONE );
     }
     return VLC_SUCCESS;
 }
@@ -737,7 +752,7 @@ static inline int ps_psm_fill( ps_psm_t *p_psm,
         es_format_Init( &tk_tmp.fmt, UNKNOWN_ES, 0 );
 
         if( ps_track_fill( &tk_tmp, p_psm, tk[i].i_id,
-                           p_buffer, i_pkt, false ) != VLC_SUCCESS )
+                           p_buffer, i_pkt, PS_MAP_OVERRIDE_NONE ) != VLC_SUCCESS )
             continue;
 
         if( tk_tmp.fmt.i_codec == tk[i].fmt.i_codec )


=====================================
modules/demux/psmf.c
=====================================
@@ -0,0 +1,1306 @@
+/*****************************************************************************
+ * psmf.c: Sorry PayStation Movie demux
+ *****************************************************************************
+ * Copyright (C) 2026 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.
+ *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_demux.h>
+#include <vlc_plugin.h>
+#include <vlc_stream.h>
+#include <vlc_charset.h>
+#include <vlc_arrays.h>
+
+#include <limits.h>
+#include <stdckdint.h>
+
+#define PSMF_MIN_HEADER_SIZE          0x82
+#define PSMF_STREAM_DESC_SIZE         16
+#define PSMF_ENTRYPOINTS_ENTRY_SIZE   10
+#define PSMF_MARK_ENTRY_SIZE          40
+#define PSMF_MARK_TYPE_CHAPTER        0x05
+#define PSMF_MARK_TYPE_EVENT          0x10
+#define PSMF_PS_PACKET_SIZE           2048
+#define PSMF_MAX_STREAMS              128
+#define PSMF_MAX_HEADER_SIZE          (16 * 1024 * 1024)
+
+typedef struct
+{
+    unsigned rate;
+    unsigned channel_id;
+    unsigned frame_size;
+    unsigned samples_per_frame;
+    unsigned bitrate;
+} atrac3p_header_t;
+
+static bool atrac3p_parse_header(const uint8_t *p, size_t len, atrac3p_header_t *h)
+{
+    static const unsigned rates[8] = {32000, 44100, 48000, 88200, 96000, 0, 0, 0};
+    if (len < 4 || GetWBE(p) != 0x0fd0)
+        return false;
+
+    const uint16_t cfg = GetWBE(p + 2);
+    const unsigned rate_idx = (cfg >> 13) & 7;
+
+    h->rate = rates[rate_idx];
+    h->channel_id = (cfg >> 10) & 7;
+    h->frame_size = ((cfg & 0x3ff) << 3) + 8;
+    h->samples_per_frame = 2048;
+
+    if (!h->rate || !h->channel_id || !h->frame_size)
+        return false;
+
+    h->bitrate = (uint64_t)h->frame_size * 8 * h->rate / h->samples_per_frame;
+
+    return true;
+}
+
+typedef struct
+{
+    uint32_t pts;
+    uint32_t packet;
+    uint8_t index;
+    uint8_t pic_offset;
+} psmf_ep_t;
+
+typedef struct
+{
+    stream_t *source;
+    uint64_t base;
+    uint64_t size;
+} psmf_stream_sys_t;
+
+typedef struct
+{
+    es_out_t *es_out;
+    demux_t *psdemux;
+
+    uint32_t data_offset;
+    uint32_t data_size;
+
+    uint64_t pts_start;
+    uint64_t pts_end;
+    vlc_tick_t duration;
+
+    psmf_ep_t *ep;
+    size_t ep_count;
+
+    input_title_t *title;
+    int current_seekpoint;
+    unsigned updates;
+
+} demux_sys_t;
+
+/*
+    Fake EsOut to intercept and handle ES specific changes
+*/
+typedef struct psmf_atrac_es_t
+{
+    es_out_id_t *id;
+    es_format_t fmt;
+
+    atrac3p_header_t header;
+    bool configured;
+
+    uint8_t frame_header[8];
+    size_t frame_header_size;
+
+    block_t *frame;
+
+    struct psmf_atrac_es_t *next;
+} psmf_atrac_es_t;
+
+#define PSMF_PES_PRIVATE_STREAM_1 0xBD
+#define PSMF_PS_PRIVATE_ID_BASE   0xBD00 // depends on the PS demux id
+
+typedef struct
+{
+    uint16_t ps_id;
+    uint8_t data[PSMF_STREAM_DESC_SIZE];
+} psmf_stream_descriptor_t;
+
+typedef struct
+{
+    es_out_t *p_dst_out;
+    es_out_t es_out;
+
+    psmf_stream_descriptor_t *descriptors;
+    size_t descriptor_count;
+
+    psmf_atrac_es_t *atrac;
+} psmf_esout_priv_t;
+
+static uint16_t psmf_DescriptorId(const uint8_t *desc)
+{
+    const uint8_t stream_id = desc[0];
+
+    if (stream_id == PSMF_PES_PRIVATE_STREAM_1)
+        return PSMF_PS_PRIVATE_ID_BASE | desc[1];
+
+    return stream_id;
+}
+
+static const psmf_stream_descriptor_t *
+psmf_FindDescriptor(const psmf_esout_priv_t *priv, int id)
+{
+    if (id < 0 || id > UINT16_MAX)
+        return NULL;
+
+    for (size_t i = 0; i < priv->descriptor_count; ++i)
+    {
+        if (priv->descriptors[i].ps_id == (uint16_t)id)
+            return &priv->descriptors[i];
+    }
+
+    return NULL;
+}
+
+static psmf_atrac_es_t * psmf_atrac_Find(psmf_esout_priv_t *priv, es_out_id_t *id)
+{
+    for (psmf_atrac_es_t *state = priv->atrac; state != NULL; state = state->next)
+        if (state->id == id)
+            return state;
+    return NULL;
+}
+
+static void psmf_atrac_Reset(psmf_atrac_es_t *state)
+{
+    if (state->frame != NULL)
+    {
+        block_Release(state->frame);
+        state->frame = NULL;
+    }
+
+    state->frame_header_size = 0;
+}
+
+static void psmf_atrac_DeleteState(psmf_atrac_es_t *state)
+{
+    psmf_atrac_Reset(state);
+    es_format_Clean(&state->fmt);
+    free(state);
+}
+
+static const struct
+{
+    uint8_t channels;
+    uint16_t physical_channels;
+} atrac3p_channel_layouts[8] = {
+    [0] = { 0 }, /* invalid */
+    [1] = { 1, AOUT_CHAN_CENTER },
+    [2] = { 2, AOUT_CHANS_STEREO },
+    [3] = { 3, AOUT_CHANS_3_0 },
+    [4] = { 4, AOUT_CHANS_4_CENTER_REAR },
+    [5] = { 6, AOUT_CHANS_5_1 },
+    [6] = { 7, AOUT_CHANS_5_1 | AOUT_CHAN_REARCENTER },
+    [7] = { 8, AOUT_CHANS_7_1 },
+};
+
+static int psmf_atrac_UpdateFormat(psmf_esout_priv_t *priv, psmf_atrac_es_t *state,
+                                   const atrac3p_header_t *header)
+{
+    if (header->channel_id == 0 || header->channel_id >= ARRAY_SIZE(atrac3p_channel_layouts))
+        return VLC_EGENERIC;
+
+    if (state->configured &&
+        state->header.rate == header->rate &&
+        state->header.channel_id == header->channel_id &&
+        state->header.frame_size == header->frame_size)
+        return VLC_SUCCESS;
+
+    state->fmt.i_bitrate = header->bitrate;
+    state->fmt.audio.i_rate = header->rate;
+    state->fmt.audio.i_channels =
+            atrac3p_channel_layouts[header->channel_id & 7].channels;
+    state->fmt.audio.i_physical_channels =
+            atrac3p_channel_layouts[header->channel_id & 7].physical_channels;
+    state->fmt.audio.i_blockalign = header->frame_size;
+    state->fmt.audio.i_bytes_per_frame = header->frame_size;
+
+    int ret = es_out_Control(priv->p_dst_out, ES_OUT_SET_ES_FMT, state->id, &state->fmt);
+    if (ret != VLC_SUCCESS)
+        return ret;
+
+    state->header = *header;
+    state->configured = true;
+    return VLC_SUCCESS;
+}
+
+static int psmf_atrac_StartFrame(psmf_esout_priv_t *priv, psmf_atrac_es_t *state,
+                                 const uint8_t header_data[8], const block_t *source,
+                                 bool *ts_used)
+{
+    atrac3p_header_t header;
+    if (!atrac3p_parse_header(header_data, 8, &header))
+        return VLC_EGENERIC;
+
+    int ret = psmf_atrac_UpdateFormat(priv, state, &header);
+    if (ret != VLC_SUCCESS)
+        return ret;
+
+    block_t *frame = block_Alloc(header.frame_size);
+    if (unlikely(frame == NULL))
+        return VLC_ENOMEM;
+
+    block_CopyProperties(frame, source);
+
+    if (*ts_used)
+    {
+        frame->i_pts = VLC_TICK_INVALID;
+        frame->i_dts = VLC_TICK_INVALID;
+        frame->i_length = 0;
+        frame->i_flags &= ~BLOCK_FLAG_DISCONTINUITY;
+    }
+    else
+        *ts_used = true;
+
+    frame->i_buffer = 0;
+    frame->i_nb_samples = header.samples_per_frame;
+    state->frame = frame;
+
+    return VLC_SUCCESS;
+}
+
+static int psmf_atrac_Append(psmf_esout_priv_t *priv, psmf_atrac_es_t *state,
+                             const uint8_t *data, size_t amount)
+{
+    block_t *frame = state->frame;
+    if (frame == NULL || !state->configured)
+        return VLC_EGENERIC;
+
+    const size_t missing = state->header.frame_size - frame->i_buffer;
+    if (amount > missing)
+        return VLC_EGENERIC;
+
+    memcpy(frame->p_buffer + frame->i_buffer, data, amount);
+    frame->i_buffer += amount;
+
+    if (frame->i_buffer != state->header.frame_size)
+        return VLC_SUCCESS;
+
+    state->frame = NULL;
+    return es_out_Send(priv->p_dst_out, state->id, frame);
+}
+
+static int psmf_atrac_ConsumeHeader(psmf_esout_priv_t *priv, psmf_atrac_es_t *state,
+                                    const uint8_t **data, size_t *size,
+                                    const block_t *source, bool *ts_used)
+{
+    const size_t needed = sizeof(state->frame_header) - state->frame_header_size;
+    const size_t amount = *size < needed ? *size : needed;
+
+    memcpy(state->frame_header + state->frame_header_size, *data, amount);
+    state->frame_header_size += amount;
+    *data += amount;
+    *size -= amount;
+
+    if (state->frame_header_size < sizeof(state->frame_header))
+        return VLC_SUCCESS;
+
+    int ret = psmf_atrac_StartFrame(priv, state, state->frame_header,
+                                    source, ts_used);
+    state->frame_header_size = 0;
+    return ret;
+}
+
+static int psmf_atrac_ConsumeContinuation(psmf_esout_priv_t *priv, psmf_atrac_es_t *state,
+                                          const uint8_t **data, size_t *size, uint16_t continuation)
+{
+    if (continuation > *size)
+    {
+        psmf_atrac_Reset(state);
+        return VLC_EGENERIC;
+    }
+
+    if (state->frame == NULL)
+    {
+        // can't continue without existing data. skip to next packet
+        *data += continuation;
+        *size -= continuation;
+        return VLC_SUCCESS;
+    }
+
+    const size_t missing =
+        state->header.frame_size - state->frame->i_buffer;
+
+    if (continuation != missing)
+    {
+        // implicit vs sum accounting disagreed. corruption ?
+        block_Release(state->frame);
+        state->frame = NULL;
+
+        *data += continuation;
+        *size -= continuation;
+        return VLC_SUCCESS;
+    }
+
+    int ret = psmf_atrac_Append(priv, state, *data, continuation);
+    *data += continuation;
+    *size -= continuation;
+    return ret;
+}
+
+static int psmf_es_out_callback_Send(es_out_t *out, es_out_id_t *id, block_t *block)
+{
+    psmf_esout_priv_t *priv = container_of(out, psmf_esout_priv_t, es_out);
+    psmf_atrac_es_t *state = psmf_atrac_Find(priv, id);
+
+    if (state == NULL)
+        return es_out_Send(priv->p_dst_out, id, block);
+
+    if (block->i_flags & BLOCK_FLAG_DISCONTINUITY)
+        psmf_atrac_Reset(state);
+
+    if (block->i_buffer < 2)
+    {
+        block_Release(block);
+        return VLC_SUCCESS;
+    }
+
+    const uint8_t *data = block->p_buffer;
+    size_t size = block->i_buffer;
+    bool ts_used = false;
+    int ret = VLC_SUCCESS;
+
+    /* stream_id data[0]
+     * reserved  data[1] */
+    data += 2;
+    size -= 2;
+
+    /* Tricky fragmentation header: Either
+     * 0x0FD0 ATRAC frame sync word on new frame, or,
+     * uint16_t continuation_size if continuing */
+    uint16_t continuation = 0;
+    if (size >= 2 && GetWBE(data) != 0x0FD0)
+    {
+        continuation = GetWBE(data);
+        data += 2;
+        size -= 2;
+    }
+
+    if (state->frame_header_size != 0)
+    {
+        // handle incomplete header
+        ret = psmf_atrac_ConsumeHeader(priv, state, &data, &size, block, &ts_used);
+        if (ret != VLC_SUCCESS || state->frame_header_size != 0)
+            goto out;
+    }
+    else if (continuation != 0)
+    {
+        ret = psmf_atrac_ConsumeContinuation(priv, state, &data, &size, continuation);
+        if (ret != VLC_SUCCESS)
+            goto out;
+    }
+    else if (state->frame != NULL)
+    {
+        // discontinuity/loss ? discard incomplete data
+        block_Release(state->frame);
+        state->frame = NULL;
+    }
+
+    while (size > 0)
+    {
+        if (state->frame == NULL)
+        {
+            ret = psmf_atrac_ConsumeHeader(priv, state, &data, &size, block, &ts_used);
+            if (ret != VLC_SUCCESS)
+                goto out;
+
+            if (state->frame == NULL)
+                break;
+        }
+
+        const size_t missing = state->header.frame_size - state->frame->i_buffer;
+        const size_t amount = size < missing ? size : missing;
+
+        ret = psmf_atrac_Append(priv, state, data, amount);
+        data += amount;
+        size -= amount;
+
+        if (ret != VLC_SUCCESS)
+            goto out;
+    }
+
+out:
+    block_Release(block);
+
+    if (ret != VLC_SUCCESS)
+        psmf_atrac_Reset(state);
+
+    return ret;
+}
+
+static es_out_id_t * psmf_es_out_callback_Add(es_out_t *out, input_source_t *in,
+                                              const es_format_t *source_fmt)
+{
+    VLC_UNUSED(in);
+
+    psmf_esout_priv_t *priv = container_of(out, psmf_esout_priv_t, es_out);
+
+    const psmf_stream_descriptor_t *descriptor = psmf_FindDescriptor(priv, source_fmt->i_id);
+    if (descriptor == NULL)
+        return NULL;
+
+    const uint8_t stream_id = descriptor->data[0];
+    const uint8_t private_id = descriptor->data[1];
+
+    es_out_id_t *id = NULL;
+
+    if ((stream_id & 0xf0) == 0xe0)
+    {
+        es_format_t fmt;
+        es_format_Init(&fmt, VIDEO_ES, VLC_CODEC_H264);
+        fmt.b_packetized = false;
+        id = es_out_Add(priv->p_dst_out, &fmt);
+        es_format_Clean(&fmt);
+    }
+    else if (stream_id == PSMF_PES_PRIVATE_STREAM_1)
+    {
+        if ((private_id & 0xf0) == 0x00)
+        {
+            psmf_atrac_es_t *state = calloc(1, sizeof(*state));
+            if (unlikely(state == NULL))
+                return NULL;
+            es_format_Init(&state->fmt, AUDIO_ES, VLC_CODEC_ATRAC3P);
+            state->fmt.b_packetized = true;
+            /* We set common values for decoder initialization
+               But *it will be updated* after reading the in-band config */
+            state->fmt.audio.i_blockalign = 376;
+            state->fmt.audio.i_bytes_per_frame = 376;
+            state->fmt.audio.i_channels = 2;
+            state->fmt.audio.i_physical_channels = AOUT_CHANS_STEREO;
+            state->fmt.audio.i_rate = 44100;
+            state->fmt.audio.i_frame_length = 2048;
+            id = es_out_Add(priv->p_dst_out, &state->fmt);
+            if (id == NULL)
+                psmf_atrac_DeleteState(state);
+            else
+            {
+                state->id = id;
+                state->next = priv->atrac;
+                priv->atrac = state;
+            }
+        }
+        else if ((private_id & 0xf0) == 0x10)
+        {
+            const unsigned channels = descriptor->data[14];
+            if (channels != 1 && channels != 2)
+                return NULL;
+
+            if (descriptor->data[15] != 2) // 44.1 kHz
+                return NULL;
+
+            es_format_t fmt;
+            es_format_Init(&fmt, AUDIO_ES, VLC_CODEC_S16L);
+            fmt.b_packetized = true;
+            fmt.i_bitrate = 44100 * channels * 16;
+            fmt.audio.i_rate = 44100;
+            fmt.audio.i_channels = channels;
+            fmt.audio.i_physical_channels = channels == 1 ? AOUT_CHAN_CENTER
+                                                          : AOUT_CHANS_STEREO;
+            fmt.audio.i_bitspersample = 16;
+            fmt.audio.i_blockalign = channels * 2;
+            fmt.audio.i_bytes_per_frame = channels * 2;
+            fmt.audio.i_frame_length = 1;
+            id = es_out_Add(priv->p_dst_out, &fmt);
+            es_format_Clean(&fmt);
+        }
+    }
+
+    return id;
+}
+
+static void psmf_es_out_callback_Del(es_out_t *out, es_out_id_t *id)
+{
+    psmf_esout_priv_t *priv = container_of(out, psmf_esout_priv_t, es_out);
+    psmf_atrac_es_t **link = &priv->atrac;
+
+    while (*link != NULL)
+    {
+        psmf_atrac_es_t *state = *link;
+
+        if (state->id == id)
+        {
+            *link = state->next;
+            psmf_atrac_DeleteState(state);
+            break;
+        }
+
+        link = &state->next;
+    }
+
+    es_out_Del(priv->p_dst_out, id);
+}
+
+static int psmf_es_out_callback_Control(es_out_t *out, input_source_t *in,
+                                        int query, va_list args)
+{
+    VLC_UNUSED(in);
+
+    psmf_esout_priv_t *priv = container_of(out, psmf_esout_priv_t, es_out);
+    return es_out_vaControl(priv->p_dst_out, query, args);
+}
+
+static void psmf_es_out_callback_Destroy(es_out_t *out)
+{
+    psmf_esout_priv_t *priv = container_of(out, psmf_esout_priv_t, es_out);
+
+    while (priv->atrac != NULL)
+    {
+        psmf_atrac_es_t *state = priv->atrac;
+        priv->atrac = state->next;
+        psmf_atrac_DeleteState(state);
+    }
+
+    free(priv->descriptors);
+    free(priv);
+}
+
+static const struct es_out_callbacks psmf_es_out_Callbacks = {
+    .add = psmf_es_out_callback_Add,
+    .send = psmf_es_out_callback_Send,
+    .del = psmf_es_out_callback_Del,
+    .control = psmf_es_out_callback_Control,
+    .destroy = psmf_es_out_callback_Destroy,
+};
+
+static es_out_t * psmf_es_out_New(es_out_t *dst, const uint8_t *header,
+                                  size_t header_size, uint16_t stream_count)
+{
+    if (stream_count > PSMF_MAX_STREAMS)
+        return NULL;
+
+    const size_t descriptors_size = (size_t)stream_count * PSMF_STREAM_DESC_SIZE;
+
+    if (PSMF_MIN_HEADER_SIZE > header_size ||
+        descriptors_size > header_size - PSMF_MIN_HEADER_SIZE)
+        return NULL;
+
+    psmf_esout_priv_t *priv = calloc(1, sizeof(*priv));
+    if (unlikely(priv == NULL))
+        return NULL;
+
+    if (stream_count > 0)
+    {
+        priv->descriptors = vlc_alloc(stream_count, sizeof(*priv->descriptors));
+        if (unlikely(priv->descriptors == NULL))
+        {
+            free(priv);
+            return NULL;
+        }
+
+        for (uint16_t i = 0; i < stream_count; i++)
+        {
+            const uint8_t *desc = header + PSMF_MIN_HEADER_SIZE +
+                                  (size_t)i * PSMF_STREAM_DESC_SIZE;
+
+            memcpy(priv->descriptors[i].data, desc, PSMF_STREAM_DESC_SIZE);
+            priv->descriptors[i].ps_id = psmf_DescriptorId(desc);
+        }
+    }
+
+    priv->descriptor_count = stream_count;
+    priv->p_dst_out = dst;
+    priv->es_out.cbs = &psmf_es_out_Callbacks;
+
+    return &priv->es_out;
+}
+/*
+    !Fake EsOut
+*/
+
+static uint64_t GetQW48BE(const uint8_t *p)
+{
+    return ((uint64_t)p[0] << 40) |
+           ((uint64_t)p[1] << 32) |
+           ((uint64_t)p[2] << 24) |
+           ((uint64_t)p[3] << 16) |
+           ((uint64_t)p[4] <<  8) |
+           ((uint64_t)p[5] <<  0);
+}
+
+static bool psmf_stream_CanSeek(stream_t *s)
+{
+    psmf_stream_sys_t *sys = s->p_sys;
+    return vlc_stream_CanSeek(sys->source);
+}
+
+static bool psmf_stream_CanPause(stream_t *s)
+{
+    psmf_stream_sys_t *sys = s->p_sys;
+    return vlc_stream_CanPause(sys->source);
+}
+
+static bool psmf_stream_CanControlPace(stream_t *s)
+{
+    psmf_stream_sys_t *sys = s->p_sys;
+    return vlc_stream_CanPace(sys->source);
+}
+
+static bool psmf_stream_CanFastSeek(stream_t *s)
+{
+    psmf_stream_sys_t *sys = s->p_sys;
+    return vlc_stream_CanFastSeek(sys->source);
+}
+
+static ssize_t psmf_stream_Read(stream_t *s, void *buf, size_t len)
+{
+    psmf_stream_sys_t *sys = s->p_sys;
+    const uint64_t pos = vlc_stream_Tell(sys->source) - sys->base;
+
+    if (pos >= sys->size)
+        return 0;
+
+    const uint64_t left = sys->size - pos;
+    if (left < len)
+        len = (size_t)left;
+
+    return vlc_stream_ReadPartial(sys->source, buf, len);
+}
+
+static int psmf_stream_Seek(stream_t *s, uint64_t pos)
+{
+    psmf_stream_sys_t *sys = s->p_sys;
+
+    if (pos > sys->size || pos > UINT64_MAX - sys->base)
+        return VLC_EGENERIC;
+
+    return vlc_stream_Seek(sys->source, sys->base + pos);
+}
+
+static int psmf_stream_GetSize(stream_t *s, uint64_t *size)
+{
+    psmf_stream_sys_t *sys = s->p_sys;
+    *size = sys->size;
+    return VLC_SUCCESS;
+}
+
+static vlc_tick_t psmf_stream_GetPtsDelay(stream_t *s)
+{
+    psmf_stream_sys_t *sys = s->p_sys;
+    return vlc_stream_GetPtsDelay(sys->source);
+}
+
+static int psmf_stream_SetPauseState(stream_t *s, bool paused)
+{
+    psmf_stream_sys_t *sys = s->p_sys;
+    return vlc_stream_Control(sys->source, STREAM_SET_PAUSE_STATE, paused);
+}
+
+static void psmf_stream_Destroy(stream_t *s)
+{
+    free(s->p_sys);
+}
+
+static const struct vlc_stream_operations stream_ops =
+{
+    .can_seek = psmf_stream_CanSeek,
+    .can_pause = psmf_stream_CanPause,
+    .can_control_pace = psmf_stream_CanControlPace,
+    .set_pause_state = psmf_stream_SetPauseState,
+    .stream =
+    {
+        .can_fastseek = psmf_stream_CanFastSeek,
+        .read = psmf_stream_Read,
+        .seek = psmf_stream_Seek,
+        .get_size = psmf_stream_GetSize,
+        .get_pts_delay = psmf_stream_GetPtsDelay,
+    },
+};
+
+static stream_t *psmf_stream_New(demux_t *demux, uint64_t base, uint64_t size)
+{
+    stream_t *s = vlc_stream_CommonNew(VLC_OBJECT(demux), psmf_stream_Destroy);
+    if (s == NULL)
+        return NULL;
+
+    psmf_stream_sys_t *sys = malloc(sizeof(*sys));
+    if (sys == NULL)
+    {
+        vlc_stream_Delete(s);
+        return NULL;
+    }
+
+    sys->source = demux->s;
+    sys->base = base;
+    sys->size = size;
+
+    s->p_sys = sys;
+    s->ops = &stream_ops;
+
+    if (vlc_stream_Seek(demux->s, base) != VLC_SUCCESS)
+    {
+        vlc_stream_Delete(s);
+        return NULL;
+    }
+
+    return s;
+}
+
+static int ParseEntryPointMap(demux_t *demux, demux_sys_t *sys,
+                              const uint8_t *header, size_t header_size)
+{
+    const uint16_t stream_count = GetWBE(header + 0x80);
+    if (stream_count == 0 || stream_count > PSMF_MAX_STREAMS)
+        return VLC_SUCCESS;
+
+    const size_t descriptors_end = PSMF_MIN_HEADER_SIZE +
+                                   (size_t)stream_count * PSMF_STREAM_DESC_SIZE;
+
+    if (descriptors_end > header_size)
+        return VLC_EGENERIC;
+
+    for(uint16_t i = 0; i < stream_count; ++i)
+    {
+        const uint8_t *desc = header + PSMF_MIN_HEADER_SIZE +
+                              (size_t)i * PSMF_STREAM_DESC_SIZE;
+        const uint8_t stream_id = desc[0];
+
+        if ((stream_id & 0xf0) != 0xe0)
+            continue;
+
+        const uint32_t ep_offset = GetDWBE(desc + 4);
+        const uint32_t ep_count = GetDWBE(desc + 8);
+        if (ep_offset == 0 || ep_count == 0)
+            continue;
+
+        size_t ep_bytes;
+        if(ckd_mul(&ep_bytes, ep_count, PSMF_ENTRYPOINTS_ENTRY_SIZE))
+            continue;
+
+        if (ep_offset > header_size || ep_bytes > header_size - ep_offset)
+            continue;
+
+        psmf_ep_t *ep = vlc_alloc(ep_count, sizeof(*ep));
+        if (ep == NULL)
+            return VLC_ENOMEM;
+
+        size_t valid = 0;
+        uint32_t last_pts = 0;
+        uint32_t last_packet = 0;
+
+        for(uint32_t j = 0; j < ep_count; ++j)
+        {
+            const uint8_t *entry = header + ep_offset + (size_t)j * PSMF_ENTRYPOINTS_ENTRY_SIZE;
+            const uint32_t pts = GetDWBE(entry + 2);
+            const uint32_t packet = GetDWBE(entry + 6);
+
+            if ((uint64_t)packet * PSMF_PS_PACKET_SIZE >= sys->data_size)
+                continue;
+
+            if (valid > 0 && (pts < last_pts || packet < last_packet))
+                break;
+
+            ep[valid].index = entry[0];
+            ep[valid].pic_offset = entry[1];
+            ep[valid].pts = pts;
+            ep[valid].packet = packet;
+            last_pts = pts;
+            last_packet = packet;
+            valid++;
+        }
+
+        if (valid == 0)
+        {
+            free(ep);
+            continue;
+        }
+
+        sys->ep = ep;
+        sys->ep_count = valid;
+
+        msg_Dbg(demux, "using PSMF EntryPoint map: %zu entries, offset 0x%"PRIx32, valid, ep_offset);
+        return VLC_SUCCESS;
+    }
+
+    return VLC_SUCCESS;
+}
+
+static int ParseMarks(demux_t *demux, demux_sys_t *sys,
+                       const uint8_t *header, size_t header_size)
+{
+    const uint32_t mark_offset = GetDWBE(header + 0x10);
+    const uint32_t mark_size = GetDWBE(header + 0x14);
+
+    if (mark_offset == 0 || mark_size == 0)
+        return VLC_SUCCESS;
+
+    if (mark_size < 6 ||
+        mark_offset > header_size ||
+        mark_size > header_size - mark_offset)
+        return VLC_SUCCESS; // ignore
+
+    const uint8_t *section = header + mark_offset;
+    const uint32_t section_size = GetDWBE(section);
+    const uint16_t mark_count = GetWBE(section + 4);
+
+    if (section_size < 6 ||
+        section_size > mark_size ||
+        section_size > header_size - mark_offset ||
+        mark_count > (section_size - 6) / PSMF_MARK_ENTRY_SIZE)
+        return VLC_SUCCESS;
+
+    input_title_t *title = vlc_input_title_New();
+    if (title == NULL)
+        return VLC_ENOMEM;
+
+    title->i_length = sys->duration;
+
+    for(uint16_t i = 0; i < mark_count; ++i)
+    {
+        const uint8_t *entry = section + 6 + (size_t)i * PSMF_MARK_ENTRY_SIZE;
+        const uint8_t type = entry[0];
+        const uint8_t name_length = entry[1];
+
+        if (type != PSMF_MARK_TYPE_CHAPTER && type != PSMF_MARK_TYPE_EVENT)
+            continue;
+
+        const uint64_t pts = GetQW48BE(entry + 4);
+        if (pts < sys->pts_start)
+            continue;
+
+        const uint64_t delta = pts - sys->pts_start;
+        if (delta > INT64_MAX)
+            continue;
+
+        const vlc_tick_t time = vlc_tick_from_samples(delta, 90000);
+
+        if (sys->duration != VLC_TICK_INVALID && time > sys->duration)
+            continue;
+
+        seekpoint_t *seekpoint = vlc_seekpoint_New();
+        if (seekpoint == NULL)
+        {
+            vlc_input_title_Delete(title);
+            return VLC_ENOMEM;
+        }
+
+        seekpoint->i_time_offset = time;
+
+        const size_t len = __MIN(name_length, 24);
+        if (len > 0)
+        {
+            seekpoint->psz_name = strndup((const char *)entry + 16, len);
+            if (seekpoint->psz_name == NULL)
+            {
+                vlc_seekpoint_Delete(seekpoint);
+                vlc_input_title_Delete(title);
+                return VLC_ENOMEM;
+            }
+            EnsureUTF8(seekpoint->psz_name);
+        }
+        else if (type == PSMF_MARK_TYPE_EVENT)
+        {
+            if (asprintf(&seekpoint->psz_name, "Event %u", i + 1) < 0)
+                seekpoint->psz_name = NULL;
+        }
+
+        TAB_APPEND(title->i_seekpoint, title->seekpoint, seekpoint);
+
+        msg_Dbg(demux, "PSMF mark %u: type 0x%02x at %"PRId64" ms%s%s",
+                 i, type, MS_FROM_VLC_TICK(time),
+                 seekpoint->psz_name != NULL ? ", " : "",
+                 seekpoint->psz_name != NULL ? seekpoint->psz_name : "");
+    }
+
+    if (title->i_seekpoint == 0)
+    {
+        vlc_input_title_Delete(title);
+        return VLC_SUCCESS;
+    }
+
+    sys->title = title;
+    sys->current_seekpoint = 0;
+    return VLC_SUCCESS;
+}
+
+static int FindEntryPoint(const demux_sys_t *sys, uint32_t pts)
+{
+    if (sys->ep_count == 0)
+        return -1;
+
+    size_t lo = 0;
+    size_t hi = sys->ep_count;
+
+    while(lo < hi)
+    {
+        const size_t mid = lo + (hi - lo) / 2;
+        if (sys->ep[mid].pts <= pts)
+            lo = mid + 1;
+        else
+            hi = mid;
+    }
+
+    return lo == 0 ? -1 : (int)(lo - 1);
+}
+
+static void UpdateCurrentSeekpoint(demux_sys_t *sys, vlc_tick_t time)
+{
+    if (sys->title == NULL || sys->title->i_seekpoint == 0)
+        return;
+
+    int current = 0;
+    for(int i = 1; i < sys->title->i_seekpoint; ++i)
+    {
+        if (sys->title->seekpoint[i]->i_time_offset > time)
+            break;
+        current = i;
+    }
+
+    if (sys->current_seekpoint != current)
+    {
+        sys->current_seekpoint = current;
+        sys->updates |= INPUT_UPDATE_SEEKPOINT;
+    }
+}
+
+static int SeekTime(demux_t *demux, vlc_tick_t time, bool precise)
+{
+    demux_sys_t *sys = demux->p_sys;
+
+    if (time < 0)
+        time = 0;
+    if (sys->duration > 0 && time > sys->duration)
+        time = sys->duration;
+
+    if (sys->ep_count > 0)
+    {
+        const int64_t relpts = samples_from_vlc_tick(time, 90000);
+        uint64_t target = sys->pts_start;
+
+        if (relpts > 0 && (uint64_t)relpts <= UINT64_MAX - target)
+            target += (uint64_t)relpts;
+
+        if (target > UINT32_MAX)
+            target = UINT32_MAX;
+
+        const int index = FindEntryPoint(sys, (uint32_t)target);
+        uint64_t byte_offset = 0;
+
+        if (index >= 0)
+            byte_offset = (uint64_t)sys->ep[index].packet * PSMF_PS_PACKET_SIZE;
+
+        const double pos = sys->data_size > 0 ? (double)byte_offset / (double)sys->data_size
+                                              : 0.0;
+
+        return demux_SetPosition(sys->psdemux, pos, precise);
+    }
+
+    if (sys->duration > 0)
+        return demux_SetPosition(sys->psdemux, (double)time / (double)sys->duration, precise);
+
+    return demux_SetTime(sys->psdemux, time, precise);
+}
+
+static int Demux(demux_t *demux)
+{
+    demux_sys_t *sys = demux->p_sys;
+    return demux_Demux(sys->psdemux);
+}
+
+static int Control(demux_t *demux, int query, va_list args)
+{
+    demux_sys_t *sys = demux->p_sys;
+
+    switch(query)
+    {
+        case DEMUX_GET_LENGTH:
+        {
+            vlc_tick_t *length = va_arg(args, vlc_tick_t *);
+            if (sys->duration != VLC_TICK_INVALID)
+            {
+                *length = sys->duration;
+                return VLC_SUCCESS;
+            }
+            return demux_Control(sys->psdemux, DEMUX_GET_LENGTH, length);
+        }
+
+        case DEMUX_SET_TIME:
+        {
+            const vlc_tick_t time = va_arg(args, vlc_tick_t);
+            const bool precise = (bool)va_arg(args, int);
+            const int ret = SeekTime(demux, time, precise);
+            if (ret == VLC_SUCCESS)
+                UpdateCurrentSeekpoint(sys, time);
+            return ret;
+        }
+
+        case DEMUX_SET_POSITION:
+        {
+            double pos = va_arg(args, double);
+            const bool precise = (bool)va_arg(args, int);
+
+            pos = VLC_CLIP(pos, 0.0, 1.0);
+            if (sys->duration > 0)
+            {
+                const vlc_tick_t time = (vlc_tick_t)(pos * sys->duration);
+                const int ret = SeekTime(demux, time, precise);
+                if (ret == VLC_SUCCESS)
+                    UpdateCurrentSeekpoint(sys, time);
+                return ret;
+            }
+
+            return demux_SetPosition(sys->psdemux, pos, precise);
+        }
+
+        case DEMUX_GET_POSITION:
+        {
+            double *pos = va_arg(args, double *);
+            vlc_tick_t time;
+
+            if (sys->duration > 0 &&
+                demux_Control(sys->psdemux, DEMUX_GET_TIME, &time) == VLC_SUCCESS)
+            {
+                *pos = VLC_CLIP((double)time / (double)sys->duration, 0.0, 1.0);
+                return VLC_SUCCESS;
+            }
+            return demux_Control(sys->psdemux, DEMUX_GET_POSITION, pos);
+        }
+
+        case DEMUX_GET_TITLE_INFO:
+        {
+            input_title_t ***titles = va_arg(args, input_title_t ***);
+            int *count = va_arg(args, int *);
+            int *title_offset = va_arg(args, int *);
+            int *chapter_offset = va_arg(args, int *);
+
+            *title_offset = 0;
+            *chapter_offset = 0;
+
+            if (sys->title == NULL)
+                return VLC_EGENERIC;
+
+            *titles = malloc(sizeof(**titles));
+            if (*titles == NULL)
+                return VLC_ENOMEM;
+
+            (*titles)[0] = vlc_input_title_Duplicate(sys->title);
+            if ((*titles)[0] == NULL)
+            {
+                free(*titles);
+                *titles = NULL;
+                return VLC_ENOMEM;
+            }
+
+            *count = 1;
+            return VLC_SUCCESS;
+        }
+
+        case DEMUX_GET_TITLE:
+            if (sys->title == NULL)
+                return VLC_EGENERIC;
+            *va_arg(args, int *) = 0;
+            return VLC_SUCCESS;
+
+        case DEMUX_SET_TITLE:
+            return (va_arg(args, int) == 0 && sys->title != NULL) ? VLC_SUCCESS : VLC_EGENERIC;
+
+        case DEMUX_GET_SEEKPOINT:
+            if (sys->title == NULL)
+                return VLC_EGENERIC;
+            *va_arg(args, int *) = sys->current_seekpoint;
+            return VLC_SUCCESS;
+
+        case DEMUX_SET_SEEKPOINT:
+        {
+            const int seekpoint = va_arg(args, int);
+            if (sys->title == NULL || seekpoint < 0 || seekpoint >= sys->title->i_seekpoint)
+                return VLC_EGENERIC;
+
+            const vlc_tick_t time = sys->title->seekpoint[seekpoint]->i_time_offset;
+            const int ret = SeekTime(demux, time, true);
+            if (ret == VLC_SUCCESS)
+            {
+                sys->current_seekpoint = seekpoint;
+                sys->updates |= INPUT_UPDATE_SEEKPOINT;
+            }
+            return ret;
+        }
+
+        case DEMUX_TEST_AND_CLEAR_FLAGS:
+        {
+            unsigned *flags = va_arg(args, unsigned *);
+            *flags &= sys->updates;
+            sys->updates &= ~*flags;
+            return VLC_SUCCESS;
+        }
+
+        default:
+            return demux_vaControl(sys->psdemux, query, args);
+    }
+}
+
+static void Close(vlc_object_t *obj)
+{
+    demux_t *demux = (demux_t *)obj;
+    demux_sys_t *sys = demux->p_sys;
+
+    if (sys->psdemux != NULL)
+        demux_Delete(sys->psdemux);
+
+    vlc_input_title_Delete(sys->title);
+    free(sys->ep);
+    if (sys->es_out)
+        es_out_Delete(sys->es_out);
+    free(sys);
+}
+
+static int Open(vlc_object_t *obj)
+{
+    demux_t *demux = (demux_t *)obj;
+    const uint8_t *header;
+
+    if (vlc_stream_Peek(demux->s, &header, PSMF_MIN_HEADER_SIZE) < PSMF_MIN_HEADER_SIZE)
+        return VLC_EGENERIC;
+
+    if (memcmp(header, "PSMF", 4) != 0)
+        return VLC_EGENERIC;
+
+    if (header[4] != '0' || header[5] != '0' ||
+        header[6] != '1' || header[7] < '0' || header[7] > '9')
+        return VLC_EGENERIC;
+
+    const uint32_t data_offset = GetDWBE(header + 8);
+    const uint32_t data_size = GetDWBE(header + 12);
+    const uint16_t stream_count = GetWBE(header + 0x80);
+
+    if (data_offset < PSMF_MIN_HEADER_SIZE ||
+        data_offset > PSMF_MAX_HEADER_SIZE ||
+        (data_offset & (PSMF_PS_PACKET_SIZE - 1)) != 0 ||
+        data_size == 0 ||
+        stream_count > PSMF_MAX_STREAMS)
+        return VLC_EGENERIC;
+
+    size_t need = PSMF_MIN_HEADER_SIZE + (size_t)stream_count * PSMF_STREAM_DESC_SIZE;
+    if (need > data_offset)
+        return VLC_EGENERIC;
+
+    if (vlc_stream_Peek(demux->s, &header, need) < (ssize_t)need)
+        return VLC_EGENERIC;
+
+    size_t metadata_need = need;
+
+    const uint32_t mark_offset = GetDWBE(header + 0x10);
+    const uint32_t mark_size = GetDWBE(header + 0x14);
+    if (mark_offset != 0 && mark_size >= 6 &&
+        mark_offset <= data_offset && mark_size <= data_offset - mark_offset)
+    {
+        metadata_need = __MAX(metadata_need, (size_t)mark_offset + mark_size);
+    }
+
+    for(uint16_t i = 0; i < stream_count; ++i)
+    {
+        const uint8_t *desc = header + PSMF_MIN_HEADER_SIZE +
+                              (size_t)i * PSMF_STREAM_DESC_SIZE;
+        if ((desc[0] & 0xf0) != 0xe0)
+            continue;
+
+        const uint32_t ep_offset = GetDWBE(desc + 4);
+        const uint32_t ep_count = GetDWBE(desc + 8);
+        if (ep_offset == 0 || ep_count == 0)
+            continue;
+
+        size_t ep_bytes;
+        if(ckd_mul(&ep_bytes, ep_count, PSMF_ENTRYPOINTS_ENTRY_SIZE))
+            continue;
+
+        if (ep_offset > data_offset || ep_bytes > data_offset - ep_offset)
+            continue;
+
+        metadata_need = __MAX(metadata_need, (size_t)ep_offset + ep_bytes);
+    }
+
+    if (metadata_need > data_offset)
+        return VLC_EGENERIC;
+
+    if (vlc_stream_Peek(demux->s, &header, metadata_need) < (ssize_t)metadata_need)
+        return VLC_EGENERIC;
+
+    demux_sys_t *sys = calloc(1, sizeof(*sys));
+    if (sys == NULL)
+        return VLC_ENOMEM;
+    demux->p_sys = sys;
+
+    sys->es_out = psmf_es_out_New(demux->out, header, metadata_need, stream_count);
+    if (unlikely(!sys->es_out))
+        goto error;
+
+    sys->data_offset = data_offset;
+    sys->data_size = data_size;
+    sys->duration = VLC_TICK_INVALID;
+
+    sys->pts_start = GetQW48BE(header + 0x54);
+    sys->pts_end = GetQW48BE(header + 0x5a);
+
+    if (sys->pts_end >= sys->pts_start &&
+        sys->pts_end - sys->pts_start <= INT64_MAX)
+        sys->duration = vlc_tick_from_samples(sys->pts_end - sys->pts_start, 90000);
+
+    int ret = ParseEntryPointMap(demux, sys, header, metadata_need);
+    if (ret != VLC_SUCCESS)
+        goto error;
+
+    ret = ParseMarks(demux, sys, header, metadata_need);
+    if (ret != VLC_SUCCESS)
+        goto error;
+
+    stream_t *p_stream = psmf_stream_New(demux, data_offset, data_size);
+    if (p_stream == NULL)
+        goto error;
+
+    sys->psdemux = demux_New(VLC_OBJECT(demux), "ps", "psmf://", p_stream, sys->es_out);
+    if (sys->psdemux == NULL)
+    {
+        vlc_stream_Delete(p_stream);
+        goto error;
+    }
+
+    demux->pf_demux = Demux;
+    demux->pf_control = Control;
+
+    msg_Dbg(demux,
+            "PSMF version %.4s, PS offset 0x%"PRIx32", size 0x%"PRIx32
+            ", duration %"PRId64" ms, EntryPoint entries %zu, marks %d",
+            header + 4, data_offset, data_size,
+            sys->duration != VLC_TICK_INVALID ? MS_FROM_VLC_TICK(sys->duration) : -1,
+            sys->ep_count,
+            sys->title ? sys->title->i_seekpoint : 0);
+
+    return VLC_SUCCESS;
+
+error:
+    vlc_input_title_Delete(sys->title);
+    free(sys->ep);
+    if (sys->es_out)
+        es_out_Delete(sys->es_out);
+    free(sys);
+    demux->p_sys = NULL;
+    return VLC_EGENERIC;
+}
+
+vlc_module_begin()
+    set_shortname(N_("PSMF"))
+    set_description(N_("PlayStation Movie Format demuxer"))
+    set_subcategory(SUBCAT_INPUT_DEMUX)
+    set_capability("demux", 10)
+    set_callbacks(Open, Close)
+    add_shortcut("psmf")
+    add_file_extension("pmf")
+vlc_module_end()



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/47cb0cdd345b36f1c2362128c25717c18e579e18...3c71cf34c48b0a411df9193202ce7183387f2b45

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/47cb0cdd345b36f1c2362128c25717c18e579e18...3c71cf34c48b0a411df9193202ce7183387f2b45
You're receiving this email because of your account on code.videolan.org. Manage all notifications: https://code.videolan.org/-/profile/notifications | Help: https://code.videolan.org/help




More information about the vlc-commits mailing list