FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/hxvs.c
Date: 2026-04-28 19:09:24
Exec Total Coverage
Lines: 129 159 81.1%
Functions: 6 6 100.0%
Branches: 48 82 58.5%

Line Branch Exec Source
1 /*
2 * HXVS/HXVT IP camera format
3 *
4 * Copyright (c) 2025 Zhao Zhili <quinkblack@foxmail.com>
5 *
6 * This file is part of FFmpeg.
7 *
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.
12 *
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.
17 *
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
21 */
22
23 #include "libavutil/intreadwrite.h"
24
25 #include "avio_internal.h"
26 #include "demux.h"
27 #include "internal.h"
28
29 /*
30 * Ref
31 * https://code.videolan.org/videolan/vlc/-/blob/master/modules/demux/hx.c
32 * https://github.com/francescovannini/ipcam26Xconvert/tree/main
33 */
34
35 /* H.264
36 *
37 * uint32_t tag;
38 * uint32_t width;
39 * uint32_t height;
40 * uint8_t padding[4];
41 */
42 #define HXVS MKTAG('H', 'X', 'V', 'S')
43
44 /* H.265
45 *
46 * Same as HXVS.
47 */
48 #define HXVT MKTAG('H', 'X', 'V', 'T')
49
50 /* video frame
51 *
52 * uint32_t tag;
53 * uint32_t bytes
54 * uint32_t timestamp;
55 * uint32_t flags;
56 * ------------------
57 * uint8_t data[bytes]
58 *
59 * Note: each HXVF contains a single NALU or slice, not a frame.
60 */
61 #define HXVF MKTAG('H', 'X', 'V', 'F')
62
63 /* audio frame
64 *
65 * uint32_t tag;
66 * uint32_t bytes
67 * uint32_t timestamp;
68 * uint32_t flags;
69 * ------------------
70 * uint8_t data[bytes]
71 *
72 * Note: The first four bytes of data is fake start code and NALU type,
73 * which should be skipped.
74 */
75 #define HXAF MKTAG('H', 'X', 'A', 'F')
76
77 /* RAP frame index
78 *
79 * uint32_t tag;
80 * uint32_t bytes
81 * uint32_t duration;
82 * uint32_t flags;
83 */
84 #define HXFI MKTAG('H', 'X', 'F', 'I')
85
86 #define HXFI_TABLE_SIZE 200000
87 #define HXFI_TABLE_COUNT (200000 / 8)
88
89 typedef struct HxvsContext {
90 int video_index;
91 int audio_index;
92 } HxvsContext;
93
94 7480 static int hxvs_probe(const AVProbeData *p)
95 {
96 7480 uint32_t flag = 0;
97 uint32_t bytes;
98
99
2/2
✓ Branch 0 taken 7484 times.
✓ Branch 1 taken 1 times.
7485 for (size_t i = 0; i < p->buf_size; ) {
100 7484 uint32_t tag = AV_RL32(&p->buf[i]);
101
102 // first four bytes must begin with HXVS/HXVT
103
2/2
✓ Branch 0 taken 7480 times.
✓ Branch 1 taken 4 times.
7484 if (i == 0) {
104
3/4
✓ Branch 0 taken 7480 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7479 times.
✓ Branch 3 taken 1 times.
7480 if (tag != HXVS && tag != HXVT)
105 7479 return 0;
106 1 flag |= 1;
107 1 i += 16;
108 1 continue;
109 }
110
111 // Got RAP index at the end
112
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (tag == HXFI) {
113 if (flag == 7)
114 return AVPROBE_SCORE_MAX;
115 break;
116 }
117
118 4 i += 4;
119
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
4 if (tag == HXVF || tag == HXAF) {
120 4 bytes = AV_RL32(&p->buf[i]);
121
122
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (12 + bytes > INT_MAX - i)
123 return 0;
124
125 4 i += 12 + bytes;
126
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 flag |= (tag == HXVF) ? 2 : 4;
127 4 continue;
128 }
129
130 return 0;
131 }
132
133 // Get audio and video
134
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (flag == 7)
135 return AVPROBE_SCORE_EXTENSION + 10;
136 // Get video only
137
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (flag == 3)
138 1 return AVPROBE_SCORE_EXTENSION + 2;
139
140 return 0;
141 }
142
143 1 static int hxvs_create_video_stream(AVFormatContext *s, enum AVCodecID codec_id)
144 {
145 1 HxvsContext *ctx = s->priv_data;
146 1 AVIOContext *pb = s->pb;
147 1 AVStream *vt = avformat_new_stream(s, NULL);
148
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!vt)
149 return AVERROR(ENOMEM);
150
151 1 vt->id = 0;
152 1 vt->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
153 1 vt->codecpar->codec_id = codec_id;
154 1 vt->codecpar->width = avio_rl32(pb);
155 1 vt->codecpar->height = avio_rl32(pb);
156 1 avpriv_set_pts_info(vt, 32, 1, 1000);
157 1 ffstream(vt)->need_parsing = AVSTREAM_PARSE_FULL;
158 1 ctx->video_index = vt->index;
159
160 // skip padding
161 1 avio_skip(pb, 4);
162
163 1 return 0;
164 }
165
166 1 static int hxvs_create_audio_stream(AVFormatContext *s)
167 {
168 1 HxvsContext *ctx = s->priv_data;
169 1 AVStream *at = avformat_new_stream(s, NULL);
170
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (!at)
171 return AVERROR(ENOMEM);
172
173 1 at->id = 1;
174 1 at->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
175 1 at->codecpar->codec_id = AV_CODEC_ID_PCM_ALAW;
176 1 at->codecpar->ch_layout = (AVChannelLayout)AV_CHANNEL_LAYOUT_MONO;
177 1 at->codecpar->sample_rate = 8000;
178 1 avpriv_set_pts_info(at, 32, 1, 1000);
179 1 ctx->audio_index = at->index;
180
181 1 return 0;
182 }
183
184 1 static int hxvs_build_index(AVFormatContext *s)
185 {
186 1 HxvsContext *ctx = s->priv_data;
187 1 AVIOContext *pb = s->pb;
188
189 1 int64_t size = avio_size(pb);
190
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (size < 0)
191 return size;
192 // Don't return error when HXFI is missing
193 1 int64_t pos = avio_seek(pb, size -(HXFI_TABLE_SIZE + 16), SEEK_SET);
194
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (pos < 0)
195 return 0;
196
197 1 uint32_t tag = avio_rl32(pb);
198
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (tag != HXFI)
199 return 0;
200 1 avio_skip(pb, 4);
201 1 AVStream *st = s->streams[ctx->video_index];
202 1 st->duration = avio_rl32(pb);
203 1 avio_skip(pb, 4);
204
205 1 FFStream *const sti = ffstream(st);
206 uint32_t prev_time;
207
1/2
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
17 for (int i = 0; i < HXFI_TABLE_COUNT; i++) {
208 17 uint32_t offset = avio_rl32(pb);
209 // pts = first_frame_pts + time
210 17 uint32_t time = avio_rl32(pb);
211 17 av_log(s, AV_LOG_TRACE, "%s/%d: offset %u, time %u\n",
212 17 av_fourcc2str(HXAF), i, offset, time);
213
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 16 times.
17 if (!offset)
214 1 break;
215
216
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 15 times.
16 if (!i) {
217 // Get first frame timestamp
218 1 int64_t save_pos = avio_tell(pb);
219 1 pos = avio_seek(pb, offset, SEEK_SET);
220
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (pos < 0)
221 return pos;
222 1 tag = avio_rl32(pb);
223
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (tag != HXVF) {
224 av_log(s, AV_LOG_ERROR, "invalid tag %s at pos %u\n",
225 av_fourcc2str(tag), offset);
226 return AVERROR_INVALIDDATA;
227 }
228 1 avio_skip(pb, 4);
229 // save first frame timestamp to stream start_time
230 1 st->start_time = avio_rl32(pb);
231 1 pos = avio_seek(pb, save_pos, SEEK_SET);
232
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (pos < 0)
233 return pos;
234
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 3 times.
15 } else if (time == prev_time) {
235 // hxvs put SPS, PPS and slice into separate entries with same timestamp.
236 // Only record the first entry.
237 12 continue;
238 }
239 4 prev_time = time;
240 4 int ret = ff_add_index_entry(&sti->index_entries,
241 &sti->nb_index_entries,
242 &sti->index_entries_allocated_size,
243 4 offset, st->start_time + time,
244 0, 0, AVINDEX_KEYFRAME);
245
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (ret < 0)
246 return ret;
247 }
248
249 1 return 0;
250 }
251
252 1 static int hxvs_read_header(AVFormatContext *s)
253 {
254 1 AVIOContext *pb = s->pb;
255 1 uint32_t tag = avio_rl32(pb);
256 enum AVCodecID codec_id;
257
258
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (tag == HXVS) {
259 codec_id = AV_CODEC_ID_H264;
260
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 } else if (tag == HXVT) {
261 1 codec_id = AV_CODEC_ID_HEVC;
262 } else {
263 av_log(s, AV_LOG_ERROR, "Unknown tag %s\n", av_fourcc2str(tag));
264 return AVERROR_INVALIDDATA;
265 }
266
267 1 int ret = hxvs_create_video_stream(s, codec_id);
268
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ret < 0)
269 return ret;
270
271 1 ret = hxvs_create_audio_stream(s);
272
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ret < 0)
273 return ret;
274
275
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (pb->seekable & AVIO_SEEKABLE_NORMAL) {
276 1 int64_t pos = avio_tell(pb);
277
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (pos < 0)
278 return pos;
279
280 1 ret = hxvs_build_index(s);
281
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (ret < 0)
282 return ret;
283
284 1 pos = avio_seek(pb, pos, SEEK_SET);
285
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (pos < 0)
286 return ret;
287 }
288
289 1 return 0;
290 }
291
292 220 static int hxvs_read_packet(AVFormatContext *s, AVPacket *pkt)
293 {
294 220 HxvsContext *ctx = s->priv_data;
295 220 AVIOContext *pb = s->pb;
296 220 int64_t pos = avio_tell(pb);
297 220 uint32_t tag = avio_rl32(pb);
298 uint32_t bytes;
299 int ret;
300
301
2/4
✓ Branch 1 taken 220 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 220 times.
220 if (avio_feof(pb) || (tag == HXFI))
302 return AVERROR_EOF;
303
304
3/4
✓ Branch 0 taken 168 times.
✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 168 times.
220 if (tag != HXVF && tag != HXAF)
305 return AVERROR_INVALIDDATA;
306
307 220 bytes = avio_rl32(pb);
308
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 220 times.
220 if (bytes < 4)
309 return AVERROR_INVALIDDATA;
310
311 220 uint32_t timestamp = avio_rl32(pb);
312 220 int key_flag = 0;
313 int index;
314
2/2
✓ Branch 0 taken 52 times.
✓ Branch 1 taken 168 times.
220 if (tag == HXVF) {
315
2/2
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 40 times.
52 if (avio_rl32(pb) == 1)
316 12 key_flag = AV_PKT_FLAG_KEY;
317 52 index = ctx->video_index;
318 } else {
319 168 avio_skip(pb, 8);
320 168 index = ctx->audio_index;
321 168 bytes -= 4;
322 }
323
324 220 ret = av_get_packet(pb, pkt, bytes);
325
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 220 times.
220 if (ret < 0)
326 return ret;
327 220 pkt->pts = timestamp;
328 220 pkt->pos = pos;
329 220 pkt->stream_index = index;
330 220 pkt->flags |= key_flag;
331
332 220 return 0;
333 }
334
335 const FFInputFormat ff_hxvs_demuxer = {
336 .p.name = "hxvs",
337 .p.long_name = NULL_IF_CONFIG_SMALL("HXVF/HXVS IP camera format"),
338 .p.extensions = "264,265",
339 .p.flags = AVFMT_GENERIC_INDEX,
340 .read_probe = hxvs_probe,
341 .read_header = hxvs_read_header,
342 .read_packet = hxvs_read_packet,
343 .priv_data_size = sizeof(HxvsContext),
344 };
345