FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/pdvenc.c
Date: 2026-08-15 14:54:27
Exec Total Coverage
Lines: 67 94 71.3%
Functions: 5 5 100.0%
Branches: 32 60 53.3%

Line Branch Exec Source
1 /*
2 * PDV muxer
3 *
4 * Copyright (c) 2026 Priyanshu Thapliyal <priyanshuthapliyal2005@gmail.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/mem.h"
24 #include "libavutil/opt.h"
25 #include "libavutil/rational.h"
26 #include "avformat.h"
27 #include "avio_internal.h"
28 #include "mux.h"
29
30 #define PDV_MAGIC "Playdate VID\x00\x00\x00\x00"
31 #define PDV_MAX_FRAMES UINT16_MAX
32 #define PDV_MAX_OFFSET ((1U << 30) - 1)
33
34 typedef struct PDVMuxContext {
35 uint32_t *entries;
36 int nb_frames;
37 int max_frames;
38 uint32_t fps_bits;
39 int64_t nb_frames_pos;
40 int64_t table_pos;
41 int64_t payload_start;
42 } PDVMuxContext;
43
44 2 static void pdv_deinit(AVFormatContext *s)
45 {
46 2 PDVMuxContext *pdv = s->priv_data;
47
48 2 av_freep(&pdv->entries);
49 2 }
50
51 2 static int pdv_get_fps(AVFormatContext *s, AVStream *st, uint32_t *fps_bits)
52 {
53 2 AVRational rate = st->avg_frame_rate;
54 2 const AVRational zero = { 0, 1 };
55
56
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (!rate.num || !rate.den)
57 rate = av_inv_q(st->time_base);
58
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (!rate.num || !rate.den) {
59 av_log(s, AV_LOG_ERROR, "A valid frame rate is required for PDV output.\n");
60 return AVERROR(EINVAL);
61 }
62
63
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if (av_cmp_q(rate, zero) <= 0) {
64 av_log(s, AV_LOG_ERROR, "Invalid frame rate for PDV output.\n");
65 return AVERROR(EINVAL);
66 }
67
68 2 *fps_bits = av_q2intfloat(rate);
69 2 return 0;
70 }
71
72 2 static int pdv_write_header(AVFormatContext *s)
73 {
74 2 PDVMuxContext *pdv = s->priv_data;
75 AVStream *st;
76 int ret;
77
78
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!(s->pb->seekable & AVIO_SEEKABLE_NORMAL)) {
79 av_log(s, AV_LOG_ERROR, "PDV muxer requires seekable output.\n");
80 return AVERROR(EINVAL);
81 }
82
83 2 st = s->streams[0];
84
85
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (st->codecpar->width > UINT16_MAX || st->codecpar->height > UINT16_MAX) {
86 av_log(s, AV_LOG_ERROR, "Output dimensions exceed PDV limits.\n");
87 return AVERROR(EINVAL);
88 }
89
90 2 ret = pdv_get_fps(s, st, &pdv->fps_bits);
91
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (ret < 0)
92 return ret;
93
94
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (pdv->max_frames < 1 || pdv->max_frames > PDV_MAX_FRAMES) {
95 av_log(s, AV_LOG_ERROR,
96 "The -max_frames option must be set to a value in [1, %u].\n",
97 PDV_MAX_FRAMES);
98 return AVERROR(EINVAL);
99 }
100
101 2 pdv->entries = av_malloc_array(pdv->max_frames + 1, sizeof(*pdv->entries));
102
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!pdv->entries)
103 return AVERROR(ENOMEM);
104
105 2 avio_write(s->pb, PDV_MAGIC, 16);
106 2 pdv->nb_frames_pos = avio_tell(s->pb);
107 2 avio_wl16(s->pb, 0);
108 2 avio_wl16(s->pb, 0);
109 2 avio_wl32(s->pb, pdv->fps_bits);
110 2 avio_wl16(s->pb, st->codecpar->width);
111 2 avio_wl16(s->pb, st->codecpar->height);
112
113 2 pdv->table_pos = avio_tell(s->pb);
114 2 ffio_fill(s->pb, 0, 4LL * (pdv->max_frames + 1));
115 2 pdv->payload_start = avio_tell(s->pb);
116
117
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
2 if (pdv->nb_frames_pos < 0 || pdv->table_pos < 0 || pdv->payload_start < 0)
118 return AVERROR(EIO);
119
120 2 return 0;
121 }
122
123 6 static int pdv_write_packet(AVFormatContext *s, AVPacket *pkt)
124 {
125 6 PDVMuxContext *pdv = s->priv_data;
126 6 int64_t offset = avio_tell(s->pb);
127 6 const uint32_t max_table_gap = 4U * pdv->max_frames;
128
129
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (offset < 0)
130 return AVERROR(EIO);
131 6 offset -= pdv->payload_start;
132
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (offset < 0)
133 return AVERROR(EIO);
134
135
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (pkt->size <= 0)
136 return AVERROR_INVALIDDATA;
137
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if (pdv->nb_frames >= pdv->max_frames) {
138 av_log(s, AV_LOG_ERROR, "Too many frames for PDV output.\n");
139 return AVERROR(EINVAL);
140 }
141
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (offset > PDV_MAX_OFFSET - max_table_gap ||
142
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 pkt->size > PDV_MAX_OFFSET - max_table_gap - offset) {
143 av_log(s, AV_LOG_ERROR, "PDV payload exceeds container limits.\n");
144 return AVERROR(EINVAL);
145 }
146
147 12 pdv->entries[pdv->nb_frames] = ((uint32_t)offset << 2) |
148
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 (pkt->flags & AV_PKT_FLAG_KEY ? 1 : 2);
149 6 avio_write(s->pb, pkt->data, pkt->size);
150
151 6 pdv->nb_frames++;
152
153 6 return 0;
154 }
155
156 2 static int pdv_write_trailer(AVFormatContext *s)
157 {
158 2 PDVMuxContext *pdv = s->priv_data;
159 2 int64_t payload_size = avio_tell(s->pb);
160 2 const uint32_t table_gap = 4U * (pdv->max_frames - pdv->nb_frames);
161 int64_t ret;
162
163
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (payload_size < 0)
164 return AVERROR(EIO);
165 2 payload_size -= pdv->payload_start;
166
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (payload_size < 0 || payload_size > PDV_MAX_OFFSET - table_gap)
167 return AVERROR(EINVAL);
168
169 2 pdv->entries[pdv->nb_frames] = (uint32_t)payload_size << 2;
170
171
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if ((ret = avio_seek(s->pb, pdv->nb_frames_pos, SEEK_SET)) < 0)
172 return ret;
173 2 avio_wl16(s->pb, pdv->nb_frames);
174
175
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if ((ret = avio_seek(s->pb, pdv->table_pos, SEEK_SET)) < 0)
176 return ret;
177
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
10 for (int i = 0; i <= pdv->nb_frames; i++) {
178 8 const uint32_t frame_off = (pdv->entries[i] >> 2) + table_gap;
179
180
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if (frame_off > PDV_MAX_OFFSET)
181 return AVERROR(EINVAL);
182 8 avio_wl32(s->pb, frame_off << 2 | (pdv->entries[i] & 3));
183 }
184
185
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if ((ret = avio_seek(s->pb, pdv->payload_start + payload_size, SEEK_SET)) < 0)
186 return ret;
187
188 2 return 0;
189 }
190
191 #define OFFSET(x) offsetof(PDVMuxContext, x)
192 #define ENC AV_OPT_FLAG_ENCODING_PARAM
193 static const AVOption options[] = {
194 { "max_frames", "maximum number of frames reserved in table (mandatory)",
195 OFFSET(max_frames), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, PDV_MAX_FRAMES, ENC },
196 { NULL },
197 };
198
199 static const AVClass pdv_muxer_class = {
200 .class_name = "PDV muxer",
201 .item_name = av_default_item_name,
202 .option = options,
203 .version = LIBAVUTIL_VERSION_INT,
204 .category = AV_CLASS_CATEGORY_MUXER,
205 };
206
207 const FFOutputFormat ff_pdv_muxer = {
208 .p.name = "pdv",
209 .p.long_name = NULL_IF_CONFIG_SMALL("PlayDate Video"),
210 .p.extensions = "pdv",
211 .p.priv_class = &pdv_muxer_class,
212 .p.audio_codec = AV_CODEC_ID_NONE,
213 .p.video_codec = AV_CODEC_ID_PDV,
214 .p.subtitle_codec = AV_CODEC_ID_NONE,
215 .priv_data_size = sizeof(PDVMuxContext),
216 .p.flags = AVFMT_NOTIMESTAMPS,
217 .flags_internal = FF_OFMT_FLAG_MAX_ONE_OF_EACH |
218 FF_OFMT_FLAG_ONLY_DEFAULT_CODECS,
219 .write_header = pdv_write_header,
220 .write_packet = pdv_write_packet,
221 .write_trailer = pdv_write_trailer,
222 .deinit = pdv_deinit,
223 };
224