1cabdff1aSopenharmony_ci/*
2cabdff1aSopenharmony_ci * APNG muxer
3cabdff1aSopenharmony_ci * Copyright (c) 2015 Donny Yang
4cabdff1aSopenharmony_ci *
5cabdff1aSopenharmony_ci * first version by Donny Yang <work@kota.moe>
6cabdff1aSopenharmony_ci *
7cabdff1aSopenharmony_ci * This file is part of FFmpeg.
8cabdff1aSopenharmony_ci *
9cabdff1aSopenharmony_ci * FFmpeg is free software; you can redistribute it and/or
10cabdff1aSopenharmony_ci * modify it under the terms of the GNU Lesser General Public
11cabdff1aSopenharmony_ci * License as published by the Free Software Foundation; either
12cabdff1aSopenharmony_ci * version 2.1 of the License, or (at your option) any later version.
13cabdff1aSopenharmony_ci *
14cabdff1aSopenharmony_ci * FFmpeg is distributed in the hope that it will be useful,
15cabdff1aSopenharmony_ci * but WITHOUT ANY WARRANTY; without even the implied warranty of
16cabdff1aSopenharmony_ci * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17cabdff1aSopenharmony_ci * Lesser General Public License for more details.
18cabdff1aSopenharmony_ci *
19cabdff1aSopenharmony_ci * You should have received a copy of the GNU Lesser General Public
20cabdff1aSopenharmony_ci * License along with FFmpeg; if not, write to the Free Software
21cabdff1aSopenharmony_ci * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22cabdff1aSopenharmony_ci */
23cabdff1aSopenharmony_ci
24cabdff1aSopenharmony_ci#include "avformat.h"
25cabdff1aSopenharmony_ci#include "libavutil/avassert.h"
26cabdff1aSopenharmony_ci#include "libavutil/crc.h"
27cabdff1aSopenharmony_ci#include "libavutil/intreadwrite.h"
28cabdff1aSopenharmony_ci#include "libavutil/log.h"
29cabdff1aSopenharmony_ci#include "libavutil/opt.h"
30cabdff1aSopenharmony_ci#include "libavcodec/apng.h"
31cabdff1aSopenharmony_ci#include "libavcodec/png.h"
32cabdff1aSopenharmony_ci
33cabdff1aSopenharmony_citypedef struct APNGMuxContext {
34cabdff1aSopenharmony_ci    AVClass *class;
35cabdff1aSopenharmony_ci
36cabdff1aSopenharmony_ci    uint32_t plays;
37cabdff1aSopenharmony_ci    AVRational last_delay;
38cabdff1aSopenharmony_ci
39cabdff1aSopenharmony_ci    uint64_t acTL_offset;
40cabdff1aSopenharmony_ci    uint32_t frame_number;
41cabdff1aSopenharmony_ci
42cabdff1aSopenharmony_ci    AVPacket *prev_packet;
43cabdff1aSopenharmony_ci    AVRational prev_delay;
44cabdff1aSopenharmony_ci
45cabdff1aSopenharmony_ci    int framerate_warned;
46cabdff1aSopenharmony_ci
47cabdff1aSopenharmony_ci    uint8_t *extra_data;
48cabdff1aSopenharmony_ci    int extra_data_size;
49cabdff1aSopenharmony_ci} APNGMuxContext;
50cabdff1aSopenharmony_ci
51cabdff1aSopenharmony_cistatic const uint8_t *apng_find_chunk(uint32_t tag, const uint8_t *buf,
52cabdff1aSopenharmony_ci                                      size_t length)
53cabdff1aSopenharmony_ci{
54cabdff1aSopenharmony_ci    size_t b;
55cabdff1aSopenharmony_ci    for (b = 0; AV_RB32(buf + b) + 12ULL <= length - b; b += AV_RB32(buf + b) + 12ULL)
56cabdff1aSopenharmony_ci        if (AV_RB32(&buf[b + 4]) == tag)
57cabdff1aSopenharmony_ci            return &buf[b];
58cabdff1aSopenharmony_ci    return NULL;
59cabdff1aSopenharmony_ci}
60cabdff1aSopenharmony_ci
61cabdff1aSopenharmony_cistatic void apng_write_chunk(AVIOContext *io_context, uint32_t tag,
62cabdff1aSopenharmony_ci                             uint8_t *buf, size_t length)
63cabdff1aSopenharmony_ci{
64cabdff1aSopenharmony_ci    const AVCRC *crc_table = av_crc_get_table(AV_CRC_32_IEEE_LE);
65cabdff1aSopenharmony_ci    uint32_t crc = ~0U;
66cabdff1aSopenharmony_ci    uint8_t tagbuf[4];
67cabdff1aSopenharmony_ci
68cabdff1aSopenharmony_ci    av_assert0(crc_table);
69cabdff1aSopenharmony_ci
70cabdff1aSopenharmony_ci    avio_wb32(io_context, length);
71cabdff1aSopenharmony_ci    AV_WB32(tagbuf, tag);
72cabdff1aSopenharmony_ci    crc = av_crc(crc_table, crc, tagbuf, 4);
73cabdff1aSopenharmony_ci    avio_wb32(io_context, tag);
74cabdff1aSopenharmony_ci    if (length > 0) {
75cabdff1aSopenharmony_ci        crc = av_crc(crc_table, crc, buf, length);
76cabdff1aSopenharmony_ci        avio_write(io_context, buf, length);
77cabdff1aSopenharmony_ci    }
78cabdff1aSopenharmony_ci    avio_wb32(io_context, ~crc);
79cabdff1aSopenharmony_ci}
80cabdff1aSopenharmony_ci
81cabdff1aSopenharmony_cistatic int apng_write_header(AVFormatContext *format_context)
82cabdff1aSopenharmony_ci{
83cabdff1aSopenharmony_ci    APNGMuxContext *apng = format_context->priv_data;
84cabdff1aSopenharmony_ci    AVCodecParameters *par = format_context->streams[0]->codecpar;
85cabdff1aSopenharmony_ci
86cabdff1aSopenharmony_ci    if (format_context->nb_streams != 1 ||
87cabdff1aSopenharmony_ci        format_context->streams[0]->codecpar->codec_type != AVMEDIA_TYPE_VIDEO ||
88cabdff1aSopenharmony_ci        format_context->streams[0]->codecpar->codec_id   != AV_CODEC_ID_APNG) {
89cabdff1aSopenharmony_ci        av_log(format_context, AV_LOG_ERROR,
90cabdff1aSopenharmony_ci               "APNG muxer supports only a single video APNG stream.\n");
91cabdff1aSopenharmony_ci        return AVERROR(EINVAL);
92cabdff1aSopenharmony_ci    }
93cabdff1aSopenharmony_ci
94cabdff1aSopenharmony_ci    if (apng->last_delay.num > UINT16_MAX || apng->last_delay.den > UINT16_MAX) {
95cabdff1aSopenharmony_ci        av_reduce(&apng->last_delay.num, &apng->last_delay.den,
96cabdff1aSopenharmony_ci                  apng->last_delay.num, apng->last_delay.den, UINT16_MAX);
97cabdff1aSopenharmony_ci        av_log(format_context, AV_LOG_WARNING,
98cabdff1aSopenharmony_ci               "Last frame delay is too precise. Reducing to %d/%d (%f).\n",
99cabdff1aSopenharmony_ci               apng->last_delay.num, apng->last_delay.den, (double)apng->last_delay.num / apng->last_delay.den);
100cabdff1aSopenharmony_ci    }
101cabdff1aSopenharmony_ci
102cabdff1aSopenharmony_ci    avio_wb64(format_context->pb, PNGSIG);
103cabdff1aSopenharmony_ci    // Remaining headers are written when they are copied from the encoder
104cabdff1aSopenharmony_ci
105cabdff1aSopenharmony_ci    if (par->extradata_size) {
106cabdff1aSopenharmony_ci        apng->extra_data = av_mallocz(par->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
107cabdff1aSopenharmony_ci        if (!apng->extra_data)
108cabdff1aSopenharmony_ci            return AVERROR(ENOMEM);
109cabdff1aSopenharmony_ci        apng->extra_data_size = par->extradata_size;
110cabdff1aSopenharmony_ci        memcpy(apng->extra_data, par->extradata, par->extradata_size);
111cabdff1aSopenharmony_ci    }
112cabdff1aSopenharmony_ci
113cabdff1aSopenharmony_ci    return 0;
114cabdff1aSopenharmony_ci}
115cabdff1aSopenharmony_ci
116cabdff1aSopenharmony_cistatic int flush_packet(AVFormatContext *format_context, AVPacket *packet)
117cabdff1aSopenharmony_ci{
118cabdff1aSopenharmony_ci    APNGMuxContext *apng = format_context->priv_data;
119cabdff1aSopenharmony_ci    AVIOContext *io_context = format_context->pb;
120cabdff1aSopenharmony_ci    AVStream *codec_stream = format_context->streams[0];
121cabdff1aSopenharmony_ci    uint8_t *side_data = NULL;
122cabdff1aSopenharmony_ci    size_t side_data_size;
123cabdff1aSopenharmony_ci
124cabdff1aSopenharmony_ci    av_assert0(apng->prev_packet);
125cabdff1aSopenharmony_ci
126cabdff1aSopenharmony_ci    side_data = av_packet_get_side_data(apng->prev_packet, AV_PKT_DATA_NEW_EXTRADATA, &side_data_size);
127cabdff1aSopenharmony_ci
128cabdff1aSopenharmony_ci    if (side_data_size) {
129cabdff1aSopenharmony_ci        av_freep(&apng->extra_data);
130cabdff1aSopenharmony_ci        apng->extra_data = av_mallocz(side_data_size + AV_INPUT_BUFFER_PADDING_SIZE);
131cabdff1aSopenharmony_ci        if (!apng->extra_data)
132cabdff1aSopenharmony_ci            return AVERROR(ENOMEM);
133cabdff1aSopenharmony_ci        apng->extra_data_size = side_data_size;
134cabdff1aSopenharmony_ci        memcpy(apng->extra_data, side_data, apng->extra_data_size);
135cabdff1aSopenharmony_ci    }
136cabdff1aSopenharmony_ci
137cabdff1aSopenharmony_ci    if (apng->frame_number == 0 && !packet) {
138cabdff1aSopenharmony_ci        const uint8_t *existing_acTL_chunk;
139cabdff1aSopenharmony_ci        const uint8_t *existing_fcTL_chunk;
140cabdff1aSopenharmony_ci
141cabdff1aSopenharmony_ci        av_log(format_context, AV_LOG_INFO, "Only a single frame so saving as a normal PNG.\n");
142cabdff1aSopenharmony_ci
143cabdff1aSopenharmony_ci        // Write normal PNG headers without acTL chunk
144cabdff1aSopenharmony_ci        existing_acTL_chunk = apng_find_chunk(MKBETAG('a', 'c', 'T', 'L'), apng->extra_data, apng->extra_data_size);
145cabdff1aSopenharmony_ci        if (existing_acTL_chunk) {
146cabdff1aSopenharmony_ci            const uint8_t *chunk_after_acTL = existing_acTL_chunk + AV_RB32(existing_acTL_chunk) + 12;
147cabdff1aSopenharmony_ci            avio_write(io_context, apng->extra_data, existing_acTL_chunk - apng->extra_data);
148cabdff1aSopenharmony_ci            avio_write(io_context, chunk_after_acTL, apng->extra_data + apng->extra_data_size - chunk_after_acTL);
149cabdff1aSopenharmony_ci        } else {
150cabdff1aSopenharmony_ci            avio_write(io_context, apng->extra_data, apng->extra_data_size);
151cabdff1aSopenharmony_ci        }
152cabdff1aSopenharmony_ci
153cabdff1aSopenharmony_ci        // Write frame data without fcTL chunk
154cabdff1aSopenharmony_ci        existing_fcTL_chunk = apng_find_chunk(MKBETAG('f', 'c', 'T', 'L'), apng->prev_packet->data, apng->prev_packet->size);
155cabdff1aSopenharmony_ci        if (existing_fcTL_chunk) {
156cabdff1aSopenharmony_ci            const uint8_t *chunk_after_fcTL = existing_fcTL_chunk + AV_RB32(existing_fcTL_chunk) + 12;
157cabdff1aSopenharmony_ci            avio_write(io_context, apng->prev_packet->data, existing_fcTL_chunk - apng->prev_packet->data);
158cabdff1aSopenharmony_ci            avio_write(io_context, chunk_after_fcTL, apng->prev_packet->data + apng->prev_packet->size - chunk_after_fcTL);
159cabdff1aSopenharmony_ci        } else {
160cabdff1aSopenharmony_ci            avio_write(io_context, apng->prev_packet->data, apng->prev_packet->size);
161cabdff1aSopenharmony_ci        }
162cabdff1aSopenharmony_ci    } else {
163cabdff1aSopenharmony_ci        const uint8_t *data, *data_end;
164cabdff1aSopenharmony_ci        const uint8_t *existing_fcTL_chunk;
165cabdff1aSopenharmony_ci
166cabdff1aSopenharmony_ci        if (apng->frame_number == 0) {
167cabdff1aSopenharmony_ci            const uint8_t *existing_acTL_chunk;
168cabdff1aSopenharmony_ci
169cabdff1aSopenharmony_ci            // Write normal PNG headers
170cabdff1aSopenharmony_ci            avio_write(io_context, apng->extra_data, apng->extra_data_size);
171cabdff1aSopenharmony_ci
172cabdff1aSopenharmony_ci            existing_acTL_chunk = apng_find_chunk(MKBETAG('a', 'c', 'T', 'L'), apng->extra_data, apng->extra_data_size);
173cabdff1aSopenharmony_ci            if (!existing_acTL_chunk) {
174cabdff1aSopenharmony_ci                uint8_t buf[8];
175cabdff1aSopenharmony_ci                // Write animation control header
176cabdff1aSopenharmony_ci                apng->acTL_offset = avio_tell(io_context);
177cabdff1aSopenharmony_ci                AV_WB32(buf, UINT_MAX); // number of frames (filled in later)
178cabdff1aSopenharmony_ci                AV_WB32(buf + 4, apng->plays);
179cabdff1aSopenharmony_ci                apng_write_chunk(io_context, MKBETAG('a', 'c', 'T', 'L'), buf, 8);
180cabdff1aSopenharmony_ci            }
181cabdff1aSopenharmony_ci        }
182cabdff1aSopenharmony_ci
183cabdff1aSopenharmony_ci        data     = apng->prev_packet->data;
184cabdff1aSopenharmony_ci        data_end = data + apng->prev_packet->size;
185cabdff1aSopenharmony_ci        existing_fcTL_chunk = apng_find_chunk(MKBETAG('f', 'c', 'T', 'L'), apng->prev_packet->data, apng->prev_packet->size);
186cabdff1aSopenharmony_ci        if (existing_fcTL_chunk) {
187cabdff1aSopenharmony_ci            AVRational delay;
188cabdff1aSopenharmony_ci
189cabdff1aSopenharmony_ci            if (AV_RB32(existing_fcTL_chunk) != APNG_FCTL_CHUNK_SIZE)
190cabdff1aSopenharmony_ci                return AVERROR_INVALIDDATA;
191cabdff1aSopenharmony_ci
192cabdff1aSopenharmony_ci            existing_fcTL_chunk += 8;
193cabdff1aSopenharmony_ci            delay.num = AV_RB16(existing_fcTL_chunk + 20);
194cabdff1aSopenharmony_ci            delay.den = AV_RB16(existing_fcTL_chunk + 22);
195cabdff1aSopenharmony_ci
196cabdff1aSopenharmony_ci            if (delay.num == 0 && delay.den == 0) {
197cabdff1aSopenharmony_ci                uint8_t new_fcTL_chunk[APNG_FCTL_CHUNK_SIZE];
198cabdff1aSopenharmony_ci
199cabdff1aSopenharmony_ci                if (packet) {
200cabdff1aSopenharmony_ci                    int64_t delay_num_raw = (packet->dts - apng->prev_packet->dts) * codec_stream->time_base.num;
201cabdff1aSopenharmony_ci                    int64_t delay_den_raw = codec_stream->time_base.den;
202cabdff1aSopenharmony_ci                    if (!av_reduce(&delay.num, &delay.den, delay_num_raw, delay_den_raw, UINT16_MAX) &&
203cabdff1aSopenharmony_ci                        !apng->framerate_warned) {
204cabdff1aSopenharmony_ci                        av_log(format_context, AV_LOG_WARNING,
205cabdff1aSopenharmony_ci                               "Frame rate is too high or specified too precisely. Unable to copy losslessly.\n");
206cabdff1aSopenharmony_ci                        apng->framerate_warned = 1;
207cabdff1aSopenharmony_ci                    }
208cabdff1aSopenharmony_ci                } else if (apng->last_delay.num > 0) {
209cabdff1aSopenharmony_ci                    delay = apng->last_delay;
210cabdff1aSopenharmony_ci                } else {
211cabdff1aSopenharmony_ci                    delay = apng->prev_delay;
212cabdff1aSopenharmony_ci                }
213cabdff1aSopenharmony_ci
214cabdff1aSopenharmony_ci                avio_write(io_context, data, (existing_fcTL_chunk - 8) - data);
215cabdff1aSopenharmony_ci                data = existing_fcTL_chunk + APNG_FCTL_CHUNK_SIZE + 4 /* CRC-32 */;
216cabdff1aSopenharmony_ci                // Update frame control header with new delay
217cabdff1aSopenharmony_ci                memcpy(new_fcTL_chunk, existing_fcTL_chunk, sizeof(new_fcTL_chunk));
218cabdff1aSopenharmony_ci                AV_WB16(new_fcTL_chunk + 20, delay.num);
219cabdff1aSopenharmony_ci                AV_WB16(new_fcTL_chunk + 22, delay.den);
220cabdff1aSopenharmony_ci                apng_write_chunk(io_context, MKBETAG('f', 'c', 'T', 'L'),
221cabdff1aSopenharmony_ci                                 new_fcTL_chunk, sizeof(new_fcTL_chunk));
222cabdff1aSopenharmony_ci            }
223cabdff1aSopenharmony_ci            apng->prev_delay = delay;
224cabdff1aSopenharmony_ci        }
225cabdff1aSopenharmony_ci
226cabdff1aSopenharmony_ci        // Write frame data
227cabdff1aSopenharmony_ci        avio_write(io_context, data, data_end - data);
228cabdff1aSopenharmony_ci    }
229cabdff1aSopenharmony_ci    ++apng->frame_number;
230cabdff1aSopenharmony_ci
231cabdff1aSopenharmony_ci    av_packet_unref(apng->prev_packet);
232cabdff1aSopenharmony_ci    if (packet)
233cabdff1aSopenharmony_ci        av_packet_ref(apng->prev_packet, packet);
234cabdff1aSopenharmony_ci    return 0;
235cabdff1aSopenharmony_ci}
236cabdff1aSopenharmony_ci
237cabdff1aSopenharmony_cistatic int apng_write_packet(AVFormatContext *format_context, AVPacket *packet)
238cabdff1aSopenharmony_ci{
239cabdff1aSopenharmony_ci    APNGMuxContext *apng = format_context->priv_data;
240cabdff1aSopenharmony_ci    int ret;
241cabdff1aSopenharmony_ci
242cabdff1aSopenharmony_ci    if (!apng->prev_packet) {
243cabdff1aSopenharmony_ci        apng->prev_packet = av_packet_alloc();
244cabdff1aSopenharmony_ci        if (!apng->prev_packet)
245cabdff1aSopenharmony_ci            return AVERROR(ENOMEM);
246cabdff1aSopenharmony_ci
247cabdff1aSopenharmony_ci        av_packet_ref(apng->prev_packet, packet);
248cabdff1aSopenharmony_ci    } else {
249cabdff1aSopenharmony_ci        ret = flush_packet(format_context, packet);
250cabdff1aSopenharmony_ci        if (ret < 0)
251cabdff1aSopenharmony_ci            return ret;
252cabdff1aSopenharmony_ci    }
253cabdff1aSopenharmony_ci
254cabdff1aSopenharmony_ci    return 0;
255cabdff1aSopenharmony_ci}
256cabdff1aSopenharmony_ci
257cabdff1aSopenharmony_cistatic int apng_write_trailer(AVFormatContext *format_context)
258cabdff1aSopenharmony_ci{
259cabdff1aSopenharmony_ci    APNGMuxContext *apng = format_context->priv_data;
260cabdff1aSopenharmony_ci    AVIOContext *io_context = format_context->pb;
261cabdff1aSopenharmony_ci    uint8_t buf[8];
262cabdff1aSopenharmony_ci    int ret;
263cabdff1aSopenharmony_ci
264cabdff1aSopenharmony_ci    if (apng->prev_packet) {
265cabdff1aSopenharmony_ci        ret = flush_packet(format_context, NULL);
266cabdff1aSopenharmony_ci        if (ret < 0)
267cabdff1aSopenharmony_ci            return ret;
268cabdff1aSopenharmony_ci    }
269cabdff1aSopenharmony_ci
270cabdff1aSopenharmony_ci    apng_write_chunk(io_context, MKBETAG('I', 'E', 'N', 'D'), NULL, 0);
271cabdff1aSopenharmony_ci
272cabdff1aSopenharmony_ci    if (apng->acTL_offset && (io_context->seekable & AVIO_SEEKABLE_NORMAL)) {
273cabdff1aSopenharmony_ci        avio_seek(io_context, apng->acTL_offset, SEEK_SET);
274cabdff1aSopenharmony_ci
275cabdff1aSopenharmony_ci        AV_WB32(buf, apng->frame_number);
276cabdff1aSopenharmony_ci        AV_WB32(buf + 4, apng->plays);
277cabdff1aSopenharmony_ci        apng_write_chunk(io_context, MKBETAG('a', 'c', 'T', 'L'), buf, 8);
278cabdff1aSopenharmony_ci    }
279cabdff1aSopenharmony_ci
280cabdff1aSopenharmony_ci    return 0;
281cabdff1aSopenharmony_ci}
282cabdff1aSopenharmony_ci
283cabdff1aSopenharmony_cistatic void apng_deinit(AVFormatContext *s)
284cabdff1aSopenharmony_ci{
285cabdff1aSopenharmony_ci    APNGMuxContext *apng = s->priv_data;
286cabdff1aSopenharmony_ci
287cabdff1aSopenharmony_ci    av_packet_free(&apng->prev_packet);
288cabdff1aSopenharmony_ci    av_freep(&apng->extra_data);
289cabdff1aSopenharmony_ci    apng->extra_data_size = 0;
290cabdff1aSopenharmony_ci}
291cabdff1aSopenharmony_ci
292cabdff1aSopenharmony_ci#define OFFSET(x) offsetof(APNGMuxContext, x)
293cabdff1aSopenharmony_ci#define ENC AV_OPT_FLAG_ENCODING_PARAM
294cabdff1aSopenharmony_cistatic const AVOption options[] = {
295cabdff1aSopenharmony_ci    { "plays", "Number of times to play the output: 0 - infinite loop, 1 - no loop", OFFSET(plays),
296cabdff1aSopenharmony_ci      AV_OPT_TYPE_INT, { .i64 = 1 }, 0, UINT16_MAX, ENC },
297cabdff1aSopenharmony_ci    { "final_delay", "Force delay after the last frame", OFFSET(last_delay),
298cabdff1aSopenharmony_ci      AV_OPT_TYPE_RATIONAL, { .dbl = 0 }, 0, UINT16_MAX, ENC },
299cabdff1aSopenharmony_ci    { NULL },
300cabdff1aSopenharmony_ci};
301cabdff1aSopenharmony_ci
302cabdff1aSopenharmony_cistatic const AVClass apng_muxer_class = {
303cabdff1aSopenharmony_ci    .class_name = "APNG muxer",
304cabdff1aSopenharmony_ci    .item_name  = av_default_item_name,
305cabdff1aSopenharmony_ci    .version    = LIBAVUTIL_VERSION_INT,
306cabdff1aSopenharmony_ci    .option     = options,
307cabdff1aSopenharmony_ci};
308cabdff1aSopenharmony_ci
309cabdff1aSopenharmony_ciconst AVOutputFormat ff_apng_muxer = {
310cabdff1aSopenharmony_ci    .name           = "apng",
311cabdff1aSopenharmony_ci    .long_name      = NULL_IF_CONFIG_SMALL("Animated Portable Network Graphics"),
312cabdff1aSopenharmony_ci    .mime_type      = "image/png",
313cabdff1aSopenharmony_ci    .extensions     = "apng",
314cabdff1aSopenharmony_ci    .priv_data_size = sizeof(APNGMuxContext),
315cabdff1aSopenharmony_ci    .audio_codec    = AV_CODEC_ID_NONE,
316cabdff1aSopenharmony_ci    .video_codec    = AV_CODEC_ID_APNG,
317cabdff1aSopenharmony_ci    .write_header   = apng_write_header,
318cabdff1aSopenharmony_ci    .write_packet   = apng_write_packet,
319cabdff1aSopenharmony_ci    .write_trailer  = apng_write_trailer,
320cabdff1aSopenharmony_ci    .deinit         = apng_deinit,
321cabdff1aSopenharmony_ci    .priv_class     = &apng_muxer_class,
322cabdff1aSopenharmony_ci    .flags          = AVFMT_VARIABLE_FPS,
323cabdff1aSopenharmony_ci};
324