Line | Branch | Exec | Source |
---|---|---|---|
1 | /* | ||
2 | * Hash/MD5 encoder (for codec/format testing) | ||
3 | * Copyright (c) 2009 Reimar Döffinger, based on crcenc (c) 2002 Fabrice Bellard | ||
4 | * | ||
5 | * This file is part of FFmpeg. | ||
6 | * | ||
7 | * FFmpeg is free software; you can redistribute it and/or | ||
8 | * modify it under the terms of the GNU Lesser General Public | ||
9 | * License as published by the Free Software Foundation; either | ||
10 | * version 2.1 of the License, or (at your option) any later version. | ||
11 | * | ||
12 | * FFmpeg is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
15 | * Lesser General Public License for more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU Lesser General Public | ||
18 | * License along with FFmpeg; if not, write to the Free Software | ||
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
20 | */ | ||
21 | |||
22 | #include "config_components.h" | ||
23 | |||
24 | #include "libavutil/avstring.h" | ||
25 | #include "libavutil/hash.h" | ||
26 | #include "libavutil/intreadwrite.h" | ||
27 | #include "libavutil/mem.h" | ||
28 | #include "libavutil/opt.h" | ||
29 | #include "avformat.h" | ||
30 | #include "internal.h" | ||
31 | #include "mux.h" | ||
32 | |||
33 | struct HashContext { | ||
34 | const AVClass *avclass; | ||
35 | struct AVHashContext **hashes; | ||
36 | char *hash_name; | ||
37 | int per_stream; | ||
38 | int format_version; | ||
39 | }; | ||
40 | |||
41 | #define OFFSET(x) offsetof(struct HashContext, x) | ||
42 | #define ENC AV_OPT_FLAG_ENCODING_PARAM | ||
43 | #define HASH_OPT(defaulttype) \ | ||
44 | { "hash", "set hash to use", OFFSET(hash_name), AV_OPT_TYPE_STRING, {.str = defaulttype}, 0, 0, ENC } | ||
45 | #define FORMAT_VERSION_OPT \ | ||
46 | { "format_version", "file format version", OFFSET(format_version), AV_OPT_TYPE_INT, {.i64 = 2}, 1, 2, ENC } | ||
47 | |||
48 | #if CONFIG_HASH_MUXER || CONFIG_STREAMHASH_MUXER | ||
49 | static const AVOption hash_streamhash_options[] = { | ||
50 | HASH_OPT("sha256"), | ||
51 | { NULL }, | ||
52 | }; | ||
53 | |||
54 | static const AVClass hash_streamhashenc_class = { | ||
55 | .class_name = "(stream) hash muxer", | ||
56 | .item_name = av_default_item_name, | ||
57 | .option = hash_streamhash_options, | ||
58 | .version = LIBAVUTIL_VERSION_INT, | ||
59 | }; | ||
60 | #endif | ||
61 | |||
62 | #if CONFIG_FRAMEHASH_MUXER | ||
63 | static const AVOption framehash_options[] = { | ||
64 | HASH_OPT("sha256"), | ||
65 | FORMAT_VERSION_OPT, | ||
66 | { NULL }, | ||
67 | }; | ||
68 | #endif | ||
69 | |||
70 | #if CONFIG_MD5_MUXER | ||
71 | static const AVOption md5_options[] = { | ||
72 | HASH_OPT("md5"), | ||
73 | { NULL }, | ||
74 | }; | ||
75 | #endif | ||
76 | |||
77 | #if CONFIG_FRAMEMD5_MUXER | ||
78 | static const AVOption framemd5_options[] = { | ||
79 | HASH_OPT("md5"), | ||
80 | FORMAT_VERSION_OPT, | ||
81 | { NULL }, | ||
82 | }; | ||
83 | #endif | ||
84 | |||
85 | #if CONFIG_HASH_MUXER || CONFIG_MD5_MUXER | ||
86 | ✗ | static int hash_init(struct AVFormatContext *s) | |
87 | { | ||
88 | int res; | ||
89 | ✗ | struct HashContext *c = s->priv_data; | |
90 | ✗ | c->per_stream = 0; | |
91 | ✗ | c->hashes = av_mallocz(sizeof(*c->hashes)); | |
92 | ✗ | if (!c->hashes) | |
93 | ✗ | return AVERROR(ENOMEM); | |
94 | ✗ | res = av_hash_alloc(&c->hashes[0], c->hash_name); | |
95 | ✗ | if (res < 0) | |
96 | ✗ | return res; | |
97 | ✗ | av_hash_init(c->hashes[0]); | |
98 | ✗ | return 0; | |
99 | } | ||
100 | #endif | ||
101 | |||
102 | #if CONFIG_STREAMHASH_MUXER | ||
103 | 1 | static int streamhash_init(struct AVFormatContext *s) | |
104 | { | ||
105 | int res, i; | ||
106 | 1 | struct HashContext *c = s->priv_data; | |
107 | 1 | c->per_stream = 1; | |
108 | 1 | c->hashes = av_calloc(s->nb_streams, sizeof(*c->hashes)); | |
109 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (!c->hashes) |
110 | ✗ | return AVERROR(ENOMEM); | |
111 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
|
3 | for (i = 0; i < s->nb_streams; i++) { |
112 | 2 | res = av_hash_alloc(&c->hashes[i], c->hash_name); | |
113 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (res < 0) { |
114 | ✗ | return res; | |
115 | } | ||
116 | 2 | av_hash_init(c->hashes[i]); | |
117 | } | ||
118 | 1 | return 0; | |
119 | } | ||
120 | #endif | ||
121 | |||
122 | #if CONFIG_HASH_MUXER || CONFIG_MD5_MUXER || CONFIG_STREAMHASH_MUXER | ||
123 | 2 | static char get_media_type_char(enum AVMediaType type) | |
124 | { | ||
125 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
2 | switch (type) { |
126 | ✗ | case AVMEDIA_TYPE_VIDEO: return 'v'; | |
127 | 2 | case AVMEDIA_TYPE_AUDIO: return 'a'; | |
128 | ✗ | case AVMEDIA_TYPE_DATA: return 'd'; | |
129 | ✗ | case AVMEDIA_TYPE_SUBTITLE: return 's'; | |
130 | ✗ | case AVMEDIA_TYPE_ATTACHMENT: return 't'; | |
131 | ✗ | default: return '?'; | |
132 | } | ||
133 | } | ||
134 | |||
135 | 518 | static int hash_write_packet(struct AVFormatContext *s, AVPacket *pkt) | |
136 | { | ||
137 | 518 | struct HashContext *c = s->priv_data; | |
138 |
1/2✓ Branch 0 taken 518 times.
✗ Branch 1 not taken.
|
518 | av_hash_update(c->hashes[c->per_stream ? pkt->stream_index : 0], pkt->data, pkt->size); |
139 | 518 | return 0; | |
140 | } | ||
141 | |||
142 | 1 | static int hash_write_trailer(struct AVFormatContext *s) | |
143 | { | ||
144 | 1 | struct HashContext *c = s->priv_data; | |
145 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | int num_hashes = c->per_stream ? s->nb_streams : 1; |
146 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
|
3 | for (int i = 0; i < num_hashes; i++) { |
147 | char buf[AV_HASH_MAX_SIZE*2+128]; | ||
148 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (c->per_stream) { |
149 | 2 | AVStream *st = s->streams[i]; | |
150 | 2 | snprintf(buf, sizeof(buf) - 200, "%d,%c,%s=", i, get_media_type_char(st->codecpar->codec_type), | |
151 | 2 | av_hash_get_name(c->hashes[i])); | |
152 | } else { | ||
153 | ✗ | snprintf(buf, sizeof(buf) - 200, "%s=", av_hash_get_name(c->hashes[i])); | |
154 | } | ||
155 | 2 | av_hash_final_hex(c->hashes[i], buf + strlen(buf), sizeof(buf) - strlen(buf)); | |
156 | 2 | av_strlcatf(buf, sizeof(buf), "\n"); | |
157 | 2 | avio_write(s->pb, buf, strlen(buf)); | |
158 | } | ||
159 | |||
160 | 1 | return 0; | |
161 | } | ||
162 | #endif | ||
163 | |||
164 | 327 | static void hash_free(struct AVFormatContext *s) | |
165 | { | ||
166 | 327 | struct HashContext *c = s->priv_data; | |
167 |
1/2✓ Branch 0 taken 327 times.
✗ Branch 1 not taken.
|
327 | if (c->hashes) { |
168 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 326 times.
|
327 | int num_hashes = c->per_stream ? s->nb_streams : 1; |
169 |
2/2✓ Branch 0 taken 328 times.
✓ Branch 1 taken 327 times.
|
655 | for (int i = 0; i < num_hashes; i++) { |
170 | 328 | av_hash_freep(&c->hashes[i]); | |
171 | } | ||
172 | } | ||
173 | 327 | av_freep(&c->hashes); | |
174 | 327 | } | |
175 | |||
176 | #if CONFIG_HASH_MUXER | ||
177 | const FFOutputFormat ff_hash_muxer = { | ||
178 | .p.name = "hash", | ||
179 | .p.long_name = NULL_IF_CONFIG_SMALL("Hash testing"), | ||
180 | .priv_data_size = sizeof(struct HashContext), | ||
181 | .p.audio_codec = AV_CODEC_ID_PCM_S16LE, | ||
182 | .p.video_codec = AV_CODEC_ID_RAWVIDEO, | ||
183 | .init = hash_init, | ||
184 | .write_packet = hash_write_packet, | ||
185 | .write_trailer = hash_write_trailer, | ||
186 | .deinit = hash_free, | ||
187 | .p.flags = AVFMT_VARIABLE_FPS | AVFMT_TS_NONSTRICT | | ||
188 | AVFMT_TS_NEGATIVE, | ||
189 | .p.priv_class = &hash_streamhashenc_class, | ||
190 | }; | ||
191 | #endif | ||
192 | |||
193 | #if CONFIG_MD5_MUXER | ||
194 | static const AVClass md5enc_class = { | ||
195 | .class_name = "MD5 muxer", | ||
196 | .item_name = av_default_item_name, | ||
197 | .option = md5_options, | ||
198 | .version = LIBAVUTIL_VERSION_INT, | ||
199 | }; | ||
200 | |||
201 | const FFOutputFormat ff_md5_muxer = { | ||
202 | .p.name = "md5", | ||
203 | .p.long_name = NULL_IF_CONFIG_SMALL("MD5 testing"), | ||
204 | .priv_data_size = sizeof(struct HashContext), | ||
205 | .p.audio_codec = AV_CODEC_ID_PCM_S16LE, | ||
206 | .p.video_codec = AV_CODEC_ID_RAWVIDEO, | ||
207 | .init = hash_init, | ||
208 | .write_packet = hash_write_packet, | ||
209 | .write_trailer = hash_write_trailer, | ||
210 | .deinit = hash_free, | ||
211 | .p.flags = AVFMT_VARIABLE_FPS | AVFMT_TS_NONSTRICT | | ||
212 | AVFMT_TS_NEGATIVE, | ||
213 | .p.priv_class = &md5enc_class, | ||
214 | }; | ||
215 | #endif | ||
216 | |||
217 | #if CONFIG_STREAMHASH_MUXER | ||
218 | const FFOutputFormat ff_streamhash_muxer = { | ||
219 | .p.name = "streamhash", | ||
220 | .p.long_name = NULL_IF_CONFIG_SMALL("Per-stream hash testing"), | ||
221 | .priv_data_size = sizeof(struct HashContext), | ||
222 | .p.audio_codec = AV_CODEC_ID_PCM_S16LE, | ||
223 | .p.video_codec = AV_CODEC_ID_RAWVIDEO, | ||
224 | .init = streamhash_init, | ||
225 | .write_packet = hash_write_packet, | ||
226 | .write_trailer = hash_write_trailer, | ||
227 | .deinit = hash_free, | ||
228 | .p.flags = AVFMT_VARIABLE_FPS | AVFMT_TS_NONSTRICT | | ||
229 | AVFMT_TS_NEGATIVE, | ||
230 | .p.priv_class = &hash_streamhashenc_class, | ||
231 | }; | ||
232 | #endif | ||
233 | |||
234 | #if CONFIG_FRAMEHASH_MUXER || CONFIG_FRAMEMD5_MUXER | ||
235 | 326 | static void framehash_print_extradata(struct AVFormatContext *s) | |
236 | { | ||
237 | int i; | ||
238 | |||
239 |
2/2✓ Branch 0 taken 329 times.
✓ Branch 1 taken 326 times.
|
655 | for (i = 0; i < s->nb_streams; i++) { |
240 | 329 | AVStream *st = s->streams[i]; | |
241 | 329 | AVCodecParameters *par = st->codecpar; | |
242 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 311 times.
|
329 | if (par->extradata) { |
243 | 18 | struct HashContext *c = s->priv_data; | |
244 | char buf[AV_HASH_MAX_SIZE*2+1]; | ||
245 | |||
246 | 18 | avio_printf(s->pb, "#extradata %d, %31d, ", i, par->extradata_size); | |
247 | 18 | av_hash_init(c->hashes[0]); | |
248 | 18 | av_hash_update(c->hashes[0], par->extradata, par->extradata_size); | |
249 | 18 | av_hash_final_hex(c->hashes[0], buf, sizeof(buf)); | |
250 | 18 | avio_write(s->pb, buf, strlen(buf)); | |
251 | 18 | avio_printf(s->pb, "\n"); | |
252 | } | ||
253 | } | ||
254 | 326 | } | |
255 | |||
256 | 326 | static int framehash_init(struct AVFormatContext *s) | |
257 | { | ||
258 | int res; | ||
259 | 326 | struct HashContext *c = s->priv_data; | |
260 | 326 | c->per_stream = 0; | |
261 | 326 | c->hashes = av_mallocz(sizeof(*c->hashes)); | |
262 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 326 times.
|
326 | if (!c->hashes) |
263 | ✗ | return AVERROR(ENOMEM); | |
264 | 326 | res = av_hash_alloc(&c->hashes[0], c->hash_name); | |
265 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 326 times.
|
326 | if (res < 0) |
266 | ✗ | return res; | |
267 | 326 | return 0; | |
268 | } | ||
269 | |||
270 | 326 | static int framehash_write_header(struct AVFormatContext *s) | |
271 | { | ||
272 | 326 | struct HashContext *c = s->priv_data; | |
273 | 326 | avio_printf(s->pb, "#format: frame checksums\n"); | |
274 | 326 | avio_printf(s->pb, "#version: %d\n", c->format_version); | |
275 | 326 | avio_printf(s->pb, "#hash: %s\n", av_hash_get_name(c->hashes[0])); | |
276 | 326 | framehash_print_extradata(s); | |
277 | 326 | ff_framehash_write_header(s); | |
278 | 326 | avio_printf(s->pb, "#stream#, dts, pts, duration, size, hash\n"); | |
279 | 326 | return 0; | |
280 | } | ||
281 | |||
282 | 6484 | static int framehash_write_packet(struct AVFormatContext *s, AVPacket *pkt) | |
283 | { | ||
284 | 6484 | struct HashContext *c = s->priv_data; | |
285 | char buf[AV_HASH_MAX_SIZE*2+128]; | ||
286 | int len; | ||
287 | 6484 | av_hash_init(c->hashes[0]); | |
288 | 6484 | av_hash_update(c->hashes[0], pkt->data, pkt->size); | |
289 | |||
290 | 6484 | snprintf(buf, sizeof(buf) - (AV_HASH_MAX_SIZE * 2 + 1), "%d, %10"PRId64", %10"PRId64", %8"PRId64", %8d, ", | |
291 | pkt->stream_index, pkt->dts, pkt->pts, pkt->duration, pkt->size); | ||
292 | 6484 | len = strlen(buf); | |
293 | 6484 | av_hash_final_hex(c->hashes[0], buf + len, sizeof(buf) - len); | |
294 | 6484 | avio_write(s->pb, buf, strlen(buf)); | |
295 | |||
296 |
2/4✓ Branch 0 taken 6484 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6484 times.
|
6484 | if (c->format_version > 1 && pkt->side_data_elems) { |
297 | int i; | ||
298 | ✗ | avio_printf(s->pb, ", S=%d", pkt->side_data_elems); | |
299 | ✗ | for (i = 0; i < pkt->side_data_elems; i++) { | |
300 | ✗ | av_hash_init(c->hashes[0]); | |
301 | if (HAVE_BIGENDIAN && pkt->side_data[i].type == AV_PKT_DATA_PALETTE) { | ||
302 | for (size_t j = 0; j < pkt->side_data[i].size; j += sizeof(uint32_t)) { | ||
303 | uint32_t data = AV_RL32(pkt->side_data[i].data + j); | ||
304 | av_hash_update(c->hashes[0], (uint8_t *)&data, sizeof(uint32_t)); | ||
305 | } | ||
306 | } else | ||
307 | ✗ | av_hash_update(c->hashes[0], pkt->side_data[i].data, pkt->side_data[i].size); | |
308 | ✗ | snprintf(buf, sizeof(buf) - (AV_HASH_MAX_SIZE * 2 + 1), | |
309 | ✗ | ", %8"SIZE_SPECIFIER", ", pkt->side_data[i].size); | |
310 | ✗ | len = strlen(buf); | |
311 | ✗ | av_hash_final_hex(c->hashes[0], buf + len, sizeof(buf) - len); | |
312 | ✗ | avio_write(s->pb, buf, strlen(buf)); | |
313 | } | ||
314 | } | ||
315 | |||
316 | 6484 | avio_printf(s->pb, "\n"); | |
317 | 6484 | return 0; | |
318 | } | ||
319 | #endif | ||
320 | |||
321 | #if CONFIG_FRAMEHASH_MUXER | ||
322 | static const AVClass framehash_class = { | ||
323 | .class_name = "frame hash muxer", | ||
324 | .item_name = av_default_item_name, | ||
325 | .option = framehash_options, | ||
326 | .version = LIBAVUTIL_VERSION_INT, | ||
327 | }; | ||
328 | |||
329 | const FFOutputFormat ff_framehash_muxer = { | ||
330 | .p.name = "framehash", | ||
331 | .p.long_name = NULL_IF_CONFIG_SMALL("Per-frame hash testing"), | ||
332 | .priv_data_size = sizeof(struct HashContext), | ||
333 | .p.audio_codec = AV_CODEC_ID_PCM_S16LE, | ||
334 | .p.video_codec = AV_CODEC_ID_RAWVIDEO, | ||
335 | .init = framehash_init, | ||
336 | .write_header = framehash_write_header, | ||
337 | .write_packet = framehash_write_packet, | ||
338 | .deinit = hash_free, | ||
339 | .p.flags = AVFMT_VARIABLE_FPS | AVFMT_TS_NONSTRICT | | ||
340 | AVFMT_TS_NEGATIVE, | ||
341 | .p.priv_class = &framehash_class, | ||
342 | }; | ||
343 | #endif | ||
344 | |||
345 | #if CONFIG_FRAMEMD5_MUXER | ||
346 | static const AVClass framemd5_class = { | ||
347 | .class_name = "frame MD5 muxer", | ||
348 | .item_name = av_default_item_name, | ||
349 | .option = framemd5_options, | ||
350 | .version = LIBAVUTIL_VERSION_INT, | ||
351 | }; | ||
352 | |||
353 | const FFOutputFormat ff_framemd5_muxer = { | ||
354 | .p.name = "framemd5", | ||
355 | .p.long_name = NULL_IF_CONFIG_SMALL("Per-frame MD5 testing"), | ||
356 | .priv_data_size = sizeof(struct HashContext), | ||
357 | .p.audio_codec = AV_CODEC_ID_PCM_S16LE, | ||
358 | .p.video_codec = AV_CODEC_ID_RAWVIDEO, | ||
359 | .init = framehash_init, | ||
360 | .write_header = framehash_write_header, | ||
361 | .write_packet = framehash_write_packet, | ||
362 | .deinit = hash_free, | ||
363 | .p.flags = AVFMT_VARIABLE_FPS | AVFMT_TS_NONSTRICT | | ||
364 | AVFMT_TS_NEGATIVE, | ||
365 | .p.priv_class = &framemd5_class, | ||
366 | }; | ||
367 | #endif | ||
368 |