• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * webp muxer
3  * Copyright (c) 2014 Michael Niedermayer
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/intreadwrite.h"
23 #include "libavutil/opt.h"
24 #include "avformat.h"
25 #include "internal.h"
26 
27 typedef struct WebpContext{
28     AVClass *class;
29     int frame_count;
30     AVPacket last_pkt;
31     int loop;
32     int wrote_webp_header;
33     int using_webp_anim_encoder;
34 } WebpContext;
35 
webp_write_header(AVFormatContext * s)36 static int webp_write_header(AVFormatContext *s)
37 {
38     AVStream *st;
39 
40     if (s->nb_streams != 1) {
41         av_log(s, AV_LOG_ERROR, "Only exactly 1 stream is supported\n");
42         return AVERROR(EINVAL);
43     }
44     st = s->streams[0];
45     if (st->codecpar->codec_id != AV_CODEC_ID_WEBP) {
46         av_log(s, AV_LOG_ERROR, "Only WebP is supported\n");
47         return AVERROR(EINVAL);
48     }
49     avpriv_set_pts_info(st, 24, 1, 1000);
50 
51     return 0;
52 }
53 
is_animated_webp_packet(AVPacket * pkt)54 static int is_animated_webp_packet(AVPacket *pkt)
55 {
56         int skip = 0;
57         unsigned flags = 0;
58 
59         if (pkt->size < 4)
60         return AVERROR_INVALIDDATA;
61         if (AV_RL32(pkt->data) == AV_RL32("RIFF"))
62             skip = 12;
63     // Safe to do this as a valid WebP bitstream is >=30 bytes.
64         if (pkt->size < skip + 4)
65         return AVERROR_INVALIDDATA;
66         if (AV_RL32(pkt->data + skip) == AV_RL32("VP8X")) {
67             flags |= pkt->data[skip + 4 + 4];
68         }
69 
70         if (flags & 2)  // ANIMATION_FLAG is on
71             return 1;
72     return 0;
73 }
74 
flush(AVFormatContext * s,int trailer,int64_t pts)75 static int flush(AVFormatContext *s, int trailer, int64_t pts)
76 {
77     WebpContext *w = s->priv_data;
78     AVStream *st = s->streams[0];
79 
80     if (w->last_pkt.size) {
81         int skip = 0;
82         unsigned flags = 0;
83         int vp8x = 0;
84 
85         if (AV_RL32(w->last_pkt.data) == AV_RL32("RIFF"))
86             skip = 12;
87 
88         if (AV_RL32(w->last_pkt.data + skip) == AV_RL32("VP8X")) {
89             flags |= w->last_pkt.data[skip + 4 + 4];
90             vp8x = 1;
91             skip += AV_RL32(w->last_pkt.data + skip + 4) + 8;
92         }
93 
94         if (!w->wrote_webp_header) {
95             avio_write(s->pb, "RIFF\0\0\0\0WEBP", 12);
96             w->wrote_webp_header = 1;
97             if (w->frame_count > 1)  // first non-empty packet
98                 w->frame_count = 1;  // so we don't count previous empty packets.
99         }
100 
101         if (w->frame_count == 1) {
102             if (!trailer) {
103                 vp8x = 1;
104                 flags |= 2 + 16;
105             }
106 
107             if (vp8x) {
108                 avio_write(s->pb, "VP8X", 4);
109                 avio_wl32(s->pb, 10);
110                 avio_w8(s->pb, flags);
111                 avio_wl24(s->pb, 0);
112                 avio_wl24(s->pb, st->codecpar->width - 1);
113                 avio_wl24(s->pb, st->codecpar->height - 1);
114             }
115             if (!trailer) {
116                 avio_write(s->pb, "ANIM", 4);
117                 avio_wl32(s->pb, 6);
118                 avio_wl32(s->pb, 0xFFFFFFFF);
119                 avio_wl16(s->pb, w->loop);
120             }
121         }
122 
123         if (w->frame_count > trailer) {
124             avio_write(s->pb, "ANMF", 4);
125             avio_wl32(s->pb, 16 + w->last_pkt.size - skip);
126             avio_wl24(s->pb, 0);
127             avio_wl24(s->pb, 0);
128             avio_wl24(s->pb, st->codecpar->width - 1);
129             avio_wl24(s->pb, st->codecpar->height - 1);
130             if (w->last_pkt.pts != AV_NOPTS_VALUE && pts != AV_NOPTS_VALUE) {
131                 avio_wl24(s->pb, pts - w->last_pkt.pts);
132             } else
133                 avio_wl24(s->pb, w->last_pkt.duration);
134             avio_w8(s->pb, 0);
135         }
136         avio_write(s->pb, w->last_pkt.data + skip, w->last_pkt.size - skip);
137         av_packet_unref(&w->last_pkt);
138     }
139 
140     return 0;
141 }
142 
webp_write_packet(AVFormatContext * s,AVPacket * pkt)143 static int webp_write_packet(AVFormatContext *s, AVPacket *pkt)
144 {
145     WebpContext *w = s->priv_data;
146     int ret;
147 
148     if (!pkt->size)
149         return 0;
150     ret = is_animated_webp_packet(pkt);
151     if (ret < 0)
152         return ret;
153     w->using_webp_anim_encoder |= ret;
154 
155     if (w->using_webp_anim_encoder) {
156         avio_write(s->pb, pkt->data, pkt->size);
157         w->wrote_webp_header = 1;  // for good measure
158     } else {
159         int ret;
160         if ((ret = flush(s, 0, pkt->pts)) < 0)
161             return ret;
162         av_packet_ref(&w->last_pkt, pkt);
163     }
164     ++w->frame_count;
165 
166     return 0;
167 }
168 
webp_write_trailer(AVFormatContext * s)169 static int webp_write_trailer(AVFormatContext *s)
170 {
171     unsigned filesize;
172     WebpContext *w = s->priv_data;
173 
174     if (w->using_webp_anim_encoder) {
175         if ((w->frame_count > 1) && w->loop) {  // Write loop count.
176             avio_seek(s->pb, 42, SEEK_SET);
177             avio_wl16(s->pb, w->loop);
178         }
179     } else {
180         int ret;
181         if ((ret = flush(s, 1, AV_NOPTS_VALUE)) < 0)
182             return ret;
183 
184         filesize = avio_tell(s->pb);
185         avio_seek(s->pb, 4, SEEK_SET);
186         avio_wl32(s->pb, filesize - 8);
187         // Note: without the following, avio only writes 8 bytes to the file.
188         avio_seek(s->pb, filesize, SEEK_SET);
189     }
190 
191     return 0;
192 }
193 
webp_deinit(AVFormatContext * s)194 static void webp_deinit(AVFormatContext *s)
195 {
196     WebpContext *w = s->priv_data;
197 
198     av_packet_unref(&w->last_pkt);
199 }
200 
201 #define OFFSET(x) offsetof(WebpContext, x)
202 #define ENC AV_OPT_FLAG_ENCODING_PARAM
203 static const AVOption options[] = {
204     { "loop", "Number of times to loop the output: 0 - infinite loop", OFFSET(loop),
205       AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 65535, ENC },
206     { NULL },
207 };
208 
209 static const AVClass webp_muxer_class = {
210     .class_name = "WebP muxer",
211     .item_name  = av_default_item_name,
212     .version    = LIBAVUTIL_VERSION_INT,
213     .option     = options,
214 };
215 AVOutputFormat ff_webp_muxer = {
216     .name           = "webp",
217     .long_name      = NULL_IF_CONFIG_SMALL("WebP"),
218     .extensions     = "webp",
219     .priv_data_size = sizeof(WebpContext),
220     .video_codec    = AV_CODEC_ID_WEBP,
221     .write_header   = webp_write_header,
222     .write_packet   = webp_write_packet,
223     .write_trailer  = webp_write_trailer,
224     .deinit         = webp_deinit,
225     .priv_class     = &webp_muxer_class,
226     .flags          = AVFMT_VARIABLE_FPS,
227 };
228