2 * HXVS/HXVT IP camera format
4 * Copyright (c) 2025 Zhao Zhili <quinkblack@foxmail.com>
6 * This file is part of FFmpeg.
8 * FFmpeg is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2.1 of the License, or (at your option) any later version.
13 * FFmpeg is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with FFmpeg; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 #include "libavutil/intreadwrite.h"
25 #include "avio_internal.h"
31 * https://code.videolan.org/videolan/vlc/-/blob/master/modules/demux/hx.c
32 * https://github.com/francescovannini/ipcam26Xconvert/tree/main
42 #define HXVS MKTAG('H', 'X', 'V', 'S')
48 #define HXVT MKTAG('H', 'X', 'V', 'T')
59 * Note: each HXVF contains a single NALU or slice, not a frame.
61 #define HXVF MKTAG('H', 'X', 'V', 'F')
72 * Note: The first four bytes of data is fake start code and NALU type,
73 * which should be skipped.
75 #define HXAF MKTAG('H', 'X', 'A', 'F')
84 #define HXFI MKTAG('H', 'X', 'F', 'I')
86 #define HXFI_TABLE_SIZE 200000
87 #define HXFI_TABLE_COUNT (200000 / 8)
89 typedef struct HxvsContext
{
94 static int hxvs_probe(const AVProbeData
*p
)
99 for (size_t i
= 0; i
< p
->buf_size
; ) {
100 uint32_t tag
= AV_RL32(&p
->buf
[i
]);
102 // first four bytes must begin with HXVS/HXVT
104 if (tag
!= HXVS
&& tag
!= HXVT
)
111 // Got RAP index at the end
114 return AVPROBE_SCORE_MAX
;
119 if (tag
== HXVF
|| tag
== HXAF
) {
120 bytes
= AV_RL32(&p
->buf
[i
]);
122 flag
|= (tag
== HXVF
) ? 2 : 4;
129 // Get audio and video
131 return AVPROBE_SCORE_EXTENSION
+ 10;
134 return AVPROBE_SCORE_EXTENSION
+ 2;
139 static int hxvs_create_video_stream(AVFormatContext
*s
, enum AVCodecID codec_id
)
141 HxvsContext
*ctx
= s
->priv_data
;
142 AVIOContext
*pb
= s
->pb
;
143 AVStream
*vt
= avformat_new_stream(s
, NULL
);
145 return AVERROR(ENOMEM
);
148 vt
->codecpar
->codec_type
= AVMEDIA_TYPE_VIDEO
;
149 vt
->codecpar
->codec_id
= codec_id
;
150 vt
->codecpar
->width
= avio_rl32(pb
);
151 vt
->codecpar
->height
= avio_rl32(pb
);
152 avpriv_set_pts_info(vt
, 32, 1, 1000);
153 ffstream(vt
)->need_parsing
= AVSTREAM_PARSE_FULL
;
154 ctx
->video_index
= vt
->index
;
162 static int hxvs_create_audio_stream(AVFormatContext
*s
)
164 HxvsContext
*ctx
= s
->priv_data
;
165 AVStream
*at
= avformat_new_stream(s
, NULL
);
167 return AVERROR(ENOMEM
);
170 at
->codecpar
->codec_type
= AVMEDIA_TYPE_AUDIO
;
171 at
->codecpar
->codec_id
= AV_CODEC_ID_PCM_ALAW
;
172 at
->codecpar
->ch_layout
= (AVChannelLayout
)AV_CHANNEL_LAYOUT_MONO
;
173 at
->codecpar
->sample_rate
= 8000;
174 avpriv_set_pts_info(at
, 32, 1, 1000);
175 ctx
->audio_index
= at
->index
;
180 static int hxvs_build_index(AVFormatContext
*s
)
182 HxvsContext
*ctx
= s
->priv_data
;
183 AVIOContext
*pb
= s
->pb
;
185 int64_t size
= avio_size(pb
);
188 // Don't return error when HXFI is missing
189 int64_t pos
= avio_seek(pb
, size
-(HXFI_TABLE_SIZE
+ 16), SEEK_SET
);
193 uint32_t tag
= avio_rl32(pb
);
197 AVStream
*st
= s
->streams
[ctx
->video_index
];
198 st
->duration
= avio_rl32(pb
);
201 FFStream
*const sti
= ffstream(st
);
203 for (int i
= 0; i
< HXFI_TABLE_COUNT
; i
++) {
204 uint32_t offset
= avio_rl32(pb
);
205 // pts = first_frame_pts + time
206 uint32_t time
= avio_rl32(pb
);
207 av_log(s
, AV_LOG_TRACE
, "%s/%d: offset %u, time %u\n",
208 av_fourcc2str(HXAF
), i
, offset
, time
);
213 // Get first frame timestamp
214 int64_t save_pos
= avio_tell(pb
);
215 pos
= avio_seek(pb
, offset
, SEEK_SET
);
220 av_log(s
, AV_LOG_ERROR
, "invalid tag %s at pos %u\n",
221 av_fourcc2str(tag
), offset
);
222 return AVERROR_INVALIDDATA
;
225 // save first frame timestamp to stream start_time
226 st
->start_time
= avio_rl32(pb
);
227 pos
= avio_seek(pb
, save_pos
, SEEK_SET
);
230 } else if (time
== prev_time
) {
231 // hxvs put SPS, PPS and slice into separate entries with same timestamp.
232 // Only record the first entry.
236 int ret
= ff_add_index_entry(&sti
->index_entries
,
237 &sti
->nb_index_entries
,
238 &sti
->index_entries_allocated_size
,
239 offset
, st
->start_time
+ time
,
240 0, 0, AVINDEX_KEYFRAME
);
248 static int hxvs_read_header(AVFormatContext
*s
)
250 AVIOContext
*pb
= s
->pb
;
251 uint32_t tag
= avio_rl32(pb
);
252 enum AVCodecID codec_id
;
255 codec_id
= AV_CODEC_ID_H264
;
256 } else if (tag
== HXVT
) {
257 codec_id
= AV_CODEC_ID_HEVC
;
259 av_log(s
, AV_LOG_ERROR
, "Unknown tag %s\n", av_fourcc2str(tag
));
260 return AVERROR_INVALIDDATA
;
263 int ret
= hxvs_create_video_stream(s
, codec_id
);
267 ret
= hxvs_create_audio_stream(s
);
271 if (pb
->seekable
& AVIO_SEEKABLE_NORMAL
) {
272 int64_t pos
= avio_tell(pb
);
276 ret
= hxvs_build_index(s
);
280 pos
= avio_seek(pb
, pos
, SEEK_SET
);
288 static int hxvs_read_packet(AVFormatContext
*s
, AVPacket
*pkt
)
290 HxvsContext
*ctx
= s
->priv_data
;
291 AVIOContext
*pb
= s
->pb
;
292 int64_t pos
= avio_tell(pb
);
293 uint32_t tag
= avio_rl32(pb
);
297 if (avio_feof(pb
) || (tag
== HXFI
))
300 if (tag
!= HXVF
&& tag
!= HXAF
)
301 return AVERROR_INVALIDDATA
;
303 bytes
= avio_rl32(pb
);
305 return AVERROR_INVALIDDATA
;
307 uint32_t timestamp
= avio_rl32(pb
);
311 if (avio_rl32(pb
) == 1)
312 key_flag
= AV_PKT_FLAG_KEY
;
313 index
= ctx
->video_index
;
316 index
= ctx
->audio_index
;
320 ret
= av_get_packet(pb
, pkt
, bytes
);
323 pkt
->pts
= timestamp
;
325 pkt
->stream_index
= index
;
326 pkt
->flags
|= key_flag
;
331 const FFInputFormat ff_hxvs_demuxer
= {
333 .p
.long_name
= NULL_IF_CONFIG_SMALL("HXVF/HXVS IP camera format"),
334 .p
.extensions
= "264,265",
335 .p
.flags
= AVFMT_GENERIC_INDEX
,
336 .read_probe
= hxvs_probe
,
337 .read_header
= hxvs_read_header
,
338 .read_packet
= hxvs_read_packet
,
339 .priv_data_size
= sizeof(HxvsContext
),