[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