FFmpeg
pdvenc.c
Go to the documentation of this file.
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;
38  uint32_t fps_bits;
43 
45 {
46  PDVMuxContext *pdv = s->priv_data;
47 
48  av_freep(&pdv->entries);
49 }
50 
51 static int pdv_get_fps(AVFormatContext *s, AVStream *st, uint32_t *fps_bits)
52 {
53  AVRational rate = st->avg_frame_rate;
54  const AVRational zero = { 0, 1 };
55 
56  if (!rate.num || !rate.den)
57  rate = av_inv_q(st->time_base);
58  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  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  *fps_bits = av_q2intfloat(rate);
69  return 0;
70 }
71 
73 {
74  PDVMuxContext *pdv = s->priv_data;
75  AVStream *st;
76  int ret;
77 
78  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  if (s->nb_streams != 1) {
84  av_log(s, AV_LOG_ERROR, "PDV muxer supports exactly one stream.\n");
85  return AVERROR(EINVAL);
86  }
87 
88  st = s->streams[0];
89 
90  if (st->codecpar->width <= 0 || st->codecpar->height <= 0) {
91  av_log(s, AV_LOG_ERROR, "Invalid output dimensions.\n");
92  return AVERROR(EINVAL);
93  }
94  if (st->codecpar->width > UINT16_MAX || st->codecpar->height > UINT16_MAX) {
95  av_log(s, AV_LOG_ERROR, "Output dimensions exceed PDV limits.\n");
96  return AVERROR(EINVAL);
97  }
98 
99  ret = pdv_get_fps(s, st, &pdv->fps_bits);
100  if (ret < 0)
101  return ret;
102 
103  if (pdv->max_frames < 1 || pdv->max_frames > PDV_MAX_FRAMES) {
105  "The -max_frames option must be set to a value in [1, %u].\n",
107  return AVERROR(EINVAL);
108  }
109 
110  pdv->entries = av_malloc_array(pdv->max_frames + 1, sizeof(*pdv->entries));
111  if (!pdv->entries)
112  return AVERROR(ENOMEM);
113 
114  avio_write(s->pb, PDV_MAGIC, 16);
115  pdv->nb_frames_pos = avio_tell(s->pb);
116  avio_wl16(s->pb, 0);
117  avio_wl16(s->pb, 0);
118  avio_wl32(s->pb, pdv->fps_bits);
119  avio_wl16(s->pb, st->codecpar->width);
120  avio_wl16(s->pb, st->codecpar->height);
121 
122  pdv->table_pos = avio_tell(s->pb);
123  ffio_fill(s->pb, 0, 4LL * (pdv->max_frames + 1));
124  pdv->payload_start = avio_tell(s->pb);
125 
126  if (pdv->nb_frames_pos < 0 || pdv->table_pos < 0 || pdv->payload_start < 0)
127  return AVERROR(EIO);
128 
129  return 0;
130 }
131 
133 {
134  PDVMuxContext *pdv = s->priv_data;
135  int64_t offset = avio_tell(s->pb);
136  const uint32_t max_table_gap = 4U * pdv->max_frames;
137 
138  if (offset < 0)
139  return AVERROR(EIO);
140  offset -= pdv->payload_start;
141  if (offset < 0)
142  return AVERROR(EIO);
143 
144  if (pkt->size <= 0)
145  return AVERROR_INVALIDDATA;
146  if (pdv->nb_frames >= pdv->max_frames) {
147  av_log(s, AV_LOG_ERROR, "Too many frames for PDV output.\n");
148  return AVERROR(EINVAL);
149  }
150  if (offset > PDV_MAX_OFFSET - max_table_gap ||
151  pkt->size > PDV_MAX_OFFSET - max_table_gap - offset) {
152  av_log(s, AV_LOG_ERROR, "PDV payload exceeds container limits.\n");
153  return AVERROR(EINVAL);
154  }
155 
156  pdv->entries[pdv->nb_frames] = ((uint32_t)offset << 2) |
157  (pkt->flags & AV_PKT_FLAG_KEY ? 1 : 2);
158  avio_write(s->pb, pkt->data, pkt->size);
159 
160  pdv->nb_frames++;
161 
162  return 0;
163 }
164 
166 {
167  PDVMuxContext *pdv = s->priv_data;
168  int64_t payload_size = avio_tell(s->pb);
169  const uint32_t table_gap = 4U * (pdv->max_frames - pdv->nb_frames);
170  int ret;
171 
172  if (payload_size < 0)
173  return AVERROR(EIO);
174  payload_size -= pdv->payload_start;
175  if (payload_size < 0 || payload_size > PDV_MAX_OFFSET - table_gap)
176  return AVERROR(EINVAL);
177 
178  pdv->entries[pdv->nb_frames] = (uint32_t)payload_size << 2;
179 
180  if ((ret = avio_seek(s->pb, pdv->nb_frames_pos, SEEK_SET)) < 0)
181  return ret;
182  avio_wl16(s->pb, pdv->nb_frames);
183 
184  if ((ret = avio_seek(s->pb, pdv->table_pos, SEEK_SET)) < 0)
185  return ret;
186  for (int i = 0; i <= pdv->nb_frames; i++) {
187  const uint32_t frame_off = (pdv->entries[i] >> 2) + table_gap;
188 
189  if (frame_off > PDV_MAX_OFFSET)
190  return AVERROR(EINVAL);
191  avio_wl32(s->pb, frame_off << 2 | (pdv->entries[i] & 3));
192  }
193 
194  if ((ret = avio_seek(s->pb, pdv->payload_start + payload_size, SEEK_SET)) < 0)
195  return ret;
196 
197  return 0;
198 }
199 
200 #define OFFSET(x) offsetof(PDVMuxContext, x)
201 #define ENC AV_OPT_FLAG_ENCODING_PARAM
202 static const AVOption options[] = {
203  { "max_frames", "maximum number of frames reserved in table (mandatory)",
204  OFFSET(max_frames), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, PDV_MAX_FRAMES, ENC },
205  { NULL },
206 };
207 
208 static const AVClass pdv_muxer_class = {
209  .class_name = "PDV muxer",
210  .item_name = av_default_item_name,
211  .option = options,
212  .version = LIBAVUTIL_VERSION_INT,
213  .category = AV_CLASS_CATEGORY_MUXER,
214 };
215 
217  .p.name = "pdv",
218  .p.long_name = NULL_IF_CONFIG_SMALL("PlayDate Video"),
219  .p.extensions = "pdv",
220  .p.priv_class = &pdv_muxer_class,
221  .p.audio_codec = AV_CODEC_ID_NONE,
222  .p.video_codec = AV_CODEC_ID_PDV,
223  .p.subtitle_codec = AV_CODEC_ID_NONE,
224  .priv_data_size = sizeof(PDVMuxContext),
225  .p.flags = AVFMT_NOTIMESTAMPS,
226  .flags_internal = FF_OFMT_FLAG_MAX_ONE_OF_EACH |
228  .write_header = pdv_write_header,
229  .write_packet = pdv_write_packet,
230  .write_trailer = pdv_write_trailer,
231  .deinit = pdv_deinit,
232 };
AVOutputFormat::name
const char * name
Definition: avformat.h:506
AVERROR
Filter the word “frame” indicates either a video frame or a group of audio as stored in an AVFrame structure Format for each input and each output the list of supported formats For video that means pixel format For audio that means channel sample they are references to shared objects When the negotiation mechanism computes the intersection of the formats supported at each end of a all references to both lists are replaced with a reference to the intersection And when a single format is eventually chosen for a link amongst the remaining all references to the list are updated That means that if a filter requires that its input and output have the same format amongst a supported all it has to do is use a reference to the same list of formats query_formats can leave some formats unset and return AVERROR(EAGAIN) to cause the negotiation mechanism toagain later. That can be used by filters with complex requirements to use the format negotiated on one link to set the formats supported on another. Frame references ownership and permissions
opt.h
pdv_deinit
static void pdv_deinit(AVFormatContext *s)
Definition: pdvenc.c:44
rational.h
AVFMT_NOTIMESTAMPS
#define AVFMT_NOTIMESTAMPS
Format does not need / have any timestamps.
Definition: avformat.h:478
int64_t
long long int64_t
Definition: coverity.c:34
AVPacket::data
uint8_t * data
Definition: packet.h:588
AVOption
AVOption.
Definition: opt.h:429
AVStream::avg_frame_rate
AVRational avg_frame_rate
Average framerate.
Definition: avformat.h:833
FF_OFMT_FLAG_ONLY_DEFAULT_CODECS
#define FF_OFMT_FLAG_ONLY_DEFAULT_CODECS
If this flag is set, then the only permitted audio/video/subtitle codec ids are AVOutputFormat....
Definition: mux.h:59
AV_PKT_FLAG_KEY
#define AV_PKT_FLAG_KEY
The packet contains a keyframe.
Definition: packet.h:643
FFOutputFormat::p
AVOutputFormat p
The public AVOutputFormat.
Definition: mux.h:65
avio_wl16
void avio_wl16(AVIOContext *s, unsigned int val)
Definition: aviobuf.c:440
av_q2intfloat
uint32_t av_q2intfloat(AVRational q)
Convert an AVRational to a IEEE 32-bit float expressed in fixed-point format.
Definition: rational.c:150
ff_pdv_muxer
const FFOutputFormat ff_pdv_muxer
Definition: pdvenc.c:216
PDV_MAGIC
#define PDV_MAGIC
Definition: pdvenc.c:30
pdv_get_fps
static int pdv_get_fps(AVFormatContext *s, AVStream *st, uint32_t *fps_bits)
Definition: pdvenc.c:51
avio_tell
static av_always_inline int64_t avio_tell(AVIOContext *s)
ftell() equivalent for AVIOContext.
Definition: avio.h:494
PDVMuxContext::nb_frames_pos
int64_t nb_frames_pos
Definition: pdvenc.c:39
AVRational::num
int num
Numerator.
Definition: rational.h:59
AV_LOG_ERROR
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition: log.h:210
PDVMuxContext::max_frames
int max_frames
Definition: pdvenc.c:37
s
#define s(width, name)
Definition: cbs_vp9.c:198
OFFSET
#define OFFSET(x)
Definition: pdvenc.c:200
pdv_write_packet
static int pdv_write_packet(AVFormatContext *s, AVPacket *pkt)
Definition: pdvenc.c:132
AVCodecParameters::width
int width
Video only.
Definition: codec_par.h:134
PDVMuxContext::nb_frames
int nb_frames
Definition: pdvenc.c:36
AVFormatContext
Format I/O context.
Definition: avformat.h:1263
AVStream::codecpar
AVCodecParameters * codecpar
Codec parameters associated with this stream.
Definition: avformat.h:767
LIBAVUTIL_VERSION_INT
#define LIBAVUTIL_VERSION_INT
Definition: version.h:85
AVClass
Describe the class of an AVClass context structure.
Definition: log.h:76
pdv_write_header
static int pdv_write_header(AVFormatContext *s)
Definition: pdvenc.c:72
AVStream::time_base
AVRational time_base
This is the fundamental unit of time (in seconds) in terms of which frame timestamps are represented.
Definition: avformat.h:783
NULL
#define NULL
Definition: coverity.c:32
PDVMuxContext::payload_start
int64_t payload_start
Definition: pdvenc.c:41
AVRational
Rational number (pair of numerator and denominator).
Definition: rational.h:58
av_default_item_name
const char * av_default_item_name(void *ptr)
Return the context name.
Definition: log.c:242
options
Definition: swscale.c:45
FFOutputFormat
Definition: mux.h:61
PDV_MAX_OFFSET
#define PDV_MAX_OFFSET
Definition: pdvenc.c:32
ffio_fill
void ffio_fill(AVIOContext *s, int b, int64_t count)
Definition: aviobuf.c:192
AV_CODEC_ID_PDV
@ AV_CODEC_ID_PDV
Definition: codec_id.h:324
AVPacket::size
int size
Definition: packet.h:589
NULL_IF_CONFIG_SMALL
#define NULL_IF_CONFIG_SMALL(x)
Return NULL if CONFIG_SMALL is true, otherwise the argument without modification.
Definition: internal.h:94
i
#define i(width, name, range_min, range_max)
Definition: cbs_h264.c:63
pdv_write_trailer
static int pdv_write_trailer(AVFormatContext *s)
Definition: pdvenc.c:165
pdv_muxer_class
static const AVClass pdv_muxer_class
Definition: pdvenc.c:208
avio_write
void avio_write(AVIOContext *s, const unsigned char *buf, int size)
Definition: aviobuf.c:206
PDV_MAX_FRAMES
#define PDV_MAX_FRAMES
Definition: pdvenc.c:31
avio_wl32
void avio_wl32(AVIOContext *s, unsigned int val)
Definition: aviobuf.c:360
offset
it s the only field you need to keep assuming you have a context There is some magic you don t need to care about around this just let it vf offset
Definition: writing_filters.txt:86
PDVMuxContext::entries
uint32_t * entries
Definition: pdvenc.c:35
AVPacket::flags
int flags
A combination of AV_PKT_FLAG values.
Definition: packet.h:594
PDVMuxContext::table_pos
int64_t table_pos
Definition: pdvenc.c:40
zero
static int zero(InterplayACMContext *s, unsigned ind, unsigned col)
Definition: interplayacm.c:121
PDVMuxContext::fps_bits
uint32_t fps_bits
Definition: pdvenc.c:38
AV_CODEC_ID_NONE
@ AV_CODEC_ID_NONE
Definition: codec_id.h:50
avio_internal.h
AVCodecParameters::height
int height
Definition: codec_par.h:135
av_malloc_array
#define av_malloc_array(a, b)
Definition: tableprint_vlc.h:32
FF_OFMT_FLAG_MAX_ONE_OF_EACH
#define FF_OFMT_FLAG_MAX_ONE_OF_EACH
If this flag is set, it indicates that for each codec type whose corresponding default codec (i....
Definition: mux.h:50
av_inv_q
static av_always_inline AVRational av_inv_q(AVRational q)
Invert a rational.
Definition: rational.h:159
av_cmp_q
static int av_cmp_q(AVRational a, AVRational b)
Compare two rationals.
Definition: rational.h:89
ret
ret
Definition: filter_design.txt:187
AVStream
Stream structure.
Definition: avformat.h:744
avio_seek
int64_t avio_seek(AVIOContext *s, int64_t offset, int whence)
fseek() equivalent for AVIOContext.
Definition: aviobuf.c:236
AVClass::class_name
const char * class_name
The name of the class; usually it is the same name as the context structure type to which the AVClass...
Definition: log.h:81
avformat.h
U
#define U(x)
Definition: vpx_arith.h:37
AV_CLASS_CATEGORY_MUXER
@ AV_CLASS_CATEGORY_MUXER
Definition: log.h:32
AVIO_SEEKABLE_NORMAL
#define AVIO_SEEKABLE_NORMAL
Seeking works like for a local file.
Definition: avio.h:41
AVRational::den
int den
Denominator.
Definition: rational.h:60
AV_OPT_TYPE_INT
@ AV_OPT_TYPE_INT
Underlying C type is int.
Definition: opt.h:259
Windows::Graphics::DirectX::Direct3D11::p
IDirect3DDxgiInterfaceAccess _COM_Outptr_ void ** p
Definition: vsrc_gfxcapture_winrt.hpp:53
ENC
#define ENC
Definition: pdvenc.c:201
mem.h
AVPacket
This structure stores compressed data.
Definition: packet.h:565
av_freep
#define av_freep(p)
Definition: tableprint_vlc.h:35
av_log
#define av_log(a,...)
Definition: tableprint_vlc.h:27
AVERROR_INVALIDDATA
#define AVERROR_INVALIDDATA
Invalid data found when processing input.
Definition: error.h:61
pkt
static AVPacket * pkt
Definition: demux_decode.c:55
PDVMuxContext
Definition: pdvenc.c:34
options
static const AVOption options[]
Definition: pdvenc.c:202
mux.h