]> git.sesse.net Git - cubemap/blob - ffmpeg_metacube_hack.c
Better HOWTO in the README.
[cubemap] / ffmpeg_metacube_hack.c
1 // A shared library that you can LD_PRELOAD into an FFmpeg-using process
2 // (most likely ffmpeg(1)) to make it output Metacube. This is obviously
3 // pretty hacky, since it needs to override various FFmpeg functions,
4 // so there are few guarantees here. It is written in C to avoid pulling
5 // in the C++ runtime into FFmpeg's C-only world.
6 //
7 // You should not link to this library. It does not have ABI stability.
8 // It is licensed the same as the rest of Cubemap.
9
10 #define _GNU_SOURCE
11 #include <assert.h>
12 #include <dlfcn.h>
13 #include <libavformat/avformat.h>
14 #include <libavformat/avio.h>
15 #include <libavutil/avassert.h>
16 #include <libavutil/crc.h>
17 #include <libavutil/error.h>
18 #include <libavutil/intreadwrite.h>
19 #include <limits.h>
20 #include <pthread.h>
21 #include <stdbool.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <sys/signal.h>
25 #include "metacube2.h"
26
27 static pthread_once_t metacube2_crc_once_control = PTHREAD_ONCE_INIT;
28 static AVCRC metacube2_crc_table[257];
29
30 // We need to store some extra information for each context,
31 // so this is where we do it. The “opaque” field in the AVIOContext
32 // points to this struct, but we can also look it up by the AVIOContext
33 // pointer by scanning through the singly linked list starting with
34 // first_extra_data.
35 struct ContextExtraData {
36         struct ContextExtraData *next;  // NULL for last entry.
37         AVIOContext *ctx;       // The context we are associating data with.
38         bool seen_sync_point;
39         void *old_opaque;
40         int (*old_write_data_type)(void *opaque, uint8_t * buf,
41                                    int buf_size,
42                                    enum AVIODataMarkerType type,
43                                    int64_t time);
44
45         // Used during avformat_write_header(), to combine adjacent header blocks
46         // into one (in particular, the MP4 mux has an unneeded avio_flush()
47         // halfway throughout).
48         bool in_header;
49         int64_t header_first_time;
50         uint8_t *buffered_header;
51         size_t buffered_header_bytes;
52 };
53 static struct ContextExtraData *first_extra_data = NULL;
54
55 // Look up ContextExtraData for the given context, creating a new one if needed.
56 static struct ContextExtraData *get_extra_data(AVIOContext * ctx)
57 {
58         for (struct ContextExtraData * ed = first_extra_data; ed != NULL;
59              ed = ed->next) {
60                 if (ed->ctx == ctx) {
61                         return ed;
62                 }
63         }
64         struct ContextExtraData *ed = (struct ContextExtraData *)
65             malloc(sizeof(struct ContextExtraData));
66         ed->ctx = ctx;
67         ed->seen_sync_point = false;
68         ed->old_write_data_type = NULL;
69         ed->in_header = false;
70         ed->buffered_header = NULL;
71         ed->buffered_header_bytes = 0;
72
73         ed->next = first_extra_data;
74         first_extra_data = ed;
75         return ed;
76 }
77
78 // Clear ContextExtraData for a given context (presumably before it's freed).
79 static void free_extra_data(AVIOContext * ctx)
80 {
81         if (first_extra_data == NULL) {
82                 return;
83         }
84         if (first_extra_data->ctx == ctx) {
85                 struct ContextExtraData *to_free = first_extra_data;
86                 first_extra_data = to_free->next;
87                 free(to_free);
88                 return;
89         }
90         for (struct ContextExtraData * ed = first_extra_data; ed != NULL;
91              ed = ed->next) {
92                 if (ed->next != NULL && ed->next->ctx == ctx) {
93                         struct ContextExtraData *to_free = ed->next;
94                         ed->next = to_free->next;
95                         free(to_free);
96                         return;
97                 }
98         }
99 }
100
101 static void metacube2_crc_init_table_once(void)
102 {
103         av_assert0(av_crc_init
104                    (metacube2_crc_table, 0, 16, 0x8fdb,
105                     sizeof(metacube2_crc_table)) >= 0);
106 }
107
108 static uint16_t metacube2_compute_crc_ff(const struct
109                                          metacube2_block_header *hdr)
110 {
111         static const int data_len = sizeof(hdr->size) + sizeof(hdr->flags);
112         const uint8_t *data = (uint8_t *) & hdr->size;
113         uint16_t crc;
114
115         pthread_once(&metacube2_crc_once_control,
116                      metacube2_crc_init_table_once);
117
118         // Metacube2 specifies a CRC start of 0x1234, but its pycrc-derived CRC
119         // includes a finalization step that is done somewhat differently in av_crc().
120         // 0x1234 alone sent through that finalization becomes 0x394a, and then we
121         // need a byte-swap of the CRC value (both on input and output) to account for
122         // differing conventions.
123         crc = av_crc(metacube2_crc_table, 0x4a39, data, data_len);
124         return av_bswap16(crc);
125 }
126
127 static int write_packet(void *opaque, uint8_t * buf, int buf_size,
128                         enum AVIODataMarkerType type, int64_t time)
129 {
130         if (buf_size < 0) {
131                 return AVERROR(EINVAL);
132         }
133
134         struct ContextExtraData *ed = (struct ContextExtraData *) opaque;
135
136         if (ed->in_header) {
137                 if (ed->buffered_header_bytes == 0) {
138                         ed->header_first_time = time;
139                 }
140
141                 size_t new_buffered_header_bytes =
142                     ed->buffered_header_bytes + buf_size;
143                 if (new_buffered_header_bytes < ed->buffered_header_bytes) {
144                         return AVERROR(ENOMEM);
145                 }
146                 ed->buffered_header =
147                     (uint8_t *) realloc(ed->buffered_header,
148                                         new_buffered_header_bytes);
149                 if (ed->buffered_header == NULL) {
150                         return AVERROR(ENOMEM);
151                 }
152
153                 memcpy(ed->buffered_header + ed->buffered_header_bytes,
154                        buf, buf_size);
155                 ed->buffered_header_bytes = new_buffered_header_bytes;
156                 return buf_size;
157         }
158         // Find block size if we add a Metacube2 header in front.
159         unsigned new_buf_size =
160             (unsigned) buf_size + sizeof(struct metacube2_block_header);
161         if (new_buf_size < (unsigned) buf_size
162             || new_buf_size > (unsigned) INT_MAX) {
163                 // Overflow.
164                 return -1;
165         }
166         // Fill in the header.
167         struct metacube2_block_header hdr;
168         int flags = 0;
169         if (type == AVIO_DATA_MARKER_SYNC_POINT)
170                 ed->seen_sync_point = 1;
171         else if (type == AVIO_DATA_MARKER_HEADER)
172                 // NOTE: If there are multiple blocks marked METACUBE_FLAGS_HEADER,
173                 // only the last one will count. This may become a problem if the
174                 // mux flushes halfway through the stream header; if so, we would
175                 // need to keep track of and concatenate the different parts.
176                 flags |= METACUBE_FLAGS_HEADER;
177         else if (ed->seen_sync_point)
178                 flags |= METACUBE_FLAGS_NOT_SUITABLE_FOR_STREAM_START;
179
180         memcpy(hdr.sync, METACUBE2_SYNC, sizeof(hdr.sync));
181         AV_WB32(&hdr.size, buf_size);
182         AV_WB16(&hdr.flags, flags);
183         AV_WB16(&hdr.csum, metacube2_compute_crc_ff(&hdr));
184
185         int ret;
186         ed->ctx->opaque = ed->old_opaque;
187         if (new_buf_size < ed->ctx->max_packet_size) {
188                 // Combine the two packets. (This is what we normally want.)
189                 // So we allocate a new block, with a Metacube2 header in front.
190                 uint8_t *buf_with_hdr = (uint8_t *) malloc(new_buf_size);
191                 if (buf_with_hdr == NULL) {
192                         return AVERROR(ENOMEM);
193                 }
194                 memcpy(buf_with_hdr, &hdr, sizeof(hdr));
195                 memcpy(buf_with_hdr + sizeof(hdr), buf, buf_size);
196                 if (ed->old_write_data_type) {
197                         ret =
198                             ed->old_write_data_type(ed->old_opaque,
199                                                     buf_with_hdr,
200                                                     new_buf_size, type,
201                                                     time);
202                 } else {
203                         ret =
204                             ed->ctx->write_packet(ed->old_opaque,
205                                                   buf_with_hdr,
206                                                   new_buf_size);
207                 }
208                 free(buf_with_hdr);
209
210                 if (ret >= 0
211                     && ret >= sizeof(struct metacube2_block_header)) {
212                         ret -= sizeof(struct metacube2_block_header);
213                 }
214         } else {
215                 // Send separately. This will split a header block if it's really large,
216                 // which we don't want, but that's how things are.
217                 if (ed->old_write_data_type) {
218                         ret =
219                             ed->old_write_data_type(ed->old_opaque,
220                                                     (uint8_t *) & hdr,
221                                                     sizeof(hdr), type,
222                                                     time);
223                 } else {
224                         ret =
225                             ed->ctx->write_packet(ed->old_opaque,
226                                                   (uint8_t *) & hdr,
227                                                   sizeof(hdr));
228                 }
229                 if (ret < 0) {
230                         return ret;
231                 }
232                 if (ret != sizeof(hdr)) {
233                         return AVERROR(EIO);
234                 }
235
236                 if (ed->old_write_data_type) {
237                         ret =
238                             ed->old_write_data_type(ed->old_opaque, buf,
239                                                     buf_size, type, time);
240                 } else {
241                         ret =
242                             ed->ctx->write_packet(ed->old_opaque, buf,
243                                                   buf_size);
244                 }
245         }
246
247         ed->ctx->opaque = ed;
248         return ret;
249 }
250
251 // Actual hooked functions below.
252
253 int avformat_write_header(AVFormatContext * ctx, AVDictionary ** options)
254 {
255         metacube2_crc_init_table_once();
256
257         struct ContextExtraData *ed = get_extra_data(ctx->pb);
258         ed->old_opaque = ctx->pb->opaque;
259         ed->old_write_data_type = ctx->pb->write_data_type;
260         ctx->pb->opaque = ed;
261         ctx->pb->write_data_type = write_packet;
262         ctx->pb->seek = NULL;
263         ctx->pb->seekable = 0;
264         if (ed->old_write_data_type == NULL) {
265                 ctx->pb->ignore_boundary_point = 1;
266         }
267
268         int (*original_func)(AVFormatContext * ctx,
269                              AVDictionary ** options);
270         original_func = dlsym(RTLD_NEXT, "avformat_write_header");
271
272         ed->in_header = true;
273         int ret = (*original_func) (ctx, options);
274         ed->in_header = false;
275
276         if (ed->buffered_header_bytes > 0) {
277                 int hdr_ret = write_packet(ed, ed->buffered_header,
278                                            ed->buffered_header_bytes,
279                                            AVIO_DATA_MARKER_HEADER,
280                                            ed->header_first_time);
281                 free(ed->buffered_header);
282                 ed->buffered_header = NULL;
283
284                 if (hdr_ret >= 0 && hdr_ret < ed->buffered_header_bytes) {
285                         hdr_ret = AVERROR(EIO);
286                 }
287                 ed->buffered_header_bytes = 0;
288                 if (hdr_ret < 0) {
289                         return hdr_ret;
290                 }
291         }
292
293         return ret;
294 }
295
296 void avformat_free_context(AVFormatContext * ctx)
297 {
298         if (ctx == NULL) {
299                 return;
300         }
301         free_extra_data(ctx->pb);
302
303         void (*original_func)(AVFormatContext * ctx);
304         original_func = dlsym(RTLD_NEXT, "avformat_free_context");
305         return (*original_func) (ctx);
306 }
307
308 // Hook so that we can restore opaque instead of ours being freed by the caller.
309 int avio_close(AVIOContext * ctx)
310 {
311         if (ctx == NULL) {
312                 return 0;
313         }
314         struct ContextExtraData ed = *get_extra_data(ctx);
315         free_extra_data(ctx);
316         ctx->opaque = ed.old_opaque;
317
318         int (*original_func)(AVIOContext * ctx);
319         original_func = dlsym(RTLD_NEXT, "avio_close");
320         return (*original_func) (ctx);
321 }
322
323 // Identical to FFmpeg's definition, but we cannot hook avio_close()
324 // when called from FFmpeg's avio_closep(), so we need to hook this one
325 // as well.
326 int avio_closep(AVIOContext ** s)
327 {
328         int ret = avio_close(*s);
329         *s = NULL;
330         return ret;
331 }
332
333
334 int avio_open2(AVIOContext ** s, const char *filename, int flags,
335                const AVIOInterruptCB * int_cb, AVDictionary ** options)
336 {
337         // The options, if any, are destroyed on entry, so we can add new ones
338         // pretty freely.
339         if (options && *options) {
340                 AVDictionaryEntry *listen =
341                     av_dict_get(*options, "listen", NULL,
342                                 AV_DICT_MATCH_CASE);
343                 if (listen != NULL && atoi(listen->value) != 0) {
344                         // If -listen is set, we'll want to add a header, too.
345                         av_dict_set(options, "headers",
346                                     "Content-encoding: metacube\r\n",
347                                     AV_DICT_APPEND);
348                 }
349         }
350
351         int (*original_func)(AVIOContext ** s, const char *filename,
352                              int flags, const AVIOInterruptCB * int_cb,
353                              AVDictionary ** options);
354         original_func = dlsym(RTLD_NEXT, "avio_open2");
355         return (*original_func) (s, filename, flags, int_cb, options);
356 }