Moving lame mp3 encoder plugin from -ugly
[platform/upstream/gst-plugins-good.git] / gst / isomp4 / atomsrecovery.c
1 /* Quicktime muxer plugin for GStreamer
2  * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@collabora.co.uk>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 /*
20  * Unless otherwise indicated, Source Code is licensed under MIT license.
21  * See further explanation attached in License Statement (distributed in the file
22  * LICENSE).
23  *
24  * Permission is hereby granted, free of charge, to any person obtaining a copy of
25  * this software and associated documentation files (the "Software"), to deal in
26  * the Software without restriction, including without limitation the rights to
27  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
28  * of the Software, and to permit persons to whom the Software is furnished to do
29  * so, subject to the following conditions:
30  *
31  * The above copyright notice and this permission notice shall be included in all
32  * copies or substantial portions of the Software.
33  *
34  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
35  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
36  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
37  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
38  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
39  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
40  * SOFTWARE.
41  */
42
43 /*
44  * This module contains functions for serializing partial information from
45  * a mux in progress (by qtmux elements). This enables reconstruction of the
46  * moov box if a crash happens and thus recovering the movie file.
47  *
48  * Usage:
49  * 1) pipeline: ...yourelements ! qtmux moov-recovery-file=path.mrf ! \
50  * filesink location=moovie.mov
51  *
52  * 2) CRASH!
53  *
54  * 3) gst-launch-1.0 qtmoovrecover recovery-input=path.mrf broken-input=moovie.mov \
55         fixed-output=recovered.mov
56  *
57  * 4) (Hopefully) enjoy recovered.mov.
58  *
59  * --- Recovery file layout ---
60  * 1) Version (a guint16)
61  * 2) Prefix atom (if present)
62  * 3) ftyp atom
63  * 4) MVHD atom (without timescale/duration set)
64  * 5) moovie timescale
65  * 6) number of traks
66  * 7) list of trak atoms (stbl data is ignored, except for the stsd atom)
67  * 8) Buffers metadata (metadata that is relevant to the container)
68  *    Buffers metadata are stored in the order they are added to the mdat,
69  *    each entre has a fixed size and is stored in BE. booleans are stored
70  *    as a single byte where 0 means false, otherwise is true.
71  *   Metadata:
72  *   - guint32   track_id;
73  *   - guint32   nsamples;
74  *   - guint32   delta;
75  *   - guint32   size;
76  *   - guint64   chunk_offset;
77  *   - gboolean  sync;
78  *   - gboolean  do_pts;
79  *   - guint64   pts_offset; (always present, ignored if do_pts is false)
80  *
81  * The mdat file might contain ftyp and then mdat, in case this is the faststart
82  * temporary file there is no ftyp and no mdat header, only the buffers data.
83  *
84  * Notes about recovery file layout: We still don't store tags nor EDTS data.
85  *
86  * IMPORTANT: this is still at a experimental state.
87  */
88
89 #include "atomsrecovery.h"
90
91 #define ATOMS_RECOV_OUTPUT_WRITE_ERROR(err) \
92     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE, \
93         "Failed to write to output file: %s", g_strerror (errno))
94
95 static gboolean
96 atoms_recov_write_version (FILE * f)
97 {
98   guint8 data[2];
99   GST_WRITE_UINT16_BE (data, ATOMS_RECOV_FILE_VERSION);
100   return fwrite (data, 2, 1, f) == 1;
101 }
102
103 static gboolean
104 atoms_recov_write_ftyp_info (FILE * f, AtomFTYP * ftyp, GstBuffer * prefix)
105 {
106   guint8 *data = NULL;
107   guint64 offset = 0;
108   guint64 size = 0;
109
110   if (prefix) {
111     GstMapInfo map;
112
113     gst_buffer_map (prefix, &map, GST_MAP_READ);
114     if (fwrite (map.data, 1, map.size, f) != map.size) {
115       gst_buffer_unmap (prefix, &map);
116       return FALSE;
117     }
118     gst_buffer_unmap (prefix, &map);
119   }
120   if (!atom_ftyp_copy_data (ftyp, &data, &size, &offset)) {
121     return FALSE;
122   }
123   if (fwrite (data, 1, offset, f) != offset) {
124     g_free (data);
125     return FALSE;
126   }
127   g_free (data);
128   return TRUE;
129 }
130
131 /*
132  * Writes important info on the 'moov' atom (non-trak related)
133  * to be able to recover the moov structure after a crash.
134  *
135  * Currently, it writes the MVHD atom.
136  */
137 static gboolean
138 atoms_recov_write_moov_info (FILE * f, AtomMOOV * moov)
139 {
140   guint8 *data;
141   guint64 size;
142   guint64 offset = 0;
143   guint64 atom_size = 0;
144   gint writen = 0;
145
146   /* likely enough */
147   size = 256;
148   data = g_malloc (size);
149   atom_size = atom_mvhd_copy_data (&moov->mvhd, &data, &size, &offset);
150   if (atom_size > 0)
151     writen = fwrite (data, 1, atom_size, f);
152   g_free (data);
153   return atom_size > 0 && writen == atom_size;
154 }
155
156 /*
157  * Writes the number of traks to the file.
158  * This simply writes a guint32 in BE.
159  */
160 static gboolean
161 atoms_recov_write_traks_number (FILE * f, guint32 traks)
162 {
163   guint8 data[4];
164   GST_WRITE_UINT32_BE (data, traks);
165   return fwrite (data, 4, 1, f) == 1;
166 }
167
168 /*
169  * Writes the moov's timescale to the file
170  * This simply writes a guint32 in BE.
171  */
172 static gboolean
173 atoms_recov_write_moov_timescale (FILE * f, guint32 timescale)
174 {
175   guint8 data[4];
176   GST_WRITE_UINT32_BE (data, timescale);
177   return fwrite (data, 4, 1, f) == 1;
178 }
179
180 /*
181  * Writes the trak atom to the file.
182  */
183 gboolean
184 atoms_recov_write_trak_info (FILE * f, AtomTRAK * trak)
185 {
186   guint8 *data;
187   guint64 size;
188   guint64 offset = 0;
189   guint64 atom_size = 0;
190   gint writen = 0;
191
192   /* buffer is realloced to a larger size if needed */
193   size = 4 * 1024;
194   data = g_malloc (size);
195   atom_size = atom_trak_copy_data (trak, &data, &size, &offset);
196   if (atom_size > 0)
197     writen = fwrite (data, atom_size, 1, f);
198   g_free (data);
199   return atom_size > 0 && writen == atom_size;
200 }
201
202 gboolean
203 atoms_recov_write_trak_samples (FILE * f, AtomTRAK * trak, guint32 nsamples,
204     guint32 delta, guint32 size, guint64 chunk_offset, gboolean sync,
205     gboolean do_pts, gint64 pts_offset)
206 {
207   guint8 data[TRAK_BUFFER_ENTRY_INFO_SIZE];
208   /*
209    * We have to write a TrakBufferEntryInfo
210    */
211   GST_WRITE_UINT32_BE (data + 0, trak->tkhd.track_ID);
212   GST_WRITE_UINT32_BE (data + 4, nsamples);
213   GST_WRITE_UINT32_BE (data + 8, delta);
214   GST_WRITE_UINT32_BE (data + 12, size);
215   GST_WRITE_UINT64_BE (data + 16, chunk_offset);
216   if (sync)
217     GST_WRITE_UINT8 (data + 24, 1);
218   else
219     GST_WRITE_UINT8 (data + 24, 0);
220   if (do_pts) {
221     GST_WRITE_UINT8 (data + 25, 1);
222     GST_WRITE_UINT64_BE (data + 26, pts_offset);
223   } else {
224     GST_WRITE_UINT8 (data + 25, 0);
225     GST_WRITE_UINT64_BE (data + 26, 0);
226   }
227
228   return fwrite (data, 1, TRAK_BUFFER_ENTRY_INFO_SIZE, f) ==
229       TRAK_BUFFER_ENTRY_INFO_SIZE;
230 }
231
232 gboolean
233 atoms_recov_write_headers (FILE * f, AtomFTYP * ftyp, GstBuffer * prefix,
234     AtomMOOV * moov, guint32 timescale, guint32 traks_number)
235 {
236   if (!atoms_recov_write_version (f)) {
237     return FALSE;
238   }
239
240   if (!atoms_recov_write_ftyp_info (f, ftyp, prefix)) {
241     return FALSE;
242   }
243
244   if (!atoms_recov_write_moov_info (f, moov)) {
245     return FALSE;
246   }
247
248   if (!atoms_recov_write_moov_timescale (f, timescale)) {
249     return FALSE;
250   }
251
252   if (!atoms_recov_write_traks_number (f, traks_number)) {
253     return FALSE;
254   }
255
256   return TRUE;
257 }
258
259 static gboolean
260 read_atom_header (FILE * f, guint32 * fourcc, guint32 * size)
261 {
262   guint8 aux[8];
263
264   if (fread (aux, 1, 8, f) != 8)
265     return FALSE;
266   *size = GST_READ_UINT32_BE (aux);
267   *fourcc = GST_READ_UINT32_LE (aux + 4);
268   return TRUE;
269 }
270
271 static gboolean
272 moov_recov_file_parse_prefix (MoovRecovFile * moovrf)
273 {
274   guint32 fourcc;
275   guint32 size;
276   guint32 total_size = 0;
277   if (fseek (moovrf->file, 2, SEEK_SET) != 0)
278     return FALSE;
279   if (!read_atom_header (moovrf->file, &fourcc, &size)) {
280     return FALSE;
281   }
282
283   if (fourcc != FOURCC_ftyp) {
284     /* we might have a prefix here */
285     if (fseek (moovrf->file, size - 8, SEEK_CUR) != 0)
286       return FALSE;
287
288     total_size += size;
289
290     /* now read the ftyp */
291     if (!read_atom_header (moovrf->file, &fourcc, &size))
292       return FALSE;
293   }
294
295   /* this has to be the ftyp */
296   if (fourcc != FOURCC_ftyp)
297     return FALSE;
298   total_size += size;
299   moovrf->prefix_size = total_size;
300   return fseek (moovrf->file, size - 8, SEEK_CUR) == 0;
301 }
302
303 static gboolean
304 moov_recov_file_parse_mvhd (MoovRecovFile * moovrf)
305 {
306   guint32 fourcc;
307   guint32 size;
308   if (!read_atom_header (moovrf->file, &fourcc, &size)) {
309     return FALSE;
310   }
311   /* check for sanity */
312   if (fourcc != FOURCC_mvhd)
313     return FALSE;
314
315   moovrf->mvhd_size = size;
316   moovrf->mvhd_pos = ftell (moovrf->file) - 8;
317
318   /* skip the remaining of the mvhd in the file */
319   return fseek (moovrf->file, size - 8, SEEK_CUR) == 0;
320 }
321
322 static gboolean
323 mdat_recov_file_parse_mdat_start (MdatRecovFile * mdatrf)
324 {
325   guint32 fourcc, size;
326
327   if (!read_atom_header (mdatrf->file, &fourcc, &size)) {
328     return FALSE;
329   }
330   if (size == 1) {
331     mdatrf->mdat_header_size = 16;
332     mdatrf->mdat_size = 16;
333   } else {
334     mdatrf->mdat_header_size = 8;
335     mdatrf->mdat_size = 8;
336   }
337   mdatrf->mdat_start = ftell (mdatrf->file) - 8;
338
339   return fourcc == FOURCC_mdat;
340 }
341
342 static gboolean
343 mdat_recov_file_find_mdat (FILE * file, GError ** err)
344 {
345   guint32 fourcc = 0, size = 0;
346   gboolean failure = FALSE;
347   while (fourcc != FOURCC_mdat && !failure) {
348     if (!read_atom_header (file, &fourcc, &size)) {
349       goto parse_error;
350     }
351     switch (fourcc) {
352         /* skip these atoms */
353       case FOURCC_ftyp:
354       case FOURCC_free:
355       case FOURCC_udta:
356         if (fseek (file, size - 8, SEEK_CUR) != 0) {
357           goto file_seek_error;
358         }
359         break;
360       case FOURCC_mdat:
361         break;
362       default:
363         GST_ERROR ("Unexpected atom in headers %" GST_FOURCC_FORMAT,
364             GST_FOURCC_ARGS (fourcc));
365         failure = TRUE;
366         break;
367     }
368   }
369
370   if (!failure) {
371     /* Reverse to mdat start */
372     if (fseek (file, -8, SEEK_CUR) != 0)
373       goto file_seek_error;
374   }
375
376   return !failure;
377
378 parse_error:
379   g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
380       "Failed to parse atom");
381   return FALSE;
382
383 file_seek_error:
384   g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
385       "Failed to seek to start of the file");
386   return FALSE;
387
388 }
389
390 MdatRecovFile *
391 mdat_recov_file_create (FILE * file, gboolean datafile, GError ** err)
392 {
393   MdatRecovFile *mrf = g_new0 (MdatRecovFile, 1);
394
395   g_return_val_if_fail (file != NULL, NULL);
396
397   mrf->file = file;
398   mrf->rawfile = datafile;
399
400   /* get the file/data length */
401   if (fseek (file, 0, SEEK_END) != 0)
402     goto file_length_error;
403   /* still needs to deduce the mdat header and ftyp size */
404   mrf->data_size = ftell (file);
405   if (mrf->data_size == -1L)
406     goto file_length_error;
407
408   if (fseek (file, 0, SEEK_SET) != 0)
409     goto file_seek_error;
410
411   if (datafile) {
412     /* this file contains no atoms, only raw data to be placed on the mdat
413      * this happens when faststart mode is used */
414     mrf->mdat_start = 0;
415     mrf->mdat_header_size = 16;
416     mrf->mdat_size = 16;
417     return mrf;
418   }
419
420   if (!mdat_recov_file_find_mdat (file, err)) {
421     goto fail;
422   }
423
424   /* we don't parse this if we have a tmpdatafile */
425   if (!mdat_recov_file_parse_mdat_start (mrf)) {
426     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
427         "Error while parsing mdat atom");
428     goto fail;
429   }
430
431   return mrf;
432
433 file_seek_error:
434   g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
435       "Failed to seek to start of the file");
436   goto fail;
437
438 file_length_error:
439   g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
440       "Failed to determine file size");
441   goto fail;
442
443 fail:
444   mdat_recov_file_free (mrf);
445   return NULL;
446 }
447
448 void
449 mdat_recov_file_free (MdatRecovFile * mrf)
450 {
451   fclose (mrf->file);
452   g_free (mrf);
453 }
454
455 static gboolean
456 moov_recov_parse_num_traks (MoovRecovFile * moovrf)
457 {
458   guint8 traks[4];
459   if (fread (traks, 1, 4, moovrf->file) != 4)
460     return FALSE;
461   moovrf->num_traks = GST_READ_UINT32_BE (traks);
462   return TRUE;
463 }
464
465 static gboolean
466 moov_recov_parse_moov_timescale (MoovRecovFile * moovrf)
467 {
468   guint8 ts[4];
469   if (fread (ts, 1, 4, moovrf->file) != 4)
470     return FALSE;
471   moovrf->timescale = GST_READ_UINT32_BE (ts);
472   return TRUE;
473 }
474
475 static gboolean
476 skip_atom (MoovRecovFile * moovrf, guint32 expected_fourcc)
477 {
478   guint32 size;
479   guint32 fourcc;
480
481   if (!read_atom_header (moovrf->file, &fourcc, &size))
482     return FALSE;
483   if (fourcc != expected_fourcc)
484     return FALSE;
485
486   return (fseek (moovrf->file, size - 8, SEEK_CUR) == 0);
487 }
488
489 static gboolean
490 moov_recov_parse_tkhd (MoovRecovFile * moovrf, TrakRecovData * trakrd)
491 {
492   guint32 size;
493   guint32 fourcc;
494   guint8 data[4];
495
496   /* make sure we are on a tkhd atom */
497   if (!read_atom_header (moovrf->file, &fourcc, &size))
498     return FALSE;
499   if (fourcc != FOURCC_tkhd)
500     return FALSE;
501
502   trakrd->tkhd_file_offset = ftell (moovrf->file) - 8;
503
504   /* move 8 bytes forward to the trak_id pos */
505   if (fseek (moovrf->file, 12, SEEK_CUR) != 0)
506     return FALSE;
507   if (fread (data, 1, 4, moovrf->file) != 4)
508     return FALSE;
509
510   /* advance the rest of tkhd */
511   if (fseek (moovrf->file, 68, SEEK_CUR) != 0)
512     return FALSE;
513
514   trakrd->trak_id = GST_READ_UINT32_BE (data);
515   return TRUE;
516 }
517
518 static gboolean
519 moov_recov_parse_stbl (MoovRecovFile * moovrf, TrakRecovData * trakrd)
520 {
521   guint32 size;
522   guint32 fourcc;
523   guint32 auxsize;
524
525   if (!read_atom_header (moovrf->file, &fourcc, &size))
526     return FALSE;
527   if (fourcc != FOURCC_stbl)
528     return FALSE;
529
530   trakrd->stbl_file_offset = ftell (moovrf->file) - 8;
531   trakrd->stbl_size = size;
532
533   /* skip the stsd */
534   if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
535     return FALSE;
536   if (fourcc != FOURCC_stsd)
537     return FALSE;
538   if (fseek (moovrf->file, auxsize - 8, SEEK_CUR) != 0)
539     return FALSE;
540
541   trakrd->stsd_size = auxsize;
542   trakrd->post_stsd_offset = ftell (moovrf->file);
543
544   /* as this is the last atom we parse, we don't skip forward */
545
546   return TRUE;
547 }
548
549 static gboolean
550 moov_recov_parse_minf (MoovRecovFile * moovrf, TrakRecovData * trakrd)
551 {
552   guint32 size;
553   guint32 fourcc;
554   guint32 auxsize;
555
556   if (!read_atom_header (moovrf->file, &fourcc, &size))
557     return FALSE;
558   if (fourcc != FOURCC_minf)
559     return FALSE;
560
561   trakrd->minf_file_offset = ftell (moovrf->file) - 8;
562   trakrd->minf_size = size;
563
564   /* skip either of vmhd, smhd, hmhd that might follow */
565   if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
566     return FALSE;
567   if (fourcc != FOURCC_vmhd && fourcc != FOURCC_smhd && fourcc != FOURCC_hmhd &&
568       fourcc != FOURCC_gmhd)
569     return FALSE;
570   if (fseek (moovrf->file, auxsize - 8, SEEK_CUR))
571     return FALSE;
572
573   /* skip a possible hdlr and the following dinf */
574   if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
575     return FALSE;
576   if (fourcc == FOURCC_hdlr) {
577     if (fseek (moovrf->file, auxsize - 8, SEEK_CUR))
578       return FALSE;
579     if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
580       return FALSE;
581   }
582   if (fourcc != FOURCC_dinf)
583     return FALSE;
584   if (fseek (moovrf->file, auxsize - 8, SEEK_CUR))
585     return FALSE;
586
587   /* now we are ready to read the stbl */
588   if (!moov_recov_parse_stbl (moovrf, trakrd))
589     return FALSE;
590
591   return TRUE;
592 }
593
594 static gboolean
595 moov_recov_parse_mdhd (MoovRecovFile * moovrf, TrakRecovData * trakrd)
596 {
597   guint32 size;
598   guint32 fourcc;
599   guint8 data[4];
600
601   /* make sure we are on a tkhd atom */
602   if (!read_atom_header (moovrf->file, &fourcc, &size))
603     return FALSE;
604   if (fourcc != FOURCC_mdhd)
605     return FALSE;
606
607   trakrd->mdhd_file_offset = ftell (moovrf->file) - 8;
608
609   /* get the timescale */
610   if (fseek (moovrf->file, 12, SEEK_CUR) != 0)
611     return FALSE;
612   if (fread (data, 1, 4, moovrf->file) != 4)
613     return FALSE;
614   trakrd->timescale = GST_READ_UINT32_BE (data);
615   if (fseek (moovrf->file, 8, SEEK_CUR) != 0)
616     return FALSE;
617   return TRUE;
618 }
619
620 static gboolean
621 moov_recov_parse_mdia (MoovRecovFile * moovrf, TrakRecovData * trakrd)
622 {
623   guint32 size;
624   guint32 fourcc;
625
626   /* make sure we are on a tkhd atom */
627   if (!read_atom_header (moovrf->file, &fourcc, &size))
628     return FALSE;
629   if (fourcc != FOURCC_mdia)
630     return FALSE;
631
632   trakrd->mdia_file_offset = ftell (moovrf->file) - 8;
633   trakrd->mdia_size = size;
634
635   if (!moov_recov_parse_mdhd (moovrf, trakrd))
636     return FALSE;
637
638   if (!skip_atom (moovrf, FOURCC_hdlr))
639     return FALSE;
640   if (!moov_recov_parse_minf (moovrf, trakrd))
641     return FALSE;
642   return TRUE;
643 }
644
645 static gboolean
646 moov_recov_parse_trak (MoovRecovFile * moovrf, TrakRecovData * trakrd)
647 {
648   guint64 offset;
649   guint32 size;
650   guint32 fourcc;
651
652   offset = ftell (moovrf->file);
653   if (offset == -1) {
654     return FALSE;
655   }
656
657   /* make sure we are on a trak atom */
658   if (!read_atom_header (moovrf->file, &fourcc, &size)) {
659     return FALSE;
660   }
661   if (fourcc != FOURCC_trak) {
662     return FALSE;
663   }
664   trakrd->trak_size = size;
665
666   /* now we should have a trak header 'tkhd' */
667   if (!moov_recov_parse_tkhd (moovrf, trakrd))
668     return FALSE;
669
670   /* FIXME add edts handling here and in qtmux, as this is only detected
671    * after buffers start flowing */
672
673   if (!moov_recov_parse_mdia (moovrf, trakrd))
674     return FALSE;
675
676   if (fseek (moovrf->file,
677           (long int) trakrd->mdia_file_offset + trakrd->mdia_size,
678           SEEK_SET) != 0)
679     return FALSE;
680
681   trakrd->extra_atoms_offset = ftell (moovrf->file);
682   trakrd->extra_atoms_size = size - (trakrd->extra_atoms_offset - offset);
683
684   trakrd->file_offset = offset;
685   /* position after the trak */
686   return fseek (moovrf->file, (long int) offset + size, SEEK_SET) == 0;
687 }
688
689 MoovRecovFile *
690 moov_recov_file_create (FILE * file, GError ** err)
691 {
692   gint i;
693   MoovRecovFile *moovrf = g_new0 (MoovRecovFile, 1);
694
695   g_return_val_if_fail (file != NULL, NULL);
696
697   moovrf->file = file;
698
699   /* look for ftyp and prefix at the start */
700   if (!moov_recov_file_parse_prefix (moovrf)) {
701     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
702         "Error while parsing prefix atoms");
703     goto fail;
704   }
705
706   /* parse the mvhd */
707   if (!moov_recov_file_parse_mvhd (moovrf)) {
708     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
709         "Error while parsing mvhd atom");
710     goto fail;
711   }
712
713   if (!moov_recov_parse_moov_timescale (moovrf)) {
714     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
715         "Error while parsing timescale");
716     goto fail;
717   }
718   if (!moov_recov_parse_num_traks (moovrf)) {
719     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
720         "Error while parsing parsing number of traks");
721     goto fail;
722   }
723
724   /* sanity check */
725   if (moovrf->num_traks > 1024) {
726     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
727         "Unsupported number of traks");
728     goto fail;
729   }
730
731   /* init the traks */
732   moovrf->traks_rd = g_new0 (TrakRecovData, moovrf->num_traks);
733   for (i = 0; i < moovrf->num_traks; i++) {
734     atom_stbl_init (&(moovrf->traks_rd[i].stbl));
735   }
736   for (i = 0; i < moovrf->num_traks; i++) {
737     if (!moov_recov_parse_trak (moovrf, &(moovrf->traks_rd[i]))) {
738       g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
739           "Error while parsing trak atom");
740       goto fail;
741     }
742   }
743
744   return moovrf;
745
746 fail:
747   moov_recov_file_free (moovrf);
748   return NULL;
749 }
750
751 void
752 moov_recov_file_free (MoovRecovFile * moovrf)
753 {
754   gint i;
755   fclose (moovrf->file);
756   if (moovrf->traks_rd) {
757     for (i = 0; i < moovrf->num_traks; i++) {
758       atom_stbl_clear (&(moovrf->traks_rd[i].stbl));
759     }
760     g_free (moovrf->traks_rd);
761   }
762   g_free (moovrf);
763 }
764
765 static gboolean
766 moov_recov_parse_buffer_entry (MoovRecovFile * moovrf, TrakBufferEntryInfo * b)
767 {
768   guint8 data[TRAK_BUFFER_ENTRY_INFO_SIZE];
769   gint read;
770
771   read = fread (data, 1, TRAK_BUFFER_ENTRY_INFO_SIZE, moovrf->file);
772   if (read != TRAK_BUFFER_ENTRY_INFO_SIZE)
773     return FALSE;
774
775   b->track_id = GST_READ_UINT32_BE (data);
776   b->nsamples = GST_READ_UINT32_BE (data + 4);
777   b->delta = GST_READ_UINT32_BE (data + 8);
778   b->size = GST_READ_UINT32_BE (data + 12);
779   b->chunk_offset = GST_READ_UINT64_BE (data + 16);
780   b->sync = data[24] != 0;
781   b->do_pts = data[25] != 0;
782   b->pts_offset = GST_READ_UINT64_BE (data + 26);
783   return TRUE;
784 }
785
786 static gboolean
787 mdat_recov_add_sample (MdatRecovFile * mdatrf, guint32 size)
788 {
789   /* test if this data exists */
790   if (mdatrf->mdat_size - mdatrf->mdat_header_size + size > mdatrf->data_size)
791     return FALSE;
792
793   mdatrf->mdat_size += size;
794   return TRUE;
795 }
796
797 static TrakRecovData *
798 moov_recov_get_trak (MoovRecovFile * moovrf, guint32 id)
799 {
800   gint i;
801   for (i = 0; i < moovrf->num_traks; i++) {
802     if (moovrf->traks_rd[i].trak_id == id)
803       return &(moovrf->traks_rd[i]);
804   }
805   return NULL;
806 }
807
808 static void
809 trak_recov_data_add_sample (TrakRecovData * trak, TrakBufferEntryInfo * b)
810 {
811   trak->duration += b->nsamples * b->delta;
812   atom_stbl_add_samples (&trak->stbl, b->nsamples, b->delta, b->size,
813       b->chunk_offset, b->sync, b->pts_offset);
814 }
815
816 /*
817  * Parses the buffer entries in the MoovRecovFile and matches the inputs
818  * with the data in the MdatRecovFile. Whenever a buffer entry of that
819  * represents 'x' bytes of data, the same amount of data is 'validated' in
820  * the MdatRecovFile and will be inluded in the generated moovie file.
821  */
822 gboolean
823 moov_recov_parse_buffers (MoovRecovFile * moovrf, MdatRecovFile * mdatrf,
824     GError ** err)
825 {
826   TrakBufferEntryInfo entry;
827   TrakRecovData *trak;
828
829   /* we assume both moovrf and mdatrf are at the starting points of their
830    * data reading */
831   while (moov_recov_parse_buffer_entry (moovrf, &entry)) {
832     /* be sure we still have this data in mdat */
833     trak = moov_recov_get_trak (moovrf, entry.track_id);
834     if (trak == NULL) {
835       g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
836           "Invalid trak id found in buffer entry");
837       return FALSE;
838     }
839     if (!mdat_recov_add_sample (mdatrf, entry.size))
840       break;
841     trak_recov_data_add_sample (trak, &entry);
842   }
843   return TRUE;
844 }
845
846 static guint32
847 trak_recov_data_get_trak_atom_size (TrakRecovData * trak)
848 {
849   AtomSTBL *stbl = &trak->stbl;
850   guint64 offset;
851
852   /* write out our stbl child atoms */
853   offset = 0;
854
855   if (!atom_stts_copy_data (&stbl->stts, NULL, NULL, &offset)) {
856     goto fail;
857   }
858   if (atom_array_get_len (&stbl->stss.entries) > 0) {
859     if (!atom_stss_copy_data (&stbl->stss, NULL, NULL, &offset)) {
860       goto fail;
861     }
862   }
863   if (!atom_stsc_copy_data (&stbl->stsc, NULL, NULL, &offset)) {
864     goto fail;
865   }
866   if (!atom_stsz_copy_data (&stbl->stsz, NULL, NULL, &offset)) {
867     goto fail;
868   }
869   if (stbl->ctts) {
870     if (!atom_ctts_copy_data (stbl->ctts, NULL, NULL, &offset)) {
871       goto fail;
872     }
873   }
874   if (!atom_stco64_copy_data (&stbl->stco64, NULL, NULL, &offset)) {
875     goto fail;
876   }
877
878   return trak->trak_size + ((trak->stsd_size + offset + 8) - trak->stbl_size);
879
880 fail:
881   return 0;
882 }
883
884 static guint8 *
885 moov_recov_get_stbl_children_data (MoovRecovFile * moovrf, TrakRecovData * trak,
886     guint64 * p_size)
887 {
888   AtomSTBL *stbl = &trak->stbl;
889   guint8 *buffer;
890   guint64 size;
891   guint64 offset;
892
893   /* write out our stbl child atoms
894    *
895    * Use 1MB as a starting size, *_copy_data functions
896    * will grow the buffer if needed.
897    */
898   size = 1024 * 1024;
899   buffer = g_malloc0 (size);
900   offset = 0;
901
902   if (!atom_stts_copy_data (&stbl->stts, &buffer, &size, &offset)) {
903     goto fail;
904   }
905   if (atom_array_get_len (&stbl->stss.entries) > 0) {
906     if (!atom_stss_copy_data (&stbl->stss, &buffer, &size, &offset)) {
907       goto fail;
908     }
909   }
910   if (!atom_stsc_copy_data (&stbl->stsc, &buffer, &size, &offset)) {
911     goto fail;
912   }
913   if (!atom_stsz_copy_data (&stbl->stsz, &buffer, &size, &offset)) {
914     goto fail;
915   }
916   if (stbl->ctts) {
917     if (!atom_ctts_copy_data (stbl->ctts, &buffer, &size, &offset)) {
918       goto fail;
919     }
920   }
921   if (!atom_stco64_copy_data (&stbl->stco64, &buffer, &size, &offset)) {
922     goto fail;
923   }
924   *p_size = offset;
925   return buffer;
926
927 fail:
928   g_free (buffer);
929   return NULL;
930 }
931
932 static gboolean
933 copy_data_from_file_to_file (FILE * from, guint position, guint size, FILE * to,
934     GError ** err)
935 {
936   guint8 *data = NULL;
937
938   if (fseek (from, position, SEEK_SET) != 0)
939     goto fail;
940   data = g_malloc (size);
941   if (fread (data, 1, size, from) != size) {
942     goto fail;
943   }
944   if (fwrite (data, 1, size, to) != size) {
945     ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
946     goto fail;
947   }
948
949   g_free (data);
950   return TRUE;
951
952 fail:
953   g_free (data);
954   return FALSE;
955 }
956
957 gboolean
958 moov_recov_write_file (MoovRecovFile * moovrf, MdatRecovFile * mdatrf,
959     FILE * outf, GError ** err)
960 {
961   guint8 auxdata[16];
962   guint8 *data = NULL;
963   guint8 *prefix_data = NULL;
964   guint8 *mvhd_data = NULL;
965   guint8 *trak_data = NULL;
966   guint32 moov_size = 0;
967   gint i;
968   guint64 stbl_children_size = 0;
969   guint8 *stbl_children = NULL;
970   guint32 longest_duration = 0;
971   guint16 version;
972
973   /* check the version */
974   if (fseek (moovrf->file, 0, SEEK_SET) != 0) {
975     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
976         "Failed to seek to the start of the moov recovery file");
977     goto fail;
978   }
979   if (fread (auxdata, 1, 2, moovrf->file) != 2) {
980     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
981         "Failed to read version from file");
982   }
983
984   version = GST_READ_UINT16_BE (auxdata);
985   if (version != ATOMS_RECOV_FILE_VERSION) {
986     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_VERSION,
987         "Input file version (%u) is not supported in this version (%u)",
988         version, ATOMS_RECOV_FILE_VERSION);
989     return FALSE;
990   }
991
992   /* write the ftyp */
993   prefix_data = g_malloc (moovrf->prefix_size);
994   if (fread (prefix_data, 1, moovrf->prefix_size,
995           moovrf->file) != moovrf->prefix_size) {
996     g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
997         "Failed to read the ftyp atom from file");
998     goto fail;
999   }
1000   if (fwrite (prefix_data, 1, moovrf->prefix_size, outf) != moovrf->prefix_size) {
1001     ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1002     goto fail;
1003   }
1004   g_free (prefix_data);
1005   prefix_data = NULL;
1006
1007   /* need to calculate the moov size beforehand to add the offset to
1008    * chunk offset entries */
1009   moov_size += moovrf->mvhd_size + 8;   /* mvhd + moov size + fourcc */
1010   for (i = 0; i < moovrf->num_traks; i++) {
1011     TrakRecovData *trak = &(moovrf->traks_rd[i]);
1012     guint32 duration;           /* in moov's timescale */
1013     guint32 trak_size;
1014
1015     /* convert trak duration to moov's duration */
1016     duration = gst_util_uint64_scale_round (trak->duration, moovrf->timescale,
1017         trak->timescale);
1018
1019     if (duration > longest_duration)
1020       longest_duration = duration;
1021     trak_size = trak_recov_data_get_trak_atom_size (trak);
1022     if (trak_size == 0) {
1023       g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_GENERIC,
1024           "Failed to estimate trak atom size");
1025       goto fail;
1026     }
1027     moov_size += trak_size;
1028   }
1029
1030   /* add chunks offsets */
1031   for (i = 0; i < moovrf->num_traks; i++) {
1032     TrakRecovData *trak = &(moovrf->traks_rd[i]);
1033     /* 16 for the mdat header */
1034     gint64 offset = moov_size + ftell (outf) + 16;
1035     atom_stco64_chunks_set_offset (&trak->stbl.stco64, offset);
1036   }
1037
1038   /* write the moov */
1039   GST_WRITE_UINT32_BE (auxdata, moov_size);
1040   GST_WRITE_UINT32_LE (auxdata + 4, FOURCC_moov);
1041   if (fwrite (auxdata, 1, 8, outf) != 8) {
1042     ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1043     goto fail;
1044   }
1045
1046   /* write the mvhd */
1047   mvhd_data = g_malloc (moovrf->mvhd_size);
1048   if (fseek (moovrf->file, moovrf->mvhd_pos, SEEK_SET) != 0)
1049     goto fail;
1050   if (fread (mvhd_data, 1, moovrf->mvhd_size,
1051           moovrf->file) != moovrf->mvhd_size)
1052     goto fail;
1053   GST_WRITE_UINT32_BE (mvhd_data + 20, moovrf->timescale);
1054   GST_WRITE_UINT32_BE (mvhd_data + 24, longest_duration);
1055   if (fwrite (mvhd_data, 1, moovrf->mvhd_size, outf) != moovrf->mvhd_size) {
1056     ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1057     goto fail;
1058   }
1059   g_free (mvhd_data);
1060   mvhd_data = NULL;
1061
1062   /* write the traks, this is the tough part because we need to update:
1063    * - stbl atom
1064    * - sizes of atoms from stbl to trak
1065    * - trak duration
1066    */
1067   for (i = 0; i < moovrf->num_traks; i++) {
1068     TrakRecovData *trak = &(moovrf->traks_rd[i]);
1069     guint trak_data_size;
1070     guint32 stbl_new_size;
1071     guint32 minf_new_size;
1072     guint32 mdia_new_size;
1073     guint32 trak_new_size;
1074     guint32 size_diff;
1075     guint32 duration;           /* in moov's timescale */
1076
1077     /* convert trak duration to moov's duration */
1078     duration = gst_util_uint64_scale_round (trak->duration, moovrf->timescale,
1079         trak->timescale);
1080
1081     stbl_children = moov_recov_get_stbl_children_data (moovrf, trak,
1082         &stbl_children_size);
1083     if (stbl_children == NULL)
1084       goto fail;
1085
1086     /* calc the new size of the atoms from stbl to trak in the atoms tree */
1087     stbl_new_size = trak->stsd_size + stbl_children_size + 8;
1088     size_diff = stbl_new_size - trak->stbl_size;
1089     minf_new_size = trak->minf_size + size_diff;
1090     mdia_new_size = trak->mdia_size + size_diff;
1091     trak_new_size = trak->trak_size + size_diff;
1092
1093     if (fseek (moovrf->file, trak->file_offset, SEEK_SET) != 0)
1094       goto fail;
1095     trak_data_size = trak->post_stsd_offset - trak->file_offset;
1096     trak_data = g_malloc (trak_data_size);
1097     if (fread (trak_data, 1, trak_data_size, moovrf->file) != trak_data_size) {
1098       goto fail;
1099     }
1100     /* update the size values in those read atoms before writing */
1101     GST_WRITE_UINT32_BE (trak_data, trak_new_size);
1102     GST_WRITE_UINT32_BE (trak_data + (trak->mdia_file_offset -
1103             trak->file_offset), mdia_new_size);
1104     GST_WRITE_UINT32_BE (trak_data + (trak->minf_file_offset -
1105             trak->file_offset), minf_new_size);
1106     GST_WRITE_UINT32_BE (trak_data + (trak->stbl_file_offset -
1107             trak->file_offset), stbl_new_size);
1108
1109     /* update duration values in tkhd and mdhd */
1110     GST_WRITE_UINT32_BE (trak_data + (trak->tkhd_file_offset -
1111             trak->file_offset) + 28, duration);
1112     GST_WRITE_UINT32_BE (trak_data + (trak->mdhd_file_offset -
1113             trak->file_offset) + 24, trak->duration);
1114
1115     if (fwrite (trak_data, 1, trak_data_size, outf) != trak_data_size) {
1116       ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1117       goto fail;
1118     }
1119     if (fwrite (stbl_children, 1, stbl_children_size, outf) !=
1120         stbl_children_size) {
1121       ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1122       goto fail;
1123     }
1124
1125     g_free (trak_data);
1126     trak_data = NULL;
1127     g_free (stbl_children);
1128     stbl_children = NULL;
1129
1130     /* Copy the extra atoms after 'minf' */
1131     if (!copy_data_from_file_to_file (moovrf->file, trak->extra_atoms_offset,
1132             trak->extra_atoms_size, outf, err))
1133       goto fail;
1134   }
1135
1136   /* write the mdat */
1137   /* write the header first */
1138   GST_WRITE_UINT32_BE (auxdata, 1);
1139   GST_WRITE_UINT32_LE (auxdata + 4, FOURCC_mdat);
1140   GST_WRITE_UINT64_BE (auxdata + 8, mdatrf->mdat_size);
1141   if (fwrite (auxdata, 1, 16, outf) != 16) {
1142     ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
1143     goto fail;
1144   }
1145
1146   /* now read the mdat data and output to the file */
1147   if (fseek (mdatrf->file, mdatrf->mdat_start +
1148           (mdatrf->rawfile ? 0 : mdatrf->mdat_header_size), SEEK_SET) != 0)
1149     goto fail;
1150
1151   data = g_malloc (4096);
1152   while (!feof (mdatrf->file)) {
1153     gint read, write;
1154
1155     read = fread (data, 1, 4096, mdatrf->file);
1156     write = fwrite (data, 1, read, outf);
1157
1158     if (write != read) {
1159       g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
1160           "Failed to copy data to output file: %s", g_strerror (errno));
1161       goto fail;
1162     }
1163   }
1164   g_free (data);
1165
1166   return TRUE;
1167
1168 fail:
1169   g_free (stbl_children);
1170   g_free (mvhd_data);
1171   g_free (prefix_data);
1172   g_free (trak_data);
1173   g_free (data);
1174   return FALSE;
1175 }