Coverage Report

Created: 2026-07-12 08:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vlc/modules/demux/hx.c
Line
Count
Source
1
/*****************************************************************************
2
 * hx.c : raw video / audio IP cam demuxer
3
 *****************************************************************************
4
 * Copyright (C) 2022 - VideoLabs, VLC authors and VideoLAN
5
 *
6
 * This program is free software; you can redistribute it and/or modify it
7
 * under the terms of the GNU Lesser General Public License as published by
8
 * the Free Software Foundation; either version 2.1 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public License
17
 * along with this program; if not, write to the Free Software Foundation,
18
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19
 *****************************************************************************/
20
#ifdef HAVE_CONFIG_H
21
# include "config.h"
22
#endif
23
24
#include <vlc_common.h>
25
#include <vlc_arrays.h>
26
#include <vlc_plugin.h>
27
#include <vlc_demux.h>
28
29
/* All Marker formats:
30
 * [tag.4][size.4][tag dependant.8] */
31
static const char HXVS[4] = { 'H', 'X', 'V', 'S' }; /* H264 file tag */
32
static const char HXVT[4] = { 'H', 'X', 'V', 'T' }; /* HEVC */
33
static const char HXVF[4] = { 'H', 'X', 'V', 'F' }; /* Video samples */
34
static const char HXAF[4] = { 'H', 'X', 'A', 'F' }; /* Audio samples */
35
static const char HXFI[4] = { 'H', 'X', 'F', 'I' }; /* RAP Index */
36
37
9
#define HX_HEADER_SIZE 16
38
2
#define HX_INDEX_SIZE 200000
39
40
struct hxfi_index
41
{
42
    uint32_t offset;
43
    uint32_t time;
44
};
45
46
typedef struct
47
{
48
    es_out_id_t *p_es_video;
49
    es_out_id_t *p_es_audio;
50
    vlc_tick_t video_pts;
51
    vlc_tick_t audio_pts;
52
    vlc_tick_t pcr;
53
    uint32_t video_pts_offset;
54
    uint32_t audio_pts_offset;
55
    uint32_t duration;
56
    DECL_ARRAY(struct hxfi_index) index;
57
} hx_sys_t;
58
59
static int LoadIndex(demux_t *p_demux)
60
1
{
61
1
    hx_sys_t *p_sys  = p_demux->p_sys;
62
63
    /* From what I understand:
64
     * Header HXFI[size.4][duration.4][?.4]
65
     * Size is always 200K, predictable file offset
66
     * All indexes entries are 4/4 bytes
67
     * zero filled empty entries
68
     */
69
70
1
    uint64_t size;
71
1
    if(vlc_stream_GetSize(p_demux->s, &size) != VLC_SUCCESS ||
72
1
       size < HX_INDEX_SIZE + HX_HEADER_SIZE)
73
0
        return VLC_EGENERIC;
74
75
1
    if(vlc_stream_Seek(p_demux->s, size - HX_INDEX_SIZE - HX_HEADER_SIZE) != VLC_SUCCESS)
76
0
        return VLC_EGENERIC;
77
78
1
    uint8_t temp[HX_HEADER_SIZE];
79
1
    if(vlc_stream_Read(p_demux->s, temp, HX_HEADER_SIZE) != HX_HEADER_SIZE ||
80
1
       memcmp(temp, HXFI, 4))
81
1
    {
82
1
        msg_Warn(p_demux, "Could not find index at expected location");
83
1
        return VLC_EGENERIC;
84
1
    }
85
86
0
    p_sys->duration = GetDWLE(temp + 8);
87
0
    msg_Dbg(p_demux, "Reading Index Length @%" PRIu32 "ms", p_sys->duration);
88
89
0
    uint32_t prevtime = UINT32_MAX;
90
0
    for(;;)
91
0
    {
92
0
        if(vlc_stream_Read(p_demux->s, temp, 8) != 8)
93
0
            break;
94
0
        struct hxfi_index entry;
95
0
        entry.offset = GetDWLE(temp);
96
0
        entry.time = GetDWLE(temp + 4);
97
0
        if(entry.offset == 0)
98
0
            break;
99
0
        if(entry.time != prevtime)
100
0
        {
101
0
            prevtime = entry.time;
102
0
            ARRAY_APPEND(p_sys->index, entry);
103
0
        }
104
0
    }
105
0
    msg_Dbg(p_demux, "Using %d entries from HXFI index", p_sys->index.i_size);
106
0
    return VLC_SUCCESS;
107
0
}
108
109
static struct hxfi_index LookupIndex(hx_sys_t *p_sys, uint32_t time)
110
0
{
111
0
    struct hxfi_index entry = { 0, 0 };
112
0
    if(p_sys->index.i_size)
113
0
    {
114
0
        unsigned l = 0, h = p_sys->index.i_size - 1;
115
0
        while(l <= h)
116
0
        {
117
0
            unsigned m = (l + h) >> 1;
118
0
            if(p_sys->index.p_elems[m].time > time)
119
0
            {
120
0
                if(m == 0)
121
0
                    break;
122
0
                h = m -1;
123
0
            }
124
0
            else
125
0
            {
126
                /* store lowest as temp result */
127
0
                entry = p_sys->index.p_elems[m];
128
0
                if(entry.time == time)
129
0
                    break;
130
0
                l = m + 1;
131
0
            }
132
0
        };
133
0
    }
134
0
    return entry;
135
0
}
136
137
static int Demux(demux_t *p_demux)
138
2
{
139
2
    hx_sys_t *p_sys  = p_demux->p_sys;
140
141
2
    uint8_t header[HX_HEADER_SIZE];
142
2
    if(vlc_stream_Read(p_demux->s, header, HX_HEADER_SIZE) != HX_HEADER_SIZE)
143
0
        return VLC_DEMUXER_EOF;
144
145
2
    es_out_id_t *es = NULL;
146
2
    vlc_tick_t *ppts;
147
2
    uint32_t *ppts_offset;
148
149
2
    if(!memcmp(header, HXVF, 4))
150
1
    {
151
1
        es = p_sys->p_es_video;
152
1
        ppts = &p_sys->video_pts;
153
1
        ppts_offset = &p_sys->video_pts_offset;
154
1
    }
155
1
    else if(!memcmp(header, HXAF, 4))
156
0
    {
157
0
        es = p_sys->p_es_audio;
158
0
        ppts = &p_sys->audio_pts;
159
0
        ppts_offset = &p_sys->audio_pts_offset;
160
0
    }
161
1
    else if(!memcmp(header, HXVS, 4) || !memcmp(header, HXVT, 4))
162
1
    {
163
1
        return VLC_DEMUXER_SUCCESS;
164
1
    }
165
0
    else
166
0
    {
167
0
        msg_Dbg(p_demux, "EOF on %4.4s", (const char *) header);
168
0
        return VLC_DEMUXER_EOF;
169
0
    }
170
171
1
    uint32_t sz = GetDWLE(header + 4);
172
1
    int i_ret = VLC_DEMUXER_SUCCESS;
173
174
1
    if(es)
175
1
    {
176
        /* [HXAF.4][size.4][pts.4][?.4] */
177
        /* [HXVF.4][size.4][pts.4][?.4] */
178
1
        *ppts = VLC_TICK_0;
179
1
        if(*ppts_offset != UINT32_MAX)
180
0
            *ppts += VLC_TICK_FROM_MS(GetDWLE(&header[8]) - *ppts_offset);
181
1
        else
182
1
            *ppts_offset = GetDWLE(&header[8]);
183
184
1
        block_t *p_block = vlc_stream_Block(p_demux->s, sz);
185
1
        if(p_block == NULL)
186
0
            return VLC_DEMUXER_EOF;
187
188
1
        if(p_block->i_buffer < sz)
189
1
            i_ret = VLC_DEMUXER_EOF;
190
191
1
        if(p_sys->p_es_audio == es)
192
0
        {
193
            /* HXAF sample format prefix [?.1/channels.1/rate.1/?.1] */
194
0
            const uint8_t audioprefix[4] = {0x00, 0x01, 0x50, 0x00};
195
0
            if(p_block->i_buffer >= 4 &&
196
0
               !memcmp(p_block->p_buffer, audioprefix, 4))
197
0
            {
198
0
                p_block->i_buffer -= 4;
199
0
                p_block->p_buffer += 4;
200
0
            }
201
0
            else
202
0
            {
203
0
                msg_Warn(p_demux,"Unsupported audio format, dropping");
204
0
                block_Release(p_block);
205
0
                p_block = NULL;
206
0
            }
207
0
        }
208
209
1
        if(p_sys->pcr == VLC_TICK_INVALID)
210
1
        {
211
1
            p_sys->pcr = *ppts;
212
1
            es_out_SetPCR(p_demux->out, p_sys->pcr);
213
1
        }
214
215
1
        if(p_block)
216
1
        {
217
1
            p_block->i_dts = p_block->i_pts = *ppts;
218
1
            es_out_Send(p_demux->out, es, p_block);
219
1
        }
220
221
1
        if( p_sys->audio_pts > p_sys->pcr )
222
0
        {
223
0
            p_sys->pcr = p_sys->audio_pts;
224
0
            es_out_SetPCR(p_demux->out, p_sys->pcr);
225
0
        }
226
1
    }
227
0
    else
228
0
    {
229
0
        if(vlc_stream_Read(p_demux->s, NULL, sz) != sz)
230
0
            i_ret = VLC_DEMUXER_EOF;
231
0
    }
232
233
1
    return i_ret;
234
1
}
235
236
static int SeekUsingIndex(demux_t *p_demux, uint32_t time, bool b_precise)
237
0
{
238
0
    hx_sys_t *p_sys  = p_demux->p_sys;
239
0
    struct hxfi_index idx = LookupIndex(p_sys, time);
240
0
    if(idx.offset == 0)
241
0
        return VLC_EGENERIC;
242
0
    int ret = vlc_stream_Seek(p_demux->s, idx.offset);
243
0
    if(ret == VLC_SUCCESS)
244
0
    {
245
0
        p_sys->pcr = VLC_TICK_0 + VLC_TICK_FROM_MS(idx.time);
246
0
        p_sys->audio_pts = VLC_TICK_INVALID;
247
0
        p_sys->video_pts = VLC_TICK_INVALID;
248
0
        if(b_precise)
249
0
            es_out_Control(p_demux->out, ES_OUT_SET_NEXT_DISPLAY_TIME,
250
0
                           VLC_TICK_0 + VLC_TICK_FROM_MS(time));
251
0
    }
252
0
    return ret;
253
0
}
254
255
static int Control(demux_t *p_demux, int i_query, va_list args)
256
0
{
257
0
    hx_sys_t *p_sys  = p_demux->p_sys;
258
0
    switch(i_query)
259
0
    {
260
0
        case DEMUX_GET_TIME:
261
0
        {
262
0
            *va_arg(args, vlc_tick_t *) = p_sys->pcr;
263
0
            return VLC_SUCCESS;
264
0
        }
265
0
        case DEMUX_GET_LENGTH:
266
0
        {
267
0
            *va_arg(args, vlc_tick_t *) = VLC_TICK_FROM_MS(p_sys->duration);
268
0
            return VLC_SUCCESS;
269
0
        }
270
0
        case DEMUX_GET_POSITION:
271
0
        {
272
0
            if(!p_sys->duration)
273
0
                return VLC_EGENERIC;
274
0
            *va_arg(args, double *) = MS_FROM_VLC_TICK(p_sys->pcr) / p_sys->duration;
275
0
            return VLC_SUCCESS;
276
0
        }
277
0
        case DEMUX_SET_TIME:
278
0
        {
279
0
            vlc_tick_t time = va_arg(args, vlc_tick_t);
280
0
            bool b_precise = va_arg(args, int);
281
0
            return SeekUsingIndex(p_demux, MS_FROM_VLC_TICK(time), b_precise);
282
0
        }
283
0
        case DEMUX_SET_POSITION:
284
0
        {
285
0
            double pos = va_arg(args, double);
286
0
            bool b_precise = va_arg(args, int);
287
0
            if(p_sys->index.i_size == 0)
288
0
                return VLC_EGENERIC;
289
0
            return SeekUsingIndex(p_demux, pos * p_sys->duration, b_precise);
290
0
        }
291
0
        case DEMUX_CAN_SEEK:
292
0
        {
293
0
            if(p_sys->index.i_size == 0)
294
0
                return VLC_EGENERIC;
295
            /* fallthrough */
296
0
        }
297
0
        default:
298
0
            break;
299
0
    }
300
301
0
    return demux_vaControlHelper(p_demux->s, 0, -1, 0, 1, i_query, args);
302
0
}
303
304
static void Close(vlc_object_t *p_this)
305
1
{
306
1
    demux_t     *p_demux = (demux_t*)p_this;
307
1
    hx_sys_t *p_sys  = p_demux->p_sys;
308
1
    ARRAY_RESET(p_sys->index);
309
1
    free(p_sys);
310
1
}
311
312
static int Open(vlc_object_t * p_this)
313
8
{
314
8
    demux_t     *p_demux = (demux_t*)p_this;
315
8
    hx_sys_t *p_sys;
316
8
    const uint8_t *p_peek;
317
318
8
    if(vlc_stream_Peek(p_demux->s, &p_peek, 20) != 20 ||
319
8
       (memcmp(p_peek, HXVS, 4) && memcmp(p_peek, HXVT, 4)) ||
320
1
        memcmp(p_peek + 16, HXVF, 4))
321
7
        return VLC_EGENERIC;
322
323
1
    p_demux->p_sys = p_sys = malloc(sizeof(hx_sys_t));
324
1
    if(!p_sys)
325
0
        return VLC_ENOMEM;
326
1
    p_sys->audio_pts = VLC_TICK_INVALID;
327
1
    p_sys->video_pts = VLC_TICK_INVALID;
328
1
    p_sys->pcr = VLC_TICK_INVALID;
329
1
    p_sys->audio_pts_offset = UINT32_MAX;
330
1
    p_sys->video_pts_offset = UINT32_MAX;
331
1
    p_sys->p_es_audio = NULL;
332
1
    p_sys->p_es_video = NULL;
333
1
    p_sys->duration = 0;
334
1
    ARRAY_INIT(p_sys->index);
335
336
1
    bool b_seekable;
337
1
    if(vlc_stream_Control(p_demux->s, STREAM_CAN_SEEK, &b_seekable) != VLC_SUCCESS)
338
0
        b_seekable = false;
339
340
1
    vlc_fourcc_t vcodec = memcmp(p_peek, HXVT, 4) ? VLC_CODEC_H264 : VLC_CODEC_HEVC;
341
1
    unsigned width = GetDWLE(p_peek + 4);
342
1
    unsigned height = GetDWLE(p_peek + 8);
343
344
1
    if(b_seekable)
345
1
    {
346
1
        uint64_t origin = vlc_stream_Tell(p_demux->s);
347
1
        if(LoadIndex(p_demux) != VLC_SUCCESS)
348
1
            msg_Err(p_demux, "Failed to load index");
349
1
        if(vlc_stream_Seek(p_demux->s, origin) != VLC_SUCCESS)
350
0
        {
351
0
            msg_Err(p_demux, "Failed to seek after loading index, giving up");
352
0
            Close(p_this);
353
0
            return VLC_EGENERIC;
354
0
        }
355
1
    }
356
357
    /* Create ES from parameters or defaults */
358
1
    es_format_t fmt;
359
360
1
    es_format_Init(&fmt, VIDEO_ES, vcodec);
361
1
    fmt.video.i_visible_width = width;
362
1
    fmt.video.i_visible_height = height;
363
1
    fmt.b_packetized = false;
364
1
    p_sys->p_es_video = es_out_Add(p_demux->out, &fmt);
365
1
    es_format_Clean(&fmt);
366
367
1
    es_format_Init(&fmt, AUDIO_ES, VLC_CODEC_ALAW);
368
1
    fmt.audio.i_rate = 8000;
369
1
    fmt.audio.i_channels = 1;
370
1
    p_sys->p_es_audio = es_out_Add(p_demux->out, &fmt);
371
1
    es_format_Clean(&fmt);
372
373
1
    p_demux->pf_demux   = Demux;
374
1
    p_demux->pf_control = Control;
375
376
1
    return VLC_SUCCESS;
377
1
}
378
379
168
vlc_module_begin ()
380
84
    set_shortname("HX")
381
84
    set_description(N_("HX video demuxer"))
382
84
    set_capability("demux", 10)
383
84
    set_subcategory(SUBCAT_INPUT_DEMUX)
384
84
    set_callbacks(Open, Close)
385
84
    add_shortcut("hx")
386
84
vlc_module_end ()