qtmux: Adds moov recovery feature
authorThiago Santos <thiago.sousa.santos@collabora.co.uk>
Sat, 12 Dec 2009 19:07:15 +0000 (16:07 -0300)
committerTim-Philipp Müller <tim.muller@collabora.co.uk>
Tue, 12 Apr 2011 19:32:16 +0000 (20:32 +0100)
Adds a new property to qtmux that sets a path to a file to write
and update data about the moov atom (that is not writen till the
end of the file). If the pipeline/app crashes during execution it
might be possible to recover the movie using the qtmoovrecover element.

qtmoovrecover is an element that is also a pipeline. It is not
meant to be used with other elements (it has no pads). It is merely
a tool/utilitary to recover unfinished qtmux files.

Fixes #601576

gst/quicktime/atoms.c
gst/quicktime/atoms.h
gst/quicktime/atomsrecovery.c [new file with mode: 0644]
gst/quicktime/atomsrecovery.h [new file with mode: 0644]
gst/quicktime/fourcc.h
gst/quicktime/gstqtmoovrecover.c [new file with mode: 0644]
gst/quicktime/gstqtmoovrecover.h [new file with mode: 0644]
gst/quicktime/gstqtmux.c
gst/quicktime/gstqtmux.h
gst/quicktime/gstqtmuxplugin.c [new file with mode: 0644]

index d625823..bcb71ca 100644 (file)
@@ -1,5 +1,5 @@
 /* Quicktime muxer plugin for GStreamer
- * Copyright (C) 2008 Thiago Sousa Santos <thiagoss@embedded.ufcg.edu.br>
+ * Copyright (C) 2008-2010 Thiago Santos <thiagoss@embedded.ufcg.edu.br>
  * Copyright (C) 2008 Mark Nauwelaerts <mnauw@users.sf.net>
  *
  * This library is free software; you can redistribute it and/or
 #include <gst/base/gstbytewriter.h>
 
 
-/* storage helpers */
-
-#define atom_array_init(array, reserve)                                       \
-G_STMT_START {                                                                \
-  (array)->len = 0;                                                           \
-  (array)->size = reserve;                                                    \
-  (array)->data = g_malloc (sizeof (*(array)->data) * reserve);               \
-} G_STMT_END
-
-#define atom_array_append(array, elmt, inc)                                   \
-G_STMT_START {                                                                \
-  g_assert ((array)->data);                                                   \
-  g_assert (inc > 0);                                                         \
-  if (G_UNLIKELY ((array)->len == (array)->size)) {                           \
-    (array)->size += inc;                                                     \
-    (array)->data =                                                           \
-        g_realloc ((array)->data, sizeof (*((array)->data)) * (array)->size); \
-  }                                                                           \
-  (array)->data[(array)->len] = elmt;                                         \
-  (array)->len++;                                                             \
-} G_STMT_END
-
-#define atom_array_get_len(array)                  ((array)->len)
-#define atom_array_index(array, index)             ((array)->data[index])
-
-#define atom_array_clear(array)                                               \
-G_STMT_START {                                                                \
-  (array)->size = (array)->len = 0;                                           \
-  g_free ((array)->data);                                                     \
-  (array)->data = NULL;                                                       \
-} G_STMT_END
-
 /**
  * Creates a new AtomsContext for the given flavor.
  */
@@ -641,7 +609,7 @@ atom_stss_clear (AtomSTSS * stss)
   atom_array_clear (&stss->entries);
 }
 
-static void
+void
 atom_stbl_init (AtomSTBL * stbl)
 {
   atom_header_set (&stbl->header, FOURCC_stbl, 0, 0);
@@ -656,7 +624,7 @@ atom_stbl_init (AtomSTBL * stbl)
   atom_co64_init (&stbl->stco64);
 }
 
-static void
+void
 atom_stbl_clear (AtomSTBL * stbl)
 {
   atom_clear (&stbl->header);
@@ -1370,7 +1338,7 @@ atom_ftyp_copy_data (AtomFTYP * ftyp, guint8 ** buffer, guint64 * size,
   return *offset - original_offset;
 }
 
-static guint64
+guint64
 atom_mvhd_copy_data (AtomMVHD * atom, guint8 ** buffer, guint64 * size,
     guint64 * offset)
 {
@@ -1545,7 +1513,7 @@ atom_url_copy_data (AtomURL * url, guint8 ** buffer, guint64 * size,
   return original_offset - *offset;
 }
 
-static guint64
+guint64
 atom_stts_copy_data (AtomSTTS * stts, guint8 ** buffer, guint64 * size,
     guint64 * offset)
 {
@@ -1729,7 +1697,7 @@ sample_entry_mp4v_copy_data (SampleTableEntryMP4V * mp4v, guint8 ** buffer,
   return *offset - original_offset;
 }
 
-static guint64
+guint64
 atom_stsz_copy_data (AtomSTSZ * stsz, guint8 ** buffer, guint64 * size,
     guint64 * offset)
 {
@@ -1757,7 +1725,7 @@ atom_stsz_copy_data (AtomSTSZ * stsz, guint8 ** buffer, guint64 * size,
   return *offset - original_offset;
 }
 
-static guint64
+guint64
 atom_stsc_copy_data (AtomSTSC * stsc, guint8 ** buffer, guint64 * size,
     guint64 * offset)
 {
@@ -1785,7 +1753,7 @@ atom_stsc_copy_data (AtomSTSC * stsc, guint8 ** buffer, guint64 * size,
   return *offset - original_offset;
 }
 
-static guint64
+guint64
 atom_ctts_copy_data (AtomCTTS * ctts, guint8 ** buffer, guint64 * size,
     guint64 * offset)
 {
@@ -1811,7 +1779,7 @@ atom_ctts_copy_data (AtomCTTS * ctts, guint8 ** buffer, guint64 * size,
   return *offset - original_offset;
 }
 
-static guint64
+guint64
 atom_stco64_copy_data (AtomSTCO64 * stco64, guint8 ** buffer, guint64 * size,
     guint64 * offset)
 {
@@ -1843,7 +1811,7 @@ atom_stco64_copy_data (AtomSTCO64 * stco64, guint8 ** buffer, guint64 * size,
   return *offset - original_offset;
 }
 
-static guint64
+guint64
 atom_stss_copy_data (AtomSTSS * stss, guint8 ** buffer, guint64 * size,
     guint64 * offset)
 {
@@ -2152,7 +2120,7 @@ atom_edts_copy_data (AtomEDTS * edts, guint8 ** buffer, guint64 * size,
   return *offset - original_offset;
 }
 
-static guint64
+guint64
 atom_trak_copy_data (AtomTRAK * trak, guint8 ** buffer, guint64 * size,
     guint64 * offset)
 {
@@ -2439,12 +2407,10 @@ atom_stbl_add_ctts_entry (AtomSTBL * stbl, guint32 nsamples, guint32 offset)
 }
 
 void
-atom_trak_add_samples (AtomTRAK * trak, guint32 nsamples, guint32 delta,
+atom_stbl_add_samples (AtomSTBL * stbl, guint32 nsamples, guint32 delta,
     guint32 size, guint64 chunk_offset, gboolean sync,
     gboolean do_pts, gint64 pts_offset)
 {
-  AtomSTBL *stbl = &trak->mdia.minf.stbl;
-
   atom_stts_add_entry (&stbl->stts, nsamples, delta);
   atom_stsz_add_entry (&stbl->stsz, nsamples, size);
   atom_stco64_add_entry (&stbl->stco64, chunk_offset);
@@ -2456,6 +2422,16 @@ atom_trak_add_samples (AtomTRAK * trak, guint32 nsamples, guint32 delta,
     atom_stbl_add_ctts_entry (stbl, nsamples, pts_offset);
 }
 
+void
+atom_trak_add_samples (AtomTRAK * trak, guint32 nsamples, guint32 delta,
+    guint32 size, guint64 chunk_offset, gboolean sync,
+    gboolean do_pts, gint64 pts_offset)
+{
+  AtomSTBL *stbl = &trak->mdia.minf.stbl;
+  atom_stbl_add_samples (stbl, nsamples, delta, size, chunk_offset, sync,
+      do_pts, pts_offset);
+}
+
 /* trak and moov molding */
 
 guint32
@@ -2576,7 +2552,7 @@ atom_moov_set_64bits (AtomMOOV * moov, gboolean large_file)
   }
 }
 
-static void
+void
 atom_stco64_chunks_add_offset (AtomSTCO64 * stco64, guint32 offset)
 {
   guint i;
index 6d704a6..8090fcf 100644 (file)
@@ -1,5 +1,5 @@
 /* Quicktime muxer plugin for GStreamer
- * Copyright (C) 2008 Thiago Sousa Santos <thiagoss@embedded.ufcg.edu.br>
+ * Copyright (C) 2008-2010 Thiago Santos <thiagoss@embedded.ufcg.edu.br>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
@@ -59,6 +59,38 @@ struct { \
   struct_type *data; \
 }
 
+/* storage helpers */
+
+#define atom_array_init(array, reserve)                                       \
+G_STMT_START {                                                                \
+  (array)->len = 0;                                                           \
+  (array)->size = reserve;                                                    \
+  (array)->data = g_malloc (sizeof (*(array)->data) * reserve);               \
+} G_STMT_END
+
+#define atom_array_append(array, elmt, inc)                                   \
+G_STMT_START {                                                                \
+  g_assert ((array)->data);                                                   \
+  g_assert (inc > 0);                                                         \
+  if (G_UNLIKELY ((array)->len == (array)->size)) {                           \
+    (array)->size += inc;                                                     \
+    (array)->data =                                                           \
+        g_realloc ((array)->data, sizeof (*((array)->data)) * (array)->size); \
+  }                                                                           \
+  (array)->data[(array)->len] = elmt;                                         \
+  (array)->len++;                                                             \
+} G_STMT_END
+
+#define atom_array_get_len(array)                  ((array)->len)
+#define atom_array_index(array, index)             ((array)->data[index])
+
+#define atom_array_clear(array)                                               \
+G_STMT_START {                                                                \
+  (array)->size = (array)->len = 0;                                           \
+  g_free ((array)->data);                                                     \
+  (array)->data = NULL;                                                       \
+} G_STMT_END
+
 /* light-weight context that may influence header atom tree construction */
 typedef enum _AtomsTreeFlavor
 {
@@ -612,6 +644,10 @@ void       atom_trak_add_samples       (AtomTRAK * trak, guint32 nsamples, guint
 void       atom_trak_add_elst_entry    (AtomTRAK * trak, guint32 duration,
                                         guint32 media_time, guint32 rate);
 guint32    atom_trak_get_timescale     (AtomTRAK *trak);
+void       atom_stbl_add_samples       (AtomSTBL * stbl, guint32 nsamples,
+                                        guint32 delta, guint32 size,
+                                        guint64 chunk_offset, gboolean sync,
+                                        gboolean do_pts, gint64 pts_offset);
 
 AtomMOOV*  atom_moov_new               (AtomsContext *context);
 void       atom_moov_free              (AtomMOOV *moov);
@@ -622,6 +658,26 @@ void       atom_moov_set_64bits        (AtomMOOV *moov, gboolean large_file);
 void       atom_moov_chunks_add_offset (AtomMOOV *moov, guint32 offset);
 void       atom_moov_add_trak          (AtomMOOV *moov, AtomTRAK *trak);
 
+guint64    atom_mvhd_copy_data         (AtomMVHD * atom, guint8 ** buffer,
+                                        guint64 * size, guint64 * offset);
+void       atom_stco64_chunks_add_offset (AtomSTCO64 * stco64, guint32 offset);
+guint64    atom_trak_copy_data         (AtomTRAK * atom, guint8 ** buffer,
+                                        guint64 * size, guint64 * offset);
+void       atom_stbl_clear             (AtomSTBL * stbl);
+void       atom_stbl_init              (AtomSTBL * stbl);
+guint64    atom_stss_copy_data         (AtomSTSS *atom, guint8 **buffer,
+                                        guint64 *size, guint64* offset);
+guint64    atom_stts_copy_data         (AtomSTTS *atom, guint8 **buffer,
+                                        guint64 *size, guint64* offset);
+guint64    atom_stsc_copy_data         (AtomSTSC *atom, guint8 **buffer,
+                                        guint64 *size, guint64* offset);
+guint64    atom_stsz_copy_data         (AtomSTSZ *atom, guint8 **buffer,
+                                        guint64 *size, guint64* offset);
+guint64    atom_ctts_copy_data         (AtomCTTS *atom, guint8 **buffer,
+                                        guint64 *size, guint64* offset);
+guint64    atom_stco64_copy_data       (AtomSTCO64 *atom, guint8 **buffer,
+                                        guint64 *size, guint64* offset);
+
 /* media sample description related helpers */
 
 typedef struct
diff --git a/gst/quicktime/atomsrecovery.c b/gst/quicktime/atomsrecovery.c
new file mode 100644 (file)
index 0000000..9e1ba67
--- /dev/null
@@ -0,0 +1,1087 @@
+/* Quicktime muxer plugin for GStreamer
+ * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+/*
+ * Unless otherwise indicated, Source Code is licensed under MIT license.
+ * See further explanation attached in License Statement (distributed in the file
+ * LICENSE).
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/**
+ * This module contains functions for serializing partial information from
+ * a mux in progress (by qtmux elements). This enables reconstruction of the
+ * moov box if a crash happens and thus recovering the movie file.
+ *
+ * Usage:
+ * 1) pipeline: ...yourelements ! qtmux moov-recovery-file=path.mrf ! \
+ * filesink location=moovie.mov
+ *
+ * 2) CRASH!
+ *
+ * 3) gst-launch qtmoovrecover recovery-input=path.mrf broken-input=moovie.mov \
+        fixed-output=recovered.mov
+ *
+ * 4) (Hopefully) enjoy recovered.mov.
+ *
+ * --- Recovery file layout ---
+ * 1) Version (a guint16)
+ * 2) Prefix atom (if present)
+ * 3) ftyp atom
+ * 4) MVHD atom (without timescale/duration set)
+ * 5) moovie timescale
+ * 6) number of traks
+ * 7) list of trak atoms (stbl data is ignored, except for the stsd atom)
+ * 8) Buffers metadata (metadata that is relevant to the container)
+ *    Buffers metadata are stored in the order they are added to the mdat,
+ *    each entre has a fixed size and is stored in BE. booleans are stored
+ *    as a single byte where 0 means false, otherwise is true.
+ *   Metadata:
+ *   - guint32   track_id;
+ *   - guint32   nsamples;
+ *   - guint32   delta;
+ *   - guint32   size;
+ *   - guint64   chunk_offset;
+ *   - gboolean  sync;
+ *   - gboolean  do_pts;
+ *   - guint64   pts_offset; (always present, ignored if do_pts is false)
+ *
+ * The mdat file might contain ftyp and then mdat, in case this is the faststart
+ * temporary file there is no ftyp and no mdat header, only the buffers data.
+ *
+ * Notes about recovery file layout: We still don't store tags nor EDTS data.
+ *
+ * IMPORTANT: this is still at a experimental state.
+ */
+
+#include "atomsrecovery.h"
+
+#define ATOMS_RECOV_OUTPUT_WRITE_ERROR(err) \
+    g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE, \
+        "Failed to write to output file: %s", g_strerror (errno))
+
+static gboolean
+atoms_recov_write_version (FILE * f)
+{
+  guint8 data[2];
+  GST_WRITE_UINT16_BE (data, ATOMS_RECOV_FILE_VERSION);
+  return fwrite (data, 2, 1, f) == 1;
+}
+
+static gboolean
+atoms_recov_write_ftyp_info (FILE * f, AtomFTYP * ftyp, GstBuffer * prefix)
+{
+  guint8 *data = NULL;
+  guint64 offset = 0;
+  guint64 size = 0;
+
+  if (prefix) {
+    if (fwrite (GST_BUFFER_DATA (prefix), 1, GST_BUFFER_SIZE (prefix), f) !=
+        GST_BUFFER_SIZE (prefix)) {
+      return FALSE;
+    }
+  }
+  if (!atom_ftyp_copy_data (ftyp, &data, &size, &offset)) {
+    return FALSE;
+  }
+  if (fwrite (data, 1, offset, f) != offset) {
+    return FALSE;
+  }
+  return TRUE;
+}
+
+/**
+ * Writes important info on the 'moov' atom (non-trak related)
+ * to be able to recover the moov structure after a crash.
+ *
+ * Currently, it writes the MVHD atom.
+ */
+static gboolean
+atoms_recov_write_moov_info (FILE * f, AtomMOOV * moov)
+{
+  guint8 *data;
+  guint64 size;
+  guint64 offset = 0;
+  guint64 atom_size = 0;
+  gint writen = 0;
+
+  /* likely enough */
+  size = 256;
+  data = g_malloc (size);
+  atom_size = atom_mvhd_copy_data (&moov->mvhd, &data, &size, &offset);
+  if (atom_size > 0)
+    writen = fwrite (data, 1, atom_size, f);
+  g_free (data);
+  return atom_size > 0 && writen == atom_size;
+}
+
+/**
+ * Writes the number of traks to the file.
+ * This simply writes a guint32 in BE.
+ */
+static gboolean
+atoms_recov_write_traks_number (FILE * f, guint32 traks)
+{
+  guint8 data[4];
+  GST_WRITE_UINT32_BE (data, traks);
+  return fwrite (data, 4, 1, f) == 1;
+}
+
+/**
+ * Writes the moov's timescale to the file
+ * This simply writes a guint32 in BE.
+ */
+static gboolean
+atoms_recov_write_moov_timescale (FILE * f, guint32 timescale)
+{
+  guint8 data[4];
+  GST_WRITE_UINT32_BE (data, timescale);
+  return fwrite (data, 4, 1, f) == 1;
+}
+
+/**
+ * Writes the trak atom to the file.
+ */
+gboolean
+atoms_recov_write_trak_info (FILE * f, AtomTRAK * trak)
+{
+  guint8 *data;
+  guint64 size;
+  guint64 offset = 0;
+  guint64 atom_size = 0;
+  gint writen = 0;
+
+  /* buffer is realloced to a larger size if needed */
+  size = 4 * 1024;
+  data = g_malloc (size);
+  atom_size = atom_trak_copy_data (trak, &data, &size, &offset);
+  if (atom_size > 0)
+    writen = fwrite (data, atom_size, 1, f);
+  g_free (data);
+  return atom_size > 0 && writen == atom_size;
+}
+
+gboolean
+atoms_recov_write_trak_samples (FILE * f, AtomTRAK * trak, guint32 nsamples,
+    guint32 delta, guint32 size, guint64 chunk_offset, gboolean sync,
+    gboolean do_pts, gint64 pts_offset)
+{
+  guint8 data[TRAK_BUFFER_ENTRY_INFO_SIZE];
+  /*
+   * We have to write a TrakBufferEntryInfo
+   */
+  GST_WRITE_UINT32_BE (data + 0, trak->tkhd.track_ID);
+  GST_WRITE_UINT32_BE (data + 4, nsamples);
+  GST_WRITE_UINT32_BE (data + 8, delta);
+  GST_WRITE_UINT32_BE (data + 12, size);
+  GST_WRITE_UINT64_BE (data + 16, chunk_offset);
+  if (sync)
+    GST_WRITE_UINT8 (data + 24, 1);
+  else
+    GST_WRITE_UINT8 (data + 24, 0);
+  if (do_pts) {
+    GST_WRITE_UINT8 (data + 25, 1);
+    GST_WRITE_UINT64_BE (data + 26, pts_offset);
+  } else {
+    GST_WRITE_UINT8 (data + 25, 0);
+    GST_WRITE_UINT64_BE (data + 26, 0);
+  }
+
+  return fwrite (data, 1, TRAK_BUFFER_ENTRY_INFO_SIZE, f) ==
+      TRAK_BUFFER_ENTRY_INFO_SIZE;
+}
+
+gboolean
+atoms_recov_write_headers (FILE * f, AtomFTYP * ftyp, GstBuffer * prefix,
+    AtomMOOV * moov, guint32 timescale, guint32 traks_number)
+{
+  if (!atoms_recov_write_version (f)) {
+    return FALSE;
+  }
+
+  if (!atoms_recov_write_ftyp_info (f, ftyp, prefix)) {
+    return FALSE;
+  }
+
+  if (!atoms_recov_write_moov_info (f, moov)) {
+    return FALSE;
+  }
+
+  if (!atoms_recov_write_moov_timescale (f, timescale)) {
+    return FALSE;
+  }
+
+  if (!atoms_recov_write_traks_number (f, traks_number)) {
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+static gboolean
+read_atom_header (FILE * f, guint32 * fourcc, guint32 * size)
+{
+  guint8 aux[8];
+
+  if (fread (aux, 1, 8, f) != 8)
+    return FALSE;
+  *size = GST_READ_UINT32_BE (aux);
+  *fourcc = GST_READ_UINT32_LE (aux + 4);
+  return TRUE;
+}
+
+static gboolean
+moov_recov_file_parse_prefix (MoovRecovFile * moovrf)
+{
+  guint32 fourcc;
+  guint32 size;
+  guint32 total_size = 0;
+  if (fseek (moovrf->file, 2, SEEK_SET) != 0)
+    return FALSE;
+  if (!read_atom_header (moovrf->file, &fourcc, &size)) {
+    return FALSE;
+  }
+
+  if (fourcc != FOURCC_ftyp) {
+    /* we might have a prefix here */
+    if (fseek (moovrf->file, size - 8, SEEK_CUR) != 0)
+      return FALSE;
+
+    total_size += size;
+
+    /* now read the ftyp */
+    if (!read_atom_header (moovrf->file, &fourcc, &size))
+      return FALSE;
+  }
+
+  /* this has to be the ftyp */
+  if (fourcc != FOURCC_ftyp)
+    return FALSE;
+  total_size += size;
+  moovrf->prefix_size = total_size;
+  return fseek (moovrf->file, size - 8, SEEK_CUR) == 0;
+}
+
+static gboolean
+moov_recov_file_parse_mvhd (MoovRecovFile * moovrf)
+{
+  guint32 fourcc;
+  guint32 size;
+  if (!read_atom_header (moovrf->file, &fourcc, &size)) {
+    return FALSE;
+  }
+  /* check for sanity */
+  if (fourcc != FOURCC_mvhd)
+    return FALSE;
+
+  moovrf->mvhd_size = size;
+  moovrf->mvhd_pos = ftell (moovrf->file) - 8;
+
+  /* skip the remaining of the mvhd in the file */
+  return fseek (moovrf->file, size - 8, SEEK_CUR) == 0;
+}
+
+static gboolean
+mdat_recov_file_parse_mdat_start (MdatRecovFile * mdatrf)
+{
+  guint32 fourcc, size;
+
+  if (!read_atom_header (mdatrf->file, &fourcc, &size)) {
+    return FALSE;
+  }
+  if (size == 1) {
+    mdatrf->mdat_header_size = 16;
+    mdatrf->mdat_size = 16;
+  } else {
+    mdatrf->mdat_header_size = 8;
+    mdatrf->mdat_size = 8;
+  }
+  mdatrf->mdat_start = ftell (mdatrf->file) - 8;
+
+  return fourcc == FOURCC_mdat;
+}
+
+MdatRecovFile *
+mdat_recov_file_create (FILE * file, gboolean datafile, GError ** err)
+{
+  MdatRecovFile *mrf = g_new0 (MdatRecovFile, 1);
+  guint32 fourcc, size;
+
+  g_return_val_if_fail (file != NULL, NULL);
+
+  mrf->file = file;
+  mrf->rawfile = datafile;
+
+  /* get the file/data length */
+  if (fseek (file, 0, SEEK_END) != 0)
+    goto file_length_error;
+  /* still needs to deduce the mdat header and ftyp size */
+  mrf->data_size = ftell (file);
+  if (mrf->data_size == -1L)
+    goto file_length_error;
+
+  if (fseek (file, 0, SEEK_SET) != 0)
+    goto file_seek_error;
+
+  if (datafile) {
+    /* this file contains no atoms, only raw data to be placed on the mdat
+     * this happens when faststart mode is used */
+    mrf->mdat_start = 0;
+    mrf->mdat_header_size = 16;
+    mrf->mdat_size = 16;
+    return mrf;
+  }
+
+  if (!read_atom_header (file, &fourcc, &size)) {
+    goto parse_error;
+  }
+  if (fourcc != FOURCC_ftyp) {
+    /* this could be a prefix atom, let's skip it and try again */
+    if (fseek (file, size - 8, SEEK_CUR) != 0) {
+      goto file_seek_error;
+    }
+    if (!read_atom_header (file, &fourcc, &size)) {
+      goto parse_error;
+    }
+  }
+
+  if (fourcc != FOURCC_ftyp) {
+    goto parse_error;
+  }
+  if (fseek (file, size - 8, SEEK_CUR) != 0)
+    goto file_seek_error;
+
+  /* we don't parse this if we have a tmpdatafile */
+  if (!mdat_recov_file_parse_mdat_start (mrf)) {
+    g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
+        "Error while parsing mdat atom");
+    goto fail;
+  }
+
+  return mrf;
+
+parse_error:
+  g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
+      "Failed to parse atom");
+  goto fail;
+
+file_seek_error:
+  g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
+      "Failed to seek to start of the file");
+  goto fail;
+
+file_length_error:
+  g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
+      "Failed to determine file size");
+  goto fail;
+
+fail:
+  mdat_recov_file_free (mrf);
+  return NULL;
+}
+
+void
+mdat_recov_file_free (MdatRecovFile * mrf)
+{
+  fclose (mrf->file);
+  g_free (mrf);
+}
+
+static gboolean
+moov_recov_parse_num_traks (MoovRecovFile * moovrf)
+{
+  guint8 traks[4];
+  if (fread (traks, 1, 4, moovrf->file) != 4)
+    return FALSE;
+  moovrf->num_traks = GST_READ_UINT32_BE (traks);
+  return TRUE;
+}
+
+static gboolean
+moov_recov_parse_moov_timescale (MoovRecovFile * moovrf)
+{
+  guint8 ts[4];
+  if (fread (ts, 1, 4, moovrf->file) != 4)
+    return FALSE;
+  moovrf->timescale = GST_READ_UINT32_BE (ts);
+  return TRUE;
+}
+
+static gboolean
+skip_atom (MoovRecovFile * moovrf, guint32 expected_fourcc)
+{
+  guint32 size;
+  guint32 fourcc;
+
+  if (!read_atom_header (moovrf->file, &fourcc, &size))
+    return FALSE;
+  if (fourcc != expected_fourcc)
+    return FALSE;
+
+  return (fseek (moovrf->file, size - 8, SEEK_CUR) == 0);
+}
+
+static gboolean
+moov_recov_parse_tkhd (MoovRecovFile * moovrf, TrakRecovData * trakrd)
+{
+  guint32 size;
+  guint32 fourcc;
+  guint8 data[4];
+
+  /* make sure we are on a tkhd atom */
+  if (!read_atom_header (moovrf->file, &fourcc, &size))
+    return FALSE;
+  if (fourcc != FOURCC_tkhd)
+    return FALSE;
+
+  trakrd->tkhd_file_offset = ftell (moovrf->file) - 8;
+
+  /* move 8 bytes forward to the trak_id pos */
+  if (fseek (moovrf->file, 12, SEEK_CUR) != 0)
+    return FALSE;
+  if (fread (data, 1, 4, moovrf->file) != 4)
+    return FALSE;
+
+  /* advance the rest of tkhd */
+  fseek (moovrf->file, 68, SEEK_CUR);
+
+  trakrd->trak_id = GST_READ_UINT32_BE (data);
+  return TRUE;
+}
+
+static gboolean
+moov_recov_parse_stbl (MoovRecovFile * moovrf, TrakRecovData * trakrd)
+{
+  guint32 size;
+  guint32 fourcc;
+  guint32 auxsize;
+
+  if (!read_atom_header (moovrf->file, &fourcc, &size))
+    return FALSE;
+  if (fourcc != FOURCC_stbl)
+    return FALSE;
+
+  trakrd->stbl_file_offset = ftell (moovrf->file) - 8;
+  trakrd->stbl_size = size;
+
+  /* skip the stsd */
+  if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
+    return FALSE;
+  if (fourcc != FOURCC_stsd)
+    return FALSE;
+  if (fseek (moovrf->file, auxsize - 8, SEEK_CUR) != 0)
+    return FALSE;
+
+  trakrd->stsd_size = auxsize;
+  trakrd->post_stsd_offset = ftell (moovrf->file);
+
+  /* as this is the last atom we parse, we don't skip forward */
+
+  return TRUE;
+}
+
+static gboolean
+moov_recov_parse_minf (MoovRecovFile * moovrf, TrakRecovData * trakrd)
+{
+  guint32 size;
+  guint32 fourcc;
+  guint32 auxsize;
+
+  if (!read_atom_header (moovrf->file, &fourcc, &size))
+    return FALSE;
+  if (fourcc != FOURCC_minf)
+    return FALSE;
+
+  trakrd->minf_file_offset = ftell (moovrf->file) - 8;
+  trakrd->minf_size = size;
+
+  /* skip either of vmhd, smhd, hmhd that might follow */
+  if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
+    return FALSE;
+  if (fourcc != FOURCC_vmhd && fourcc != FOURCC_smhd && fourcc != FOURCC_hmhd &&
+      fourcc != FOURCC_gmhd)
+    return FALSE;
+  if (fseek (moovrf->file, auxsize - 8, SEEK_CUR))
+    return FALSE;
+
+  /* skip a possible hdlr and the following dinf */
+  if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
+    return FALSE;
+  if (fourcc == FOURCC_hdlr) {
+    if (fseek (moovrf->file, auxsize - 8, SEEK_CUR))
+      return FALSE;
+    if (!read_atom_header (moovrf->file, &fourcc, &auxsize))
+      return FALSE;
+  }
+  if (fourcc != FOURCC_dinf)
+    return FALSE;
+  if (fseek (moovrf->file, auxsize - 8, SEEK_CUR))
+    return FALSE;
+
+  /* now we are ready to read the stbl */
+  if (!moov_recov_parse_stbl (moovrf, trakrd))
+    return FALSE;
+
+  return TRUE;
+}
+
+static gboolean
+moov_recov_parse_mdhd (MoovRecovFile * moovrf, TrakRecovData * trakrd)
+{
+  guint32 size;
+  guint32 fourcc;
+  guint8 data[4];
+
+  /* make sure we are on a tkhd atom */
+  if (!read_atom_header (moovrf->file, &fourcc, &size))
+    return FALSE;
+  if (fourcc != FOURCC_mdhd)
+    return FALSE;
+
+  trakrd->mdhd_file_offset = ftell (moovrf->file) - 8;
+
+  /* get the timescale */
+  if (fseek (moovrf->file, 12, SEEK_CUR) != 0)
+    return FALSE;
+  if (fread (data, 1, 4, moovrf->file) != 4)
+    return FALSE;
+  trakrd->timescale = GST_READ_UINT32_BE (data);
+  if (fseek (moovrf->file, 8, SEEK_CUR) != 0)
+    return FALSE;
+  return TRUE;
+}
+
+static gboolean
+moov_recov_parse_mdia (MoovRecovFile * moovrf, TrakRecovData * trakrd)
+{
+  guint32 size;
+  guint32 fourcc;
+
+  /* make sure we are on a tkhd atom */
+  if (!read_atom_header (moovrf->file, &fourcc, &size))
+    return FALSE;
+  if (fourcc != FOURCC_mdia)
+    return FALSE;
+
+  trakrd->mdia_file_offset = ftell (moovrf->file) - 8;
+  trakrd->mdia_size = size;
+
+  if (!moov_recov_parse_mdhd (moovrf, trakrd))
+    return FALSE;
+
+  if (!skip_atom (moovrf, FOURCC_hdlr))
+    return FALSE;
+  if (!moov_recov_parse_minf (moovrf, trakrd))
+    return FALSE;
+  return TRUE;
+}
+
+static gboolean
+moov_recov_parse_trak (MoovRecovFile * moovrf, TrakRecovData * trakrd)
+{
+  guint64 offset;
+  guint32 size;
+  guint32 fourcc;
+
+  offset = ftell (moovrf->file);
+  if (offset == -1) {
+    return FALSE;
+  }
+
+  /* make sure we are on a trak atom */
+  if (!read_atom_header (moovrf->file, &fourcc, &size)) {
+    return FALSE;
+  }
+  if (fourcc != FOURCC_trak) {
+    return FALSE;
+  }
+  trakrd->trak_size = size;
+
+  /* now we should have a trak header 'tkhd' */
+  if (!moov_recov_parse_tkhd (moovrf, trakrd))
+    return FALSE;
+
+  /* FIXME add edts handling here and in qtmux, as this is only detected
+   * after buffers start flowing */
+
+  if (!moov_recov_parse_mdia (moovrf, trakrd))
+    return FALSE;
+
+  trakrd->file_offset = offset;
+  /* position after the trak */
+  return fseek (moovrf->file, (long int) offset + size, SEEK_SET) == 0;
+}
+
+MoovRecovFile *
+moov_recov_file_create (FILE * file, GError ** err)
+{
+  gint i;
+  MoovRecovFile *moovrf = g_new0 (MoovRecovFile, 1);
+
+  g_return_val_if_fail (file != NULL, NULL);
+
+  moovrf->file = file;
+
+  /* look for ftyp and prefix at the start */
+  if (!moov_recov_file_parse_prefix (moovrf)) {
+    g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
+        "Error while parsing prefix atoms");
+    goto fail;
+  }
+
+  /* parse the mvhd */
+  if (!moov_recov_file_parse_mvhd (moovrf)) {
+    g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
+        "Error while parsing mvhd atom");
+    goto fail;
+  }
+
+  if (!moov_recov_parse_moov_timescale (moovrf)) {
+    g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
+        "Error while parsing timescale");
+    goto fail;
+  }
+  if (!moov_recov_parse_num_traks (moovrf)) {
+    g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
+        "Error while parsing parsing number of traks");
+    goto fail;
+  }
+
+  /* init the traks */
+  moovrf->traks_rd = g_new0 (TrakRecovData, moovrf->num_traks);
+  for (i = 0; i < moovrf->num_traks; i++) {
+    atom_stbl_init (&(moovrf->traks_rd[i].stbl));
+  }
+  for (i = 0; i < moovrf->num_traks; i++) {
+    if (!moov_recov_parse_trak (moovrf, &(moovrf->traks_rd[i]))) {
+      g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
+          "Error while parsing trak atom");
+      goto fail;
+    }
+  }
+
+  return moovrf;
+
+fail:
+  moov_recov_file_free (moovrf);
+  return NULL;
+}
+
+void
+moov_recov_file_free (MoovRecovFile * moovrf)
+{
+  gint i;
+  fclose (moovrf->file);
+  if (moovrf->traks_rd) {
+    for (i = 0; i < moovrf->num_traks; i++) {
+      atom_stbl_clear (&(moovrf->traks_rd[i].stbl));
+    }
+    g_free (moovrf->traks_rd);
+  }
+  g_free (moovrf);
+}
+
+static gboolean
+moov_recov_parse_buffer_entry (MoovRecovFile * moovrf, TrakBufferEntryInfo * b)
+{
+  guint8 data[TRAK_BUFFER_ENTRY_INFO_SIZE];
+  gint read;
+
+  read = fread (data, 1, TRAK_BUFFER_ENTRY_INFO_SIZE, moovrf->file);
+  if (read != TRAK_BUFFER_ENTRY_INFO_SIZE)
+    return FALSE;
+
+  b->track_id = GST_READ_UINT32_BE (data);
+  b->nsamples = GST_READ_UINT32_BE (data + 4);
+  b->delta = GST_READ_UINT32_BE (data + 8);
+  b->size = GST_READ_UINT32_BE (data + 12);
+  b->chunk_offset = GST_READ_UINT64_BE (data + 16);
+  b->sync = data[24] != 0;
+  b->do_pts = data[25] != 0;
+  b->pts_offset = GST_READ_UINT64_BE (data + 26);
+  return TRUE;
+}
+
+static gboolean
+mdat_recov_add_sample (MdatRecovFile * mdatrf, guint32 size)
+{
+  /* test if this data exists */
+  if (mdatrf->mdat_size - mdatrf->mdat_header_size + size > mdatrf->data_size)
+    return FALSE;
+
+  mdatrf->mdat_size += size;
+  return TRUE;
+}
+
+static TrakRecovData *
+moov_recov_get_trak (MoovRecovFile * moovrf, guint32 id)
+{
+  gint i;
+  for (i = 0; i < moovrf->num_traks; i++) {
+    if (moovrf->traks_rd[i].trak_id == id)
+      return &(moovrf->traks_rd[i]);
+  }
+  return NULL;
+}
+
+static void
+trak_recov_data_add_sample (TrakRecovData * trak, TrakBufferEntryInfo * b)
+{
+  trak->duration += b->nsamples * b->delta;
+  atom_stbl_add_samples (&trak->stbl, b->nsamples, b->delta, b->size,
+      b->chunk_offset, b->sync, b->do_pts, b->pts_offset);
+}
+
+/**
+ * Parses the buffer entries in the MoovRecovFile and matches the inputs
+ * with the data in the MdatRecovFile. Whenever a buffer entry of that
+ * represents 'x' bytes of data, the same amount of data is 'validated' in
+ * the MdatRecovFile and will be inluded in the generated moovie file.
+ */
+gboolean
+moov_recov_parse_buffers (MoovRecovFile * moovrf, MdatRecovFile * mdatrf,
+    GError ** err)
+{
+  TrakBufferEntryInfo entry;
+  TrakRecovData *trak;
+
+  /* we assume both moovrf and mdatrf are at the starting points of their
+   * data reading */
+  while (moov_recov_parse_buffer_entry (moovrf, &entry)) {
+    /* be sure we still have this data in mdat */
+    trak = moov_recov_get_trak (moovrf, entry.track_id);
+    if (trak == NULL) {
+      g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
+          "Invalid trak id found in buffer entry");
+      return FALSE;
+    }
+    if (!mdat_recov_add_sample (mdatrf, entry.size))
+      break;
+    trak_recov_data_add_sample (trak, &entry);
+  }
+  return TRUE;
+}
+
+guint32
+trak_recov_data_get_trak_atom_size (TrakRecovData * trak)
+{
+  AtomSTBL *stbl = &trak->stbl;
+  guint64 offset;
+
+  /* write out our stbl child atoms */
+  offset = 0;
+
+  if (!atom_stts_copy_data (&stbl->stts, NULL, NULL, &offset)) {
+    goto fail;
+  }
+  if (atom_array_get_len (&stbl->stss.entries) > 0) {
+    if (!atom_stss_copy_data (&stbl->stss, NULL, NULL, &offset)) {
+      goto fail;
+    }
+  }
+  if (!atom_stsc_copy_data (&stbl->stsc, NULL, NULL, &offset)) {
+    goto fail;
+  }
+  if (!atom_stsz_copy_data (&stbl->stsz, NULL, NULL, &offset)) {
+    goto fail;
+  }
+  if (stbl->ctts) {
+    if (!atom_ctts_copy_data (stbl->ctts, NULL, NULL, &offset)) {
+      goto fail;
+    }
+  }
+  if (!atom_stco64_copy_data (&stbl->stco64, NULL, NULL, &offset)) {
+    goto fail;
+  }
+
+  return trak->trak_size + ((trak->stsd_size + offset + 8) - trak->stbl_size);
+
+fail:
+  return 0;
+}
+
+guint8 *
+moov_recov_get_stbl_children_data (MoovRecovFile * moovrf, TrakRecovData * trak,
+    guint64 * p_size)
+{
+  AtomSTBL *stbl = &trak->stbl;
+  guint8 *buffer;
+  guint64 size;
+  guint64 offset;
+
+  /* write out our stbl child atoms
+   *
+   * Use 1MB as a starting size, *_copy_data functions
+   * will grow the buffer if needed.
+   */
+  size = 1024 * 1024;
+  buffer = g_malloc0 (size);
+  offset = 0;
+
+  if (!atom_stts_copy_data (&stbl->stts, &buffer, &size, &offset)) {
+    goto fail;
+  }
+  if (atom_array_get_len (&stbl->stss.entries) > 0) {
+    if (!atom_stss_copy_data (&stbl->stss, &buffer, &size, &offset)) {
+      goto fail;
+    }
+  }
+  if (!atom_stsc_copy_data (&stbl->stsc, &buffer, &size, &offset)) {
+    goto fail;
+  }
+  if (!atom_stsz_copy_data (&stbl->stsz, &buffer, &size, &offset)) {
+    goto fail;
+  }
+  if (stbl->ctts) {
+    if (!atom_ctts_copy_data (stbl->ctts, &buffer, &size, &offset)) {
+      goto fail;
+    }
+  }
+  if (!atom_stco64_copy_data (&stbl->stco64, &buffer, &size, &offset)) {
+    goto fail;
+  }
+  *p_size = offset;
+  return buffer;
+
+fail:
+  g_free (buffer);
+  return NULL;
+}
+
+gboolean
+moov_recov_write_file (MoovRecovFile * moovrf, MdatRecovFile * mdatrf,
+    FILE * outf, GError ** err)
+{
+  guint8 auxdata[16];
+  guint8 *data = NULL;
+  guint8 *prefix_data = NULL;
+  guint8 *mvhd_data = NULL;
+  guint8 *trak_data = NULL;
+  guint32 moov_size = 0;
+  gint i;
+  guint64 stbl_children_size = 0;
+  guint8 *stbl_children = NULL;
+  guint32 longest_duration = 0;
+  guint16 version;
+
+  /* check the version */
+  if (fseek (moovrf->file, 0, SEEK_SET) != 0) {
+    g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
+        "Failed to seek to the start of the moov recovery file");
+    goto fail;
+  }
+  if (fread (auxdata, 1, 2, moovrf->file) != 2) {
+    g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
+        "Failed to read version from file");
+  }
+
+  version = GST_READ_UINT16_BE (auxdata);
+  if (version != ATOMS_RECOV_FILE_VERSION) {
+    g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_VERSION,
+        "Input file version (%u) is not supported in this version (%u)",
+        version, ATOMS_RECOV_FILE_VERSION);
+    return FALSE;
+  }
+
+  /* write the ftyp */
+  prefix_data = g_malloc (moovrf->prefix_size);
+  if (fread (prefix_data, 1, moovrf->prefix_size,
+          moovrf->file) != moovrf->prefix_size) {
+    g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
+        "Failed to read the ftyp atom from file");
+    goto fail;
+  }
+  if (fwrite (prefix_data, 1, moovrf->prefix_size, outf) != moovrf->prefix_size) {
+    ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
+    goto fail;
+  }
+  g_free (prefix_data);
+  prefix_data = NULL;
+
+  /* need to calculate the moov size beforehand to add the offset to
+   * chunk offset entries */
+  moov_size += moovrf->mvhd_size + 8;   /* mvhd + moov size + fourcc */
+  for (i = 0; i < moovrf->num_traks; i++) {
+    TrakRecovData *trak = &(moovrf->traks_rd[i]);
+    guint32 duration;           /* in moov's timescale */
+    guint32 trak_size;
+
+    /* convert trak duration to moov's duration */
+    duration = gst_util_uint64_scale_round (trak->duration, moovrf->timescale,
+        trak->timescale);
+
+    if (duration > longest_duration)
+      longest_duration = duration;
+    trak_size = trak_recov_data_get_trak_atom_size (trak);
+    if (trak_size == 0) {
+      g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_GENERIC,
+          "Failed to estimate trak atom size");
+      goto fail;
+    }
+    moov_size += trak_size;
+  }
+
+  /* add chunks offsets */
+  for (i = 0; i < moovrf->num_traks; i++) {
+    TrakRecovData *trak = &(moovrf->traks_rd[i]);
+    /* 16 for the mdat header */
+    gint64 offset = moov_size + ftell (outf) + 16;
+    atom_stco64_chunks_add_offset (&trak->stbl.stco64, offset);
+  }
+
+  /* write the moov */
+  GST_WRITE_UINT32_BE (auxdata, moov_size);
+  GST_WRITE_UINT32_LE (auxdata + 4, FOURCC_moov);
+  if (fwrite (auxdata, 1, 8, outf) != 8) {
+    ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
+    goto fail;
+  }
+
+  /* write the mvhd */
+  mvhd_data = g_malloc (moovrf->mvhd_size);
+  if (fseek (moovrf->file, moovrf->mvhd_pos, SEEK_SET) != 0)
+    goto fail;
+  if (fread (mvhd_data, 1, moovrf->mvhd_size,
+          moovrf->file) != moovrf->mvhd_size)
+    goto fail;
+  GST_WRITE_UINT32_BE (mvhd_data + 20, moovrf->timescale);
+  GST_WRITE_UINT32_BE (mvhd_data + 24, longest_duration);
+  if (fwrite (mvhd_data, 1, moovrf->mvhd_size, outf) != moovrf->mvhd_size) {
+    ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
+    goto fail;
+  }
+  g_free (mvhd_data);
+  mvhd_data = NULL;
+
+  /* write the traks, this is the tough part because we need to update:
+   * - stbl atom
+   * - sizes of atoms from stbl to trak
+   * - trak duration
+   */
+  for (i = 0; i < moovrf->num_traks; i++) {
+    TrakRecovData *trak = &(moovrf->traks_rd[i]);
+    guint trak_data_size;
+    guint32 stbl_new_size;
+    guint32 minf_new_size;
+    guint32 mdia_new_size;
+    guint32 trak_new_size;
+    guint32 size_diff;
+    guint32 duration;           /* in moov's timescale */
+
+    /* convert trak duration to moov's duration */
+    duration = gst_util_uint64_scale_round (trak->duration, moovrf->timescale,
+        trak->timescale);
+
+    stbl_children = moov_recov_get_stbl_children_data (moovrf, trak,
+        &stbl_children_size);
+    if (stbl_children == NULL)
+      goto fail;
+
+    /* calc the new size of the atoms from stbl to trak in the atoms tree */
+    stbl_new_size = trak->stsd_size + stbl_children_size + 8;
+    size_diff = stbl_new_size - trak->stbl_size;
+    minf_new_size = trak->minf_size + size_diff;
+    mdia_new_size = trak->mdia_size + size_diff;
+    trak_new_size = trak->trak_size + size_diff;
+
+    if (fseek (moovrf->file, trak->file_offset, SEEK_SET) != 0)
+      goto fail;
+    trak_data_size = trak->post_stsd_offset - trak->file_offset;
+    trak_data = g_malloc (trak_data_size);
+    if (fread (trak_data, 1, trak_data_size, moovrf->file) != trak_data_size) {
+      goto fail;
+    }
+    /* update the size values in those read atoms before writing */
+    GST_WRITE_UINT32_BE (trak_data, trak_new_size);
+    GST_WRITE_UINT32_BE (trak_data + (trak->mdia_file_offset -
+            trak->file_offset), mdia_new_size);
+    GST_WRITE_UINT32_BE (trak_data + (trak->minf_file_offset -
+            trak->file_offset), minf_new_size);
+    GST_WRITE_UINT32_BE (trak_data + (trak->stbl_file_offset -
+            trak->file_offset), stbl_new_size);
+
+    /* update duration values in tkhd and mdhd */
+    GST_WRITE_UINT32_BE (trak_data + (trak->tkhd_file_offset -
+            trak->file_offset) + 28, duration);
+    GST_WRITE_UINT32_BE (trak_data + (trak->mdhd_file_offset -
+            trak->file_offset) + 24, trak->duration);
+
+    if (fwrite (trak_data, 1, trak_data_size, outf) != trak_data_size) {
+      ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
+      goto fail;
+    }
+    if (fwrite (stbl_children, 1, stbl_children_size, outf) !=
+        stbl_children_size) {
+      ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
+      goto fail;
+    }
+    g_free (trak_data);
+    trak_data = NULL;
+    g_free (stbl_children);
+    stbl_children = NULL;
+  }
+
+  /* write the mdat */
+  /* write the header first */
+  GST_WRITE_UINT32_BE (auxdata, 1);
+  GST_WRITE_UINT32_LE (auxdata + 4, FOURCC_mdat);
+  GST_WRITE_UINT64_BE (auxdata + 8, mdatrf->mdat_size);
+  if (fwrite (auxdata, 1, 16, outf) != 16) {
+    ATOMS_RECOV_OUTPUT_WRITE_ERROR (err);
+    goto fail;
+  }
+
+  /* now read the mdat data and output to the file */
+  if (fseek (mdatrf->file, mdatrf->mdat_start +
+          (mdatrf->rawfile ? 0 : mdatrf->mdat_header_size), SEEK_SET) != 0)
+    goto fail;
+
+  data = g_malloc (4096);
+  while (!feof (mdatrf->file)) {
+    gint read = fread (data, 1, 4096, mdatrf->file);
+    fwrite (data, 1, read, outf);
+  }
+  g_free (data);
+
+  return TRUE;
+
+fail:
+  g_free (stbl_children);
+  g_free (mvhd_data);
+  g_free (prefix_data);
+  g_free (trak_data);
+  g_free (data);
+  return FALSE;
+}
diff --git a/gst/quicktime/atomsrecovery.h b/gst/quicktime/atomsrecovery.h
new file mode 100644 (file)
index 0000000..4dffc48
--- /dev/null
@@ -0,0 +1,159 @@
+/* Quicktime muxer plugin for GStreamer
+ * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+/*
+ * Unless otherwise indicated, Source Code is licensed under MIT license.
+ * See further explanation attached in License Statement (distributed in the file
+ * LICENSE).
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef __ATOMS_RECOVERY_H__
+#define __ATOMS_RECOVERY_H__
+
+#include <glib.h>
+#include <string.h>
+#include <stdio.h>
+#include <gst/gst.h>
+
+#include "atoms.h"
+
+/* Version to be incremented each time we decide
+ * to change the file layout */
+#define ATOMS_RECOV_FILE_VERSION          1
+
+#define ATOMS_RECOV_QUARK (g_quark_from_string ("qtmux-atoms-recovery"))
+
+/* gerror error codes */
+#define ATOMS_RECOV_ERR_GENERIC           1
+#define ATOMS_RECOV_ERR_FILE              2
+#define ATOMS_RECOV_ERR_PARSING           3
+#define ATOMS_RECOV_ERR_VERSION           4
+
+/* this struct represents each buffer in a moov file, containing the info
+ * that is placed in the stsd children atoms
+ * Fields should be writen in BE order, and booleans should be writen as
+ * 1byte with 0 for false, anything otherwise */
+#define TRAK_BUFFER_ENTRY_INFO_SIZE 34
+typedef struct
+{
+  guint32   track_id;
+  guint32   nsamples;
+  guint32   delta;
+  guint32   size;
+  guint64   chunk_offset;
+  guint64   pts_offset;
+  gboolean  sync;
+  gboolean  do_pts;
+} TrakBufferEntryInfo;
+
+typedef struct
+{
+  guint32 trak_id;
+  guint32 duration;  /* duration in trak timescale */
+  guint32 timescale; /* trak's timescale */
+
+  guint64 file_offset;
+
+  /* need for later updating duration */
+  guint64 tkhd_file_offset;
+  guint64 mdhd_file_offset;
+
+  /* need these offsets to update size */
+  guint32 trak_size;
+  guint64 mdia_file_offset;
+  guint32 mdia_size;
+  guint64 minf_file_offset;
+  guint32 minf_size;
+  guint64 stbl_file_offset;
+  guint32 stbl_size;
+
+  guint64 post_stsd_offset;
+  guint32 stsd_size;
+
+  /* for storing the samples info */
+  AtomSTBL stbl;
+} TrakRecovData;
+
+typedef struct
+{
+  FILE * file;
+  gboolean rawfile;
+
+  /* results from parsing the input file */
+  guint64   data_size;
+  guint32   mdat_header_size;
+  guint     mdat_start;
+
+  guint64   mdat_size;
+} MdatRecovFile;
+
+typedef struct
+{
+  FILE * file;
+  guint32 timescale;
+
+  guint32 mvhd_pos;
+  guint32 mvhd_size;
+  guint32 prefix_size; /* prefix + ftyp total size */
+
+  gint num_traks;
+  TrakRecovData *traks_rd;
+} MoovRecovFile;
+
+gboolean atoms_recov_write_trak_info      (FILE * f, AtomTRAK * trak);
+gboolean atoms_recov_write_headers        (FILE * f, AtomFTYP * ftyp,
+                                           GstBuffer * prefix, AtomMOOV * moov,
+                                           guint32 timescale,
+                                           guint32 traks_number);
+gboolean atoms_recov_write_trak_samples   (FILE * f, AtomTRAK * trak,
+                                           guint32 nsamples, guint32 delta,
+                                           guint32 size, guint64 chunk_offset,
+                                           gboolean sync, gboolean do_pts,
+                                           gint64 pts_offset);
+
+MdatRecovFile * mdat_recov_file_create   (FILE * file, gboolean datafile,
+                                          GError ** err);
+void            mdat_recov_file_free     (MdatRecovFile * mrf);
+MoovRecovFile * moov_recov_file_create   (FILE * file, GError ** err);
+void            moov_recov_file_free     (MoovRecovFile * moovrf);
+gboolean        moov_recov_parse_buffers (MoovRecovFile * moovrf,
+                                          MdatRecovFile * mdatrf,
+                                          GError ** err);
+gboolean        moov_recov_write_file    (MoovRecovFile * moovrf,
+                                          MdatRecovFile * mdatrf, FILE * outf,
+                                          GError ** err);
+
+#endif /* __ATOMS_RECOVERY_H__ */
index 0581793..f859e0c 100644 (file)
@@ -77,6 +77,7 @@ G_BEGIN_DECLS
 #define FOURCC_vmhd     GST_MAKE_FOURCC('v','m','h','d')
 #define FOURCC_smhd     GST_MAKE_FOURCC('s','m','h','d')
 #define FOURCC_gmhd     GST_MAKE_FOURCC('g','m','h','d')
+#define FOURCC_hmhd     GST_MAKE_FOURCC('h','m','h','d')
 #define FOURCC_gmin     GST_MAKE_FOURCC('g','m','i','n')
 #define FOURCC_dinf     GST_MAKE_FOURCC('d','i','n','f')
 #define FOURCC_dref     GST_MAKE_FOURCC('d','r','e','f')
diff --git a/gst/quicktime/gstqtmoovrecover.c b/gst/quicktime/gstqtmoovrecover.c
new file mode 100644 (file)
index 0000000..bddfa14
--- /dev/null
@@ -0,0 +1,391 @@
+/* Quicktime muxer plugin for GStreamer
+ * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+/*
+ * Unless otherwise indicated, Source Code is licensed under MIT license.
+ * See further explanation attached in License Statement (distributed in the file
+ * LICENSE).
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+
+/**
+ * SECTION:gstqtmoovrecover
+ * @short_description: Utility element for recovering unfinished quicktime files
+ *
+ * <refsect2>
+ * <para>
+ * This element recovers quicktime files created with qtmux using the moov recovery feature.
+ * </para>
+ * <title>Example pipelines</title>
+ * <para>
+ * <programlisting>
+ * TODO
+ * </programlisting>
+ * </refsect2>
+ *
+ * Last reviewed on 2010-02-01
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib/gstdio.h>
+#include <gst/gst.h>
+
+#include "gstqtmoovrecover.h"
+
+GST_DEBUG_CATEGORY_STATIC (gst_qt_moov_recover_debug);
+#define GST_CAT_DEFAULT gst_qt_moov_recover_debug
+
+static GstElementDetails gst_qt_moov_recover_details =
+GST_ELEMENT_DETAILS ("QT Moov Recover",
+    "Util", "Recovers unfinished qtmux files",
+    "Thiago Santos <thiago.sousa.santos@collabora.co.uk>");
+
+/* QTMoovRecover signals and args */
+enum
+{
+  /* FILL ME */
+  LAST_SIGNAL
+};
+
+enum
+{
+  PROP_0,
+  PROP_RECOVERY_INPUT,
+  PROP_BROKEN_INPUT,
+  PROP_FIXED_OUTPUT,
+  PROP_FAST_START_MODE
+};
+
+GST_BOILERPLATE (GstQTMoovRecover, gst_qt_moov_recover, GstPipeline,
+    GST_TYPE_PIPELINE);
+
+/* property functions */
+static void gst_qt_moov_recover_set_property (GObject * object,
+    guint prop_id, const GValue * value, GParamSpec * pspec);
+static void gst_qt_moov_recover_get_property (GObject * object,
+    guint prop_id, GValue * value, GParamSpec * pspec);
+
+static GstStateChangeReturn gst_qt_moov_recover_change_state (GstElement *
+    element, GstStateChange transition);
+
+static void gst_qt_moov_recover_finalize (GObject * object);
+
+static void
+gst_qt_moov_recover_base_init (gpointer g_class)
+{
+  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
+#if 0
+  GstQTMoovRecoverClass *klass = (GstQTMoovRecoverClass *) g_class;
+#endif
+  gst_element_class_set_details (element_class, &gst_qt_moov_recover_details);
+}
+
+static void
+gst_qt_moov_recover_class_init (GstQTMoovRecoverClass * klass)
+{
+  GObjectClass *gobject_class;
+  GstElementClass *gstelement_class;
+
+  gobject_class = (GObjectClass *) klass;
+  gstelement_class = (GstElementClass *) klass;
+
+  parent_class = g_type_class_peek_parent (klass);
+
+  gobject_class->finalize = gst_qt_moov_recover_finalize;
+  gobject_class->get_property = gst_qt_moov_recover_get_property;
+  gobject_class->set_property = gst_qt_moov_recover_set_property;
+
+  gstelement_class->change_state = gst_qt_moov_recover_change_state;
+
+  g_object_class_install_property (gobject_class, PROP_FIXED_OUTPUT,
+      g_param_spec_string ("fixed-output",
+          "Path to write the fixed file",
+          "Path to write the fixed file to (used as output)",
+          NULL, G_PARAM_READWRITE));
+  g_object_class_install_property (gobject_class, PROP_BROKEN_INPUT,
+      g_param_spec_string ("broken-input",
+          "Path to broken input file",
+          "Path to broken input file. (If qtmux was on faststart mode, this "
+          "file is the faststart file)", NULL, G_PARAM_READWRITE));
+  g_object_class_install_property (gobject_class, PROP_RECOVERY_INPUT,
+      g_param_spec_string ("recovery-input",
+          "Path to recovery file",
+          "Path to recovery file (used as input)", NULL, G_PARAM_READWRITE));
+  g_object_class_install_property (gobject_class, PROP_FAST_START_MODE,
+      g_param_spec_boolean ("faststart-mode",
+          "If the broken input is from faststart mode",
+          "If the broken input is from faststart mode",
+          FALSE, G_PARAM_READWRITE));
+
+  GST_DEBUG_CATEGORY_INIT (gst_qt_moov_recover_debug, "qtmoovrecover", 0,
+      "QT Moovie Recover");
+}
+
+static void
+gst_qt_moov_recover_init (GstQTMoovRecover * qtmr,
+    GstQTMoovRecoverClass * qtmr_klass)
+{
+}
+
+static void
+gst_qt_moov_recover_finalize (GObject * object)
+{
+  G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_qt_moov_recover_run (void *data)
+{
+  FILE *moovrec = NULL;
+  FILE *mdatinput = NULL;
+  FILE *output = NULL;
+  MdatRecovFile *mdat_recov = NULL;
+  MoovRecovFile *moov_recov = NULL;
+  GstQTMoovRecover *qtmr = GST_QT_MOOV_RECOVER_CAST (data);
+  GError *err = NULL;
+
+  GST_LOG_OBJECT (qtmr, "Starting task");
+
+  GST_DEBUG_OBJECT (qtmr, "Validating properties");
+  GST_OBJECT_LOCK (qtmr);
+  /* validate properties */
+  if (qtmr->broken_input == NULL) {
+    GST_OBJECT_UNLOCK (qtmr);
+    GST_ELEMENT_ERROR (qtmr, RESOURCE, SETTINGS,
+        ("Please set broken-input property"), NULL);
+    goto end;
+  }
+  if (qtmr->recovery_input == NULL) {
+    GST_OBJECT_UNLOCK (qtmr);
+    GST_ELEMENT_ERROR (qtmr, RESOURCE, SETTINGS,
+        ("Please set recovery-input property"), NULL);
+    goto end;
+  }
+  if (qtmr->fixed_output == NULL) {
+    GST_OBJECT_UNLOCK (qtmr);
+    GST_ELEMENT_ERROR (qtmr, RESOURCE, SETTINGS,
+        ("Please set fixed-output property"), NULL);
+    goto end;
+  }
+
+  GST_DEBUG_OBJECT (qtmr, "Opening input/output files");
+  /* open files */
+  moovrec = g_fopen (qtmr->recovery_input, "rb");
+  if (moovrec == NULL) {
+    GST_OBJECT_UNLOCK (qtmr);
+    GST_ELEMENT_ERROR (qtmr, RESOURCE, OPEN_READ,
+        ("Failed to open recovery-input file"), NULL);
+    goto end;
+  }
+
+  mdatinput = g_fopen (qtmr->broken_input, "rb");
+  if (mdatinput == NULL) {
+    GST_OBJECT_UNLOCK (qtmr);
+    GST_ELEMENT_ERROR (qtmr, RESOURCE, OPEN_READ,
+        ("Failed to open broken-input file"), NULL);
+    goto end;
+  }
+  output = g_fopen (qtmr->fixed_output, "wb+");
+  if (output == NULL) {
+    GST_OBJECT_UNLOCK (qtmr);
+    GST_ELEMENT_ERROR (qtmr, RESOURCE, OPEN_READ_WRITE,
+        ("Failed to open fixed-output file"), NULL);
+    goto end;
+  }
+  GST_OBJECT_UNLOCK (qtmr);
+
+  GST_DEBUG_OBJECT (qtmr, "Parsing input files");
+  /* now create our structures */
+  mdat_recov = mdat_recov_file_create (mdatinput, qtmr->faststart_mode, &err);
+  mdatinput = NULL;
+  if (mdat_recov == NULL) {
+    GST_ELEMENT_ERROR (qtmr, RESOURCE, FAILED,
+        ("Broken file could not be parsed correctly"), NULL);
+    goto end;
+  }
+  moov_recov = moov_recov_file_create (moovrec, &err);
+  moovrec = NULL;
+  if (moov_recov == NULL) {
+    GST_ELEMENT_ERROR (qtmr, RESOURCE, FAILED,
+        ("Recovery file could not be parsed correctly"), NULL);
+    goto end;
+  }
+
+  /* now parse the buffers data from moovrec */
+  if (!moov_recov_parse_buffers (moov_recov, mdat_recov, &err)) {
+    goto end;
+  }
+
+  GST_DEBUG_OBJECT (qtmr, "Writing fixed file to output");
+  if (!moov_recov_write_file (moov_recov, mdat_recov, output, &err)) {
+    goto end;
+  }
+
+  /* here means success */
+  GST_DEBUG_OBJECT (qtmr, "Finished successfully, posting EOS");
+  gst_element_post_message (GST_ELEMENT_CAST (qtmr),
+      gst_message_new_eos (GST_OBJECT_CAST (qtmr)));
+
+end:
+  GST_LOG_OBJECT (qtmr, "Finalizing task");
+  if (err) {
+    GST_ELEMENT_ERROR (qtmr, RESOURCE, FAILED, (err->message), NULL);
+    g_error_free (err);
+  }
+
+  if (moov_recov)
+    moov_recov_file_free (moov_recov);
+  if (moovrec)
+    fclose (moovrec);
+
+  if (mdat_recov)
+    mdat_recov_file_free (mdat_recov);
+  if (mdatinput)
+    fclose (mdatinput);
+
+  if (output)
+    fclose (output);
+  GST_LOG_OBJECT (qtmr, "Leaving task");
+  gst_task_stop (qtmr->task);
+}
+
+static void
+gst_qt_moov_recover_get_property (GObject * object,
+    guint prop_id, GValue * value, GParamSpec * pspec)
+{
+  GstQTMoovRecover *qtmr = GST_QT_MOOV_RECOVER_CAST (object);
+
+  GST_OBJECT_LOCK (qtmr);
+  switch (prop_id) {
+    case PROP_FAST_START_MODE:
+      g_value_set_boolean (value, qtmr->faststart_mode);
+      break;
+    case PROP_BROKEN_INPUT:
+      g_value_set_string (value, qtmr->broken_input);
+      break;
+    case PROP_RECOVERY_INPUT:
+      g_value_set_string (value, qtmr->recovery_input);
+      break;
+    case PROP_FIXED_OUTPUT:
+      g_value_set_string (value, qtmr->fixed_output);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+  GST_OBJECT_UNLOCK (qtmr);
+}
+
+static void
+gst_qt_moov_recover_set_property (GObject * object,
+    guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+  GstQTMoovRecover *qtmr = GST_QT_MOOV_RECOVER_CAST (object);
+
+  GST_OBJECT_LOCK (qtmr);
+  switch (prop_id) {
+    case PROP_FAST_START_MODE:
+      qtmr->faststart_mode = g_value_get_boolean (value);
+      break;
+    case PROP_BROKEN_INPUT:
+      g_free (qtmr->broken_input);
+      qtmr->broken_input = g_value_dup_string (value);
+      break;
+    case PROP_RECOVERY_INPUT:
+      g_free (qtmr->recovery_input);
+      qtmr->recovery_input = g_value_dup_string (value);
+      break;
+    case PROP_FIXED_OUTPUT:
+      g_free (qtmr->fixed_output);
+      qtmr->fixed_output = g_value_dup_string (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+  GST_OBJECT_UNLOCK (qtmr);
+}
+
+static GstStateChangeReturn
+gst_qt_moov_recover_change_state (GstElement * element,
+    GstStateChange transition)
+{
+  GstStateChangeReturn ret;
+  GstQTMoovRecover *qtmr = GST_QT_MOOV_RECOVER_CAST (element);
+
+  switch (transition) {
+    case GST_STATE_CHANGE_NULL_TO_READY:
+      qtmr->task = gst_task_create (gst_qt_moov_recover_run, qtmr);
+      qtmr->task_mutex = g_new (GStaticRecMutex, 1);
+      g_static_rec_mutex_init (qtmr->task_mutex);
+      gst_task_set_lock (qtmr->task, qtmr->task_mutex);
+      break;
+    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+      gst_task_start (qtmr->task);
+      break;
+    default:
+      break;
+  }
+
+  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+
+  switch (transition) {
+    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+      gst_task_stop (qtmr->task);
+      gst_task_join (qtmr->task);
+      break;
+    case GST_STATE_CHANGE_READY_TO_NULL:
+      g_assert (gst_task_get_state (qtmr->task) == GST_TASK_STOPPED);
+      gst_object_unref (qtmr->task);
+      qtmr->task = NULL;
+      g_static_rec_mutex_free (qtmr->task_mutex);
+      break;
+    default:
+      break;
+  }
+  return ret;
+}
+
+
+gboolean
+gst_qt_moov_recover_register (GstPlugin * plugin)
+{
+  return gst_element_register (plugin, "qtmoovrecover", GST_RANK_NONE,
+      GST_TYPE_QT_MOOV_RECOVER);
+}
diff --git a/gst/quicktime/gstqtmoovrecover.h b/gst/quicktime/gstqtmoovrecover.h
new file mode 100644 (file)
index 0000000..07dc9d9
--- /dev/null
@@ -0,0 +1,88 @@
+/* Quicktime muxer plugin for GStreamer
+ * Copyright (C) 2010 Thiago Santos <thiago.sousa.santos@collabora.co.uk>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+/*
+ * Unless otherwise indicated, Source Code is licensed under MIT license.
+ * See further explanation attached in License Statement (distributed in the file
+ * LICENSE).
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef __GST_QT_MOOV_RECOVER_H__
+#define __GST_QT_MOOV_RECOVER_H__
+
+#include <gst/gst.h>
+
+#include "atoms.h"
+#include "atomsrecovery.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_QT_MOOV_RECOVER (gst_qt_moov_recover_get_type())
+#define GST_QT_MOOV_RECOVER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_QT_MOOV_RECOVER, GstQTMoovRecover))
+#define GST_QT_MOOV_RECOVER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_QT_MOOV_RECOVER, GstQTMoovRecover))
+#define GST_IS_QT_MOOV_RECOVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_QT_MOOV_RECOVER))
+#define GST_IS_QT_MOOV_RECOVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_QT_MOOV_RECOVER))
+#define GST_QT_MOOV_RECOVER_CAST(obj) ((GstQTMoovRecover*)(obj))
+
+
+typedef struct _GstQTMoovRecover GstQTMoovRecover;
+typedef struct _GstQTMoovRecoverClass GstQTMoovRecoverClass;
+
+struct _GstQTMoovRecover
+{
+  GstPipeline pipeline;
+
+  GstTask *task;
+  GStaticRecMutex *task_mutex;
+
+  /* properties */
+  gboolean  faststart_mode;
+  gchar    *recovery_input;
+  gchar    *fixed_output;
+  gchar    *broken_input;
+};
+
+struct _GstQTMoovRecoverClass
+{
+  GstPipelineClass parent_class;
+};
+
+GType gst_qt_moov_recover_get_type (void);
+gboolean gst_qt_moov_recover_register (GstPlugin * plugin);
+
+G_END_DECLS
+
+#endif /* __GST_QT_MOOV_RECOVER_H__ */
index d44cd9a..2de1ba9 100644 (file)
@@ -1,5 +1,5 @@
 /* Quicktime muxer plugin for GStreamer
- * Copyright (C) 2008 Thiago Sousa Santos <thiagoss@embedded.ufcg.edu.br>
+ * Copyright (C) 2008-2010 Thiago Santos <thiagoss@embedded.ufcg.edu.br>
  * Copyright (C) 2008 Mark Nauwelaerts <mnauw@users.sf.net>
  *
  * This library is free software; you can redistribute it and/or
@@ -108,7 +108,8 @@ enum
   PROP_DO_CTTS,
   PROP_FLAVOR,
   PROP_FAST_START,
-  PROP_FAST_START_TEMP_FILE
+  PROP_FAST_START_TEMP_FILE,
+  PROP_MOOV_RECOV_FILE
 };
 
 /* some spare for header size as well */
@@ -120,6 +121,7 @@ enum
 #define DEFAULT_DO_CTTS                 FALSE
 #define DEFAULT_FAST_START              FALSE
 #define DEFAULT_FAST_START_TEMP_FILE    NULL
+#define DEFAULT_MOOV_RECOV_FILE         NULL
 
 static void gst_qt_mux_finalize (GObject * object);
 
@@ -234,11 +236,19 @@ gst_qt_mux_class_init (GstQTMuxClass * klass)
           "when creating a faststart file. If null a filepath will be "
           "created automatically", DEFAULT_FAST_START_TEMP_FILE,
           G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+  g_object_class_install_property (gobject_class, PROP_MOOV_RECOV_FILE,
+      g_param_spec_string ("moov-recovery-file", "File to store data for "
+          "posterior moov atom recovery", "File to be used to store "
+          "data for moov atom making movie file recovery possible in case "
+          "of a crash during muxing. Null for disabled. (Experimental)",
+          DEFAULT_MOOV_RECOV_FILE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
 
   gstelement_class->request_new_pad =
       GST_DEBUG_FUNCPTR (gst_qt_mux_request_new_pad);
   gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_qt_mux_change_state);
   gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_qt_mux_release_pad);
+
+  GST_DEBUG_CATEGORY_INIT (gst_qt_mux_debug, "qtmux", 0, "QT Muxer");
 }
 
 static void
@@ -288,6 +298,10 @@ gst_qt_mux_reset (GstQTMux * qtmux, gboolean alloc)
     fclose (qtmux->fast_start_file);
     qtmux->fast_start_file = NULL;
   }
+  if (qtmux->moov_recov_file) {
+    fclose (qtmux->moov_recov_file);
+    qtmux->moov_recov_file = NULL;
+  }
   gst_tag_setter_reset_tags (GST_TAG_SETTER (qtmux));
 
   /* reset pad data */
@@ -350,6 +364,7 @@ gst_qt_mux_finalize (GObject * object)
   gst_qt_mux_reset (qtmux, FALSE);
 
   g_free (qtmux->fast_start_file_path);
+  g_free (qtmux->moov_recov_file_path);
 
   atoms_context_free (qtmux->context);
   gst_object_unref (qtmux->collect);
@@ -1116,7 +1131,7 @@ gst_qt_mux_send_mdat_header (GstQTMux * qtmux, guint64 * off, guint64 size,
 serialize_error:
   {
     GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
-        ("Failed to serialize ftyp"));
+        ("Failed to serialize mdat"));
     return GST_FLOW_ERROR;
   }
 }
@@ -1188,27 +1203,49 @@ serialize_error:
   }
 }
 
-static GstFlowReturn
-gst_qt_mux_prepare_and_send_ftyp (GstQTMux * qtmux)
+static void
+gst_qt_mux_prepare_ftyp (GstQTMux * qtmux, AtomFTYP ** p_ftyp,
+    GstBuffer ** p_prefix)
 {
-  GstFlowReturn ret = GST_FLOW_OK;
   GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux));
   guint32 major, version;
   GList *comp;
-  GstBuffer *prefix;
+  GstBuffer *prefix = NULL;
+  AtomFTYP *ftyp = NULL;
 
-  GST_DEBUG_OBJECT (qtmux, "Preparing to send ftyp atom");
+  GST_DEBUG_OBJECT (qtmux, "Preparing ftyp and possible prefix atom");
 
   /* init and send context and ftyp based on current property state */
-  if (qtmux->ftyp)
-    atom_ftyp_free (qtmux->ftyp);
   gst_qt_mux_map_format_to_header (qtmux_klass->format, &prefix, &major,
       &version, &comp, qtmux->moov, qtmux->longest_chunk,
       qtmux->fast_start_file != NULL);
-  qtmux->ftyp = atom_ftyp_new (qtmux->context, major, version, comp);
+  ftyp = atom_ftyp_new (qtmux->context, major, version, comp);
   if (comp)
     g_list_free (comp);
   if (prefix) {
+    if (p_prefix)
+      *p_prefix = prefix;
+    else
+      gst_buffer_unref (prefix);
+  }
+  *p_ftyp = ftyp;
+}
+
+static GstFlowReturn
+gst_qt_mux_prepare_and_send_ftyp (GstQTMux * qtmux)
+{
+  GstFlowReturn ret = GST_FLOW_OK;
+  GstBuffer *prefix = NULL;
+
+  GST_DEBUG_OBJECT (qtmux, "Preparing to send ftyp atom");
+
+  /* init and send context and ftyp based on current property state */
+  if (qtmux->ftyp) {
+    atom_ftyp_free (qtmux->ftyp);
+    qtmux->ftyp = NULL;
+  }
+  gst_qt_mux_prepare_ftyp (qtmux, &qtmux->ftyp, &prefix);
+  if (prefix) {
     ret = gst_qt_mux_send_buffer (qtmux, prefix, &qtmux->header_size, FALSE);
     if (ret != GST_FLOW_OK)
       return ret;
@@ -1227,6 +1264,57 @@ gst_qt_mux_start_file (GstQTMux * qtmux)
   gst_pad_push_event (qtmux->srcpad,
       gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_BYTES, 0, -1, 0));
 
+  /* initialize our moov recovery file */
+  GST_OBJECT_LOCK (qtmux);
+  if (qtmux->moov_recov_file_path) {
+    GST_DEBUG_OBJECT (qtmux, "Openning moov recovery file: %s",
+        qtmux->moov_recov_file_path);
+    qtmux->moov_recov_file = g_fopen (qtmux->moov_recov_file_path, "wb+");
+    if (qtmux->moov_recov_file == NULL) {
+      GST_WARNING_OBJECT (qtmux, "Failed to open moov recovery file in %s",
+          qtmux->moov_recov_file_path);
+    } else {
+      GSList *walk;
+      gboolean fail = FALSE;
+      AtomFTYP *ftyp = NULL;
+      GstBuffer *prefix = NULL;
+
+      gst_qt_mux_prepare_ftyp (qtmux, &ftyp, &prefix);
+
+      if (!atoms_recov_write_headers (qtmux->moov_recov_file, ftyp, prefix,
+              qtmux->moov, qtmux->timescale,
+              g_slist_length (qtmux->collect->data))) {
+        GST_WARNING_OBJECT (qtmux, "Failed to write moov recovery file "
+            "headers");
+        fail = TRUE;
+      }
+
+      atom_ftyp_free (ftyp);
+      if (prefix)
+        gst_buffer_unref (prefix);
+
+      for (walk = qtmux->collect->data; walk && !fail;
+          walk = g_slist_next (walk)) {
+        GstCollectData *cdata = (GstCollectData *) walk->data;
+        GstQTPad *qpad = (GstQTPad *) cdata;
+        /* write info for each stream */
+        fail = atoms_recov_write_trak_info (qtmux->moov_recov_file, qpad->trak);
+        if (fail) {
+          GST_WARNING_OBJECT (qtmux, "Failed to write trak info to recovery "
+              "file");
+        }
+      }
+      if (fail) {
+        /* cleanup */
+        fclose (qtmux->moov_recov_file);
+        qtmux->moov_recov_file = NULL;
+        GST_WARNING_OBJECT (qtmux, "An error was detected while writing to "
+            "recover file, moov recovery won't work");
+      }
+    }
+  }
+  GST_OBJECT_UNLOCK (qtmux);
+
   /* 
    * send mdat header if already needed, and mark position for later update.
    * We don't send ftyp now if we are on fast start mode, because we can
@@ -1606,6 +1694,16 @@ gst_qt_mux_add_buffer (GstQTMux * qtmux, GstQTPad * pad, GstBuffer * buf)
 
   /* now we go and register this buffer/sample all over */
   /* note that a new chunk is started each time (not fancy but works) */
+  if (qtmux->moov_recov_file) {
+    if (!atoms_recov_write_trak_samples (qtmux->moov_recov_file, pad->trak,
+            nsamples, scaled_duration, sample_size, chunk_offset, sync, do_pts,
+            pts_offset)) {
+      GST_WARNING_OBJECT (qtmux, "Failed to write sample information to "
+          "recovery file, disabling recovery");
+      fclose (qtmux->moov_recov_file);
+      qtmux->moov_recov_file = NULL;
+    }
+  }
   atom_trak_add_samples (pad->trak, nsamples, scaled_duration, sample_size,
       chunk_offset, sync, do_pts, pts_offset);
 
@@ -2451,6 +2549,9 @@ gst_qt_mux_get_property (GObject * object,
     case PROP_FAST_START_TEMP_FILE:
       g_value_set_string (value, qtmux->fast_start_file_path);
       break;
+    case PROP_MOOV_RECOV_FILE:
+      g_value_set_string (value, qtmux->moov_recov_file_path);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -2499,6 +2600,10 @@ gst_qt_mux_set_property (GObject * object,
         gst_qt_mux_generate_fast_start_file_path (qtmux);
       }
       break;
+    case PROP_MOOV_RECOV_FILE:
+      g_free (qtmux->moov_recov_file_path);
+      qtmux->moov_recov_file_path = g_value_dup_string (value);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -2545,7 +2650,6 @@ gst_qt_mux_change_state (GstElement * element, GstStateChange transition)
   return ret;
 }
 
-
 gboolean
 gst_qt_mux_register (GstPlugin * plugin)
 {
@@ -2613,18 +2717,3 @@ gst_qt_mux_register (GstPlugin * plugin)
 
   return TRUE;
 }
-
-gboolean
-gst_qt_mux_plugin_init (GstPlugin * plugin)
-{
-  GST_DEBUG_CATEGORY_INIT (gst_qt_mux_debug, "qtmux", 0, "QT Muxer");
-
-  return gst_qt_mux_register (plugin);
-}
-
-GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
-    GST_VERSION_MINOR,
-    "qtmux",
-    "Quicktime Muxer plugin",
-    gst_qt_mux_plugin_init, VERSION, "LGPL", "gsoc2008 package",
-    "embedded.ufcg.edu.br")
index 6dc0de8..707231d 100644 (file)
@@ -1,5 +1,5 @@
 /* Quicktime muxer plugin for GStreamer
- * Copyright (C) 2008 Thiago Sousa Santos <thiagoss@embedded.ufcg.edu.br>
+ * Copyright (C) 2008-2010 Thiago Santos <thiagoss@embedded.ufcg.edu.br>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
@@ -48,6 +48,7 @@
 
 #include "fourcc.h"
 #include "atoms.h"
+#include "atomsrecovery.h"
 #include "gstqtmuxmap.h"
 
 G_BEGIN_DECLS
@@ -144,6 +145,9 @@ struct _GstQTMux
   /* fast start */
   FILE *fast_start_file;
 
+  /* moov recovery */
+  FILE *moov_recov_file;
+
   /* properties */
   guint32 timescale;
   AtomsTreeFlavor flavor;
@@ -151,6 +155,7 @@ struct _GstQTMux
   gboolean large_file;
   gboolean guess_pts;
   gchar *fast_start_file_path;
+  gchar *moov_recov_file_path;
 
   /* for collect pads event handling function */
   GstPadEventFunction collect_event;
@@ -178,6 +183,7 @@ typedef struct _GstQTMuxClassParams
 #define GST_QT_MUX_PARAMS_QDATA g_quark_from_static_string("qt-mux-params")
 
 GType gst_qt_mux_get_type (void);
+gboolean gst_qt_mux_register (GstPlugin * plugin);
 
 /* FIXME: ideally classification tag should be added and
  * registered in gstreamer core gsttaglist
diff --git a/gst/quicktime/gstqtmuxplugin.c b/gst/quicktime/gstqtmuxplugin.c
new file mode 100644 (file)
index 0000000..4350b1f
--- /dev/null
@@ -0,0 +1,67 @@
+/* Quicktime muxer plugin for GStreamer
+ * Copyright (C) 2008-2010 Thiago Santos <thiagoss@embedded.ufcg.edu.br>
+ * Copyright (C) 2008 Mark Nauwelaerts <mnauw@users.sf.net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+/*
+ * Unless otherwise indicated, Source Code is licensed under MIT license.
+ * See further explanation attached in License Statement (distributed in the file
+ * LICENSE).
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstqtmux.h"
+#include "gstqtmoovrecover.h"
+
+static gboolean
+gst_qt_mux_plugin_init (GstPlugin * plugin)
+{
+  if (!gst_qt_mux_register (plugin))
+    return FALSE;
+  if (!gst_qt_moov_recover_register (plugin))
+    return FALSE;
+
+  return TRUE;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    "qtmux",
+    "Quicktime Muxer plugin",
+    gst_qt_mux_plugin_init, VERSION, "LGPL", "gsoc2008 package",
+    "embedded.ufcg.edu.br")