Line | Branch | Exec | Source |
---|---|---|---|
1 | /* | ||
2 | * APV helper functions for muxers | ||
3 | * Copyright (c) 2025 Dawid Kozinski <d.kozinski@samsung.com> | ||
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 "libavutil/avassert.h" | ||
23 | #include "libavutil/intreadwrite.h" | ||
24 | #include "libavutil/mem.h" | ||
25 | |||
26 | #include "apv.h" | ||
27 | #include "cbs.h" | ||
28 | #include "avformat.h" | ||
29 | #include "avio.h" | ||
30 | #include "avio_internal.h" | ||
31 | #include "libavcodec/cbs_apv.h" | ||
32 | #include "libavcodec/packet.h" | ||
33 | |||
34 | typedef struct APVDecoderFrameInfo { | ||
35 | uint8_t color_description_present_flag; // 1 bit | ||
36 | |||
37 | // The variable indicates whether the capture_time_distance value in the APV bitstream's frame header should be ignored during playback. | ||
38 | // If capture_time_distance_ignored is set to true, the capture_time_distance information will not be utilized, | ||
39 | // and timing information for playback should be calculated using an alternative method. | ||
40 | // If set to false, the capture_time_distance value will be used as is from the frame header. | ||
41 | // It is recommended to set this variable to true, allowing the use of MP4 timestamps for playback and recording, | ||
42 | // which enables the conventional compression and playback methods based on the timestamp table defined by the ISO-based file format. | ||
43 | uint8_t capture_time_distance_ignored; // 1-bit | ||
44 | |||
45 | uint8_t profile_idc; // 8 bits | ||
46 | uint8_t level_idc; // 8 bits | ||
47 | uint8_t band_idc; // 8 bits | ||
48 | uint32_t frame_width; // 32 bits | ||
49 | uint32_t frame_height; // 32 bits | ||
50 | uint8_t chroma_format_idc; // 4 bits | ||
51 | uint8_t bit_depth_minus8; // 4 bits | ||
52 | uint8_t capture_time_distance; // 8 bits | ||
53 | |||
54 | // if (color_description_present_flag) | ||
55 | uint8_t color_primaries; // 8 bits | ||
56 | uint8_t transfer_characteristics; // 8 bits | ||
57 | uint8_t matrix_coefficients; // 8 bits | ||
58 | uint8_t full_range_flag; // 1 bit | ||
59 | } APVDecoderFrameInfo; | ||
60 | |||
61 | typedef struct APVDecoderConfigurationEntry { | ||
62 | uint8_t pbu_type; // 8 bits | ||
63 | uint8_t number_of_frame_info; // 8 bits | ||
64 | |||
65 | APVDecoderFrameInfo *frame_info; // An array of size number_of_frame_info storing elements of type APVDecoderFrameInfo* | ||
66 | } APVDecoderConfigurationEntry; | ||
67 | |||
68 | // ISOBMFF binding for APV | ||
69 | // @see https://github.com/openapv/openapv/blob/main/readme/apv_isobmff.md | ||
70 | typedef struct APVDecoderConfigurationRecord { | ||
71 | uint8_t configurationVersion; // 8 bits | ||
72 | uint8_t number_of_configuration_entry; // 8 bits | ||
73 | |||
74 | APVDecoderConfigurationEntry *configuration_entry; // table of size number_of_configuration_entry | ||
75 | |||
76 | CodedBitstreamContext *cbc; | ||
77 | CodedBitstreamFragment frag; | ||
78 | } APVDecoderConfigurationRecord; | ||
79 | |||
80 | 1 | void ff_isom_write_apvc(AVIOContext *pb, const APVDecoderConfigurationRecord *apvc, void *logctx) | |
81 | { | ||
82 | 1 | av_log(logctx, AV_LOG_TRACE, "configurationVersion: %"PRIu8"\n", | |
83 | 1 | apvc->configurationVersion); | |
84 | |||
85 | 1 | av_log(logctx, AV_LOG_TRACE, "number_of_configuration_entry: %"PRIu8"\n", | |
86 | 1 | apvc->number_of_configuration_entry); | |
87 | |||
88 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | for (int i = 0; i < apvc->number_of_configuration_entry; i++) { |
89 | 1 | const APVDecoderConfigurationEntry *configuration_entry = &apvc->configuration_entry[i]; | |
90 | |||
91 | 1 | av_log(logctx, AV_LOG_TRACE, "pbu_type: %"PRIu8"\n", | |
92 | 1 | configuration_entry->pbu_type); | |
93 | |||
94 | 1 | av_log(logctx, AV_LOG_TRACE, "number_of_frame_info: %"PRIu8"\n", | |
95 | 1 | configuration_entry->number_of_frame_info); | |
96 | |||
97 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | for (int j = 0; j < configuration_entry->number_of_frame_info; j++) { |
98 | 1 | const APVDecoderFrameInfo *frame_info = &configuration_entry->frame_info[j]; | |
99 | |||
100 | 1 | av_log(logctx, AV_LOG_TRACE, "color_description_present_flag: %"PRIu8"\n", | |
101 | 1 | frame_info->color_description_present_flag); | |
102 | |||
103 | 1 | av_log(logctx, AV_LOG_TRACE, "capture_time_distance_ignored: %"PRIu8"\n", | |
104 | 1 | frame_info->capture_time_distance_ignored); | |
105 | |||
106 | 1 | av_log(logctx, AV_LOG_TRACE, "profile_idc: %"PRIu8"\n", | |
107 | 1 | frame_info->profile_idc); | |
108 | |||
109 | 1 | av_log(logctx, AV_LOG_TRACE, "level_idc: %"PRIu8"\n", | |
110 | 1 | frame_info->level_idc); | |
111 | |||
112 | 1 | av_log(logctx, AV_LOG_TRACE, "band_idc: %"PRIu8"\n", | |
113 | 1 | frame_info->band_idc); | |
114 | |||
115 | 1 | av_log(logctx, AV_LOG_TRACE, "frame_width: %"PRIu32"\n", | |
116 | 1 | frame_info->frame_width); | |
117 | |||
118 | 1 | av_log(logctx, AV_LOG_TRACE, "frame_height: %"PRIu32"\n", | |
119 | 1 | frame_info->frame_height); | |
120 | |||
121 | 1 | av_log(logctx, AV_LOG_TRACE, "chroma_format_idc: %"PRIu8"\n", | |
122 | 1 | frame_info->chroma_format_idc); | |
123 | |||
124 | 1 | av_log(logctx, AV_LOG_TRACE, "bit_depth_minus8: %"PRIu8"\n", | |
125 | 1 | frame_info->bit_depth_minus8); | |
126 | |||
127 | 1 | av_log(logctx, AV_LOG_TRACE, "capture_time_distance: %"PRIu8"\n", | |
128 | 1 | frame_info->capture_time_distance); | |
129 | |||
130 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (frame_info->color_description_present_flag) { |
131 | 1 | av_log(logctx, AV_LOG_TRACE, "color_primaries: %"PRIu8"\n", | |
132 | 1 | frame_info->color_primaries); | |
133 | |||
134 | 1 | av_log(logctx, AV_LOG_TRACE, "transfer_characteristics: %"PRIu8"\n", | |
135 | 1 | frame_info->transfer_characteristics); | |
136 | |||
137 | 1 | av_log(logctx, AV_LOG_TRACE, "matrix_coefficients: %"PRIu8"\n", | |
138 | 1 | frame_info->matrix_coefficients); | |
139 | |||
140 | 1 | av_log(logctx, AV_LOG_TRACE, "full_range_flag: %"PRIu8"\n", | |
141 | 1 | frame_info->full_range_flag); | |
142 | } | ||
143 | } | ||
144 | } | ||
145 | |||
146 | /* unsigned int(8) configurationVersion = 1; */ | ||
147 | 1 | avio_w8(pb, apvc->configurationVersion); | |
148 | |||
149 | 1 | avio_w8(pb, apvc->number_of_configuration_entry); | |
150 | |||
151 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | for (int i = 0; i < apvc->number_of_configuration_entry; i++) { |
152 | 1 | const APVDecoderConfigurationEntry *configuration_entry = &apvc->configuration_entry[i]; | |
153 | |||
154 | 1 | avio_w8(pb, configuration_entry->pbu_type); | |
155 | 1 | avio_w8(pb, configuration_entry->number_of_frame_info); | |
156 | |||
157 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | for (int j = 0; j < configuration_entry->number_of_frame_info; j++) { |
158 | 1 | const APVDecoderFrameInfo *frame_info = &configuration_entry->frame_info[j]; | |
159 | |||
160 | /* reserved_zero_6bits | ||
161 | * unsigned int(1) color_description_present_flag | ||
162 | * unsigned int(1) capture_time_distance_ignored */ | ||
163 | 1 | avio_w8(pb, frame_info->color_description_present_flag << 1 | | |
164 | 1 | frame_info->capture_time_distance_ignored); | |
165 | |||
166 | /* unsigned int(8) profile_idc */ | ||
167 | 1 | avio_w8(pb, frame_info->profile_idc); | |
168 | |||
169 | /* unsigned int(8) level_idc */ | ||
170 | 1 | avio_w8(pb, frame_info->level_idc); | |
171 | |||
172 | /* unsigned int(8) band_idc */ | ||
173 | 1 | avio_w8(pb, frame_info->band_idc); | |
174 | |||
175 | /* unsigned int(32) frame_width_minus1 */ | ||
176 | 1 | avio_wb32(pb, frame_info->frame_width); | |
177 | |||
178 | /* unsigned int(32) frame_height_minus1 */ | ||
179 | 1 | avio_wb32(pb, frame_info->frame_height); | |
180 | |||
181 | /* unsigned int(4) chroma_format_idc */ | ||
182 | /* unsigned int(4) bit_depth_minus8 */ | ||
183 | 1 | avio_w8(pb, (frame_info->chroma_format_idc << 4) | | |
184 | 1 | frame_info->bit_depth_minus8); | |
185 | |||
186 | /* unsigned int(8) capture_time_distance */ | ||
187 | 1 | avio_w8(pb, frame_info->capture_time_distance); | |
188 | |||
189 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (frame_info->color_description_present_flag) { |
190 | /* unsigned int(8) color_primaries */ | ||
191 | 1 | avio_w8(pb, frame_info->color_primaries); | |
192 | |||
193 | /* unsigned int(8) transfer_characteristics */ | ||
194 | 1 | avio_w8(pb, frame_info->transfer_characteristics); | |
195 | |||
196 | /* unsigned int(8) matrix_coefficients */ | ||
197 | 1 | avio_w8(pb, frame_info->matrix_coefficients); | |
198 | |||
199 | /* unsigned int(1) full_range_flag | ||
200 | * reserved_zero_7bits */ | ||
201 | 1 | avio_w8(pb, frame_info->full_range_flag << 7); | |
202 | } | ||
203 | } | ||
204 | } | ||
205 | 1 | } | |
206 | |||
207 | static const CodedBitstreamUnitType decompose_unit_types[] = { | ||
208 | APV_PBU_PRIMARY_FRAME, APV_PBU_NON_PRIMARY_FRAME, | ||
209 | APV_PBU_PREVIEW_FRAME, APV_PBU_DEPTH_FRAME, APV_PBU_ALPHA_FRAME | ||
210 | }; | ||
211 | |||
212 | 1 | static int apv_add_configuration_entry(APVDecoderConfigurationRecord *apvc, int pbu_type) | |
213 | { | ||
214 | APVDecoderConfigurationEntry *temp; | ||
215 | |||
216 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | av_assert0(apvc->number_of_configuration_entry < FF_ARRAY_ELEMS(decompose_unit_types)); |
217 | 1 | temp = av_realloc_array(apvc->configuration_entry, | |
218 | 1 | apvc->number_of_configuration_entry + 1, sizeof(*apvc->configuration_entry)); | |
219 | |||
220 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (!temp) |
221 | ✗ | return AVERROR(ENOMEM); | |
222 | |||
223 | 1 | apvc->configuration_entry = temp; | |
224 | 1 | memset(&apvc->configuration_entry[apvc->number_of_configuration_entry], 0, sizeof(*apvc->configuration_entry)); | |
225 | 1 | apvc->configuration_entry[apvc->number_of_configuration_entry].pbu_type = pbu_type; | |
226 | 1 | apvc->number_of_configuration_entry++; | |
227 | |||
228 | 1 | return 0; | |
229 | } | ||
230 | |||
231 | 1 | static int apv_add_frameinfo(APVDecoderConfigurationEntry *configuration_entry, | |
232 | const APVDecoderFrameInfo *frame_info) | ||
233 | { | ||
234 | APVDecoderFrameInfo *temp; | ||
235 | |||
236 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (configuration_entry->number_of_frame_info >= UINT8_MAX) |
237 | ✗ | return AVERROR(EINVAL); | |
238 | |||
239 | 1 | temp = av_realloc_array(configuration_entry->frame_info, | |
240 | 1 | configuration_entry->number_of_frame_info + 1, sizeof(*configuration_entry->frame_info)); | |
241 | |||
242 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (!temp) |
243 | ✗ | return AVERROR(ENOMEM); | |
244 | |||
245 | 1 | configuration_entry->frame_info = temp; | |
246 | 1 | memcpy(&configuration_entry->frame_info[configuration_entry->number_of_frame_info], frame_info, sizeof(*frame_info)); | |
247 | 1 | configuration_entry->number_of_frame_info++; | |
248 | |||
249 | 1 | return 0; | |
250 | } | ||
251 | |||
252 | 3 | int ff_isom_parse_apvc(APVDecoderConfigurationRecord *apvc, | |
253 | const AVPacket *pkt, void *logctx) | ||
254 | { | ||
255 | APVDecoderFrameInfo frame_info; | ||
256 | int ret; | ||
257 | |||
258 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
|
3 | if (pkt->size < 8 || AV_RB32(pkt->data) != APV_SIGNATURE) |
259 | /* We can't write a valid apvC from the provided data */ | ||
260 | ✗ | return AVERROR_INVALIDDATA; | |
261 | |||
262 | 3 | ret = ff_lavf_cbs_read(apvc->cbc, &apvc->frag, pkt->buf, pkt->data, pkt->size); | |
263 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (ret < 0) { |
264 | ✗ | av_log(logctx, AV_LOG_ERROR, "Failed to parse access unit.\n"); | |
265 | ✗ | return ret; | |
266 | } | ||
267 | |||
268 | 3 | memset(&frame_info, 0, sizeof(frame_info)); | |
269 | 3 | frame_info.capture_time_distance_ignored = 1; | |
270 | |||
271 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 3 times.
|
9 | for (int i = 0; i < apvc->frag.nb_units; i++) { |
272 | 6 | const CodedBitstreamUnit *pbu = &apvc->frag.units[i]; | |
273 | int j; | ||
274 | |||
275 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | switch (pbu->type) { |
276 | 3 | case APV_PBU_PRIMARY_FRAME: | |
277 | case APV_PBU_NON_PRIMARY_FRAME: | ||
278 | case APV_PBU_PREVIEW_FRAME: | ||
279 | case APV_PBU_DEPTH_FRAME: | ||
280 | case APV_PBU_ALPHA_FRAME: | ||
281 | 3 | break; | |
282 | 3 | default: | |
283 | 3 | continue; | |
284 | }; | ||
285 | |||
286 | 3 | const APVRawFrame *frame = pbu->content; | |
287 | 3 | const APVRawFrameHeader *header = &frame->frame_header; | |
288 | 3 | const APVRawFrameInfo *info = &header->frame_info; | |
289 | 3 | int bit_depth = info->bit_depth_minus8 + 8; | |
290 | |||
291 |
3/6✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
|
3 | if (bit_depth < 8 || bit_depth > 16 || bit_depth % 2) |
292 | break; | ||
293 | |||
294 | 3 | frame_info.profile_idc = info->profile_idc; | |
295 | 3 | frame_info.level_idc = info->level_idc; | |
296 | 3 | frame_info.band_idc = info->band_idc; | |
297 | |||
298 | 3 | frame_info.frame_width = info->frame_width; | |
299 | 3 | frame_info.frame_height =info->frame_height; | |
300 | 3 | frame_info.chroma_format_idc = info->chroma_format_idc; | |
301 | 3 | frame_info.bit_depth_minus8 = info->bit_depth_minus8; | |
302 | 3 | frame_info.capture_time_distance = info->capture_time_distance; | |
303 | |||
304 | 3 | frame_info.color_description_present_flag = header->color_description_present_flag; | |
305 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | if (frame_info.color_description_present_flag) { |
306 | 3 | frame_info.color_primaries = header->color_primaries; | |
307 | 3 | frame_info.transfer_characteristics = header->transfer_characteristics; | |
308 | 3 | frame_info.matrix_coefficients = header->matrix_coefficients; | |
309 | 3 | frame_info.full_range_flag = header->full_range_flag; | |
310 | } else { | ||
311 | ✗ | frame_info.color_primaries = | |
312 | ✗ | frame_info.transfer_characteristics = | |
313 | ✗ | frame_info.matrix_coefficients = | |
314 | ✗ | frame_info.full_range_flag = 0; | |
315 | } | ||
316 | |||
317 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
|
3 | for (j = 0; j < apvc->number_of_configuration_entry; j++) { |
318 | int k; | ||
319 | |||
320 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (apvc->configuration_entry[j].pbu_type != pbu->type) |
321 | ✗ | continue; | |
322 | |||
323 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | for (k = 0; k < apvc->configuration_entry[j].number_of_frame_info; k++) { |
324 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (!memcmp(&apvc->configuration_entry[j].frame_info[k], &frame_info, sizeof(frame_info))) |
325 | 2 | break; | |
326 | } | ||
327 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | if (k == apvc->configuration_entry[j].number_of_frame_info) { |
328 | ✗ | ret = apv_add_frameinfo(&apvc->configuration_entry[j], &frame_info); | |
329 | ✗ | if (ret < 0) | |
330 | ✗ | goto end; | |
331 | } | ||
332 | 2 | break; | |
333 | } | ||
334 | |||
335 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
|
3 | if (j == apvc->number_of_configuration_entry) { |
336 | 1 | ret = apv_add_configuration_entry(apvc, pbu->type); | |
337 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (ret < 0) |
338 | ✗ | goto end; | |
339 | 1 | ret = apv_add_frameinfo(&apvc->configuration_entry[j], &frame_info); | |
340 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (ret < 0) |
341 | ✗ | goto end; | |
342 | } | ||
343 | } | ||
344 | |||
345 | 3 | ret = 0; | |
346 | 3 | end: | |
347 | 3 | ff_lavf_cbs_fragment_reset(&apvc->frag); | |
348 | |||
349 | 3 | return ret; | |
350 | } | ||
351 | |||
352 | 1 | int ff_isom_init_apvc(APVDecoderConfigurationRecord **papvc, void *logctx) | |
353 | { | ||
354 | 1 | APVDecoderConfigurationRecord *apvc = av_mallocz(sizeof(*apvc)); | |
355 | |||
356 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (!apvc) |
357 | ✗ | return AVERROR(ENOMEM); | |
358 | |||
359 | 1 | int ret = ff_lavf_cbs_init(&apvc->cbc, AV_CODEC_ID_APV, logctx); | |
360 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (ret < 0) { |
361 | ✗ | av_freep(&apvc); | |
362 | ✗ | return ret; | |
363 | } | ||
364 | |||
365 | 1 | apvc->cbc->decompose_unit_types = decompose_unit_types; | |
366 | 1 | apvc->cbc->nb_decompose_unit_types = FF_ARRAY_ELEMS(decompose_unit_types); | |
367 | |||
368 | 1 | apvc->configurationVersion = 1; | |
369 | |||
370 | 1 | *papvc = apvc; | |
371 | |||
372 | 1 | return 0; | |
373 | } | ||
374 | |||
375 | 281 | void ff_isom_close_apvc(APVDecoderConfigurationRecord **papvc) | |
376 | { | ||
377 | 281 | APVDecoderConfigurationRecord *apvc = *papvc; | |
378 | |||
379 |
2/2✓ Branch 0 taken 280 times.
✓ Branch 1 taken 1 times.
|
281 | if (!apvc) |
380 | 280 | return; | |
381 | |||
382 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
|
2 | for (int i = 0; i < apvc->number_of_configuration_entry; i++) |
383 | 1 | av_freep(&apvc->configuration_entry[i].frame_info); | |
384 | 1 | av_freep(&apvc->configuration_entry); | |
385 | |||
386 | 1 | ff_lavf_cbs_fragment_free(&apvc->frag); | |
387 | 1 | ff_lavf_cbs_close(&apvc->cbc); | |
388 | |||
389 | 1 | av_freep(papvc); | |
390 | } | ||
391 |