/* 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.
*/
atom_array_clear (&stss->entries);
}
-static void
+void
atom_stbl_init (AtomSTBL * stbl)
{
atom_header_set (&stbl->header, FOURCC_stbl, 0, 0);
atom_co64_init (&stbl->stco64);
}
-static void
+void
atom_stbl_clear (AtomSTBL * stbl)
{
atom_clear (&stbl->header);
return *offset - original_offset;
}
-static guint64
+guint64
atom_mvhd_copy_data (AtomMVHD * atom, guint8 ** buffer, guint64 * size,
guint64 * offset)
{
return original_offset - *offset;
}
-static guint64
+guint64
atom_stts_copy_data (AtomSTTS * stts, guint8 ** buffer, guint64 * size,
guint64 * offset)
{
return *offset - original_offset;
}
-static guint64
+guint64
atom_stsz_copy_data (AtomSTSZ * stsz, guint8 ** buffer, guint64 * size,
guint64 * offset)
{
return *offset - original_offset;
}
-static guint64
+guint64
atom_stsc_copy_data (AtomSTSC * stsc, guint8 ** buffer, guint64 * size,
guint64 * offset)
{
return *offset - original_offset;
}
-static guint64
+guint64
atom_ctts_copy_data (AtomCTTS * ctts, guint8 ** buffer, guint64 * size,
guint64 * offset)
{
return *offset - original_offset;
}
-static guint64
+guint64
atom_stco64_copy_data (AtomSTCO64 * stco64, guint8 ** buffer, guint64 * size,
guint64 * offset)
{
return *offset - original_offset;
}
-static guint64
+guint64
atom_stss_copy_data (AtomSTSS * stss, guint8 ** buffer, guint64 * size,
guint64 * offset)
{
return *offset - original_offset;
}
-static guint64
+guint64
atom_trak_copy_data (AtomTRAK * trak, guint8 ** buffer, guint64 * size,
guint64 * 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);
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
}
}
-static void
+void
atom_stco64_chunks_add_offset (AtomSTCO64 * stco64, guint32 offset)
{
guint i;
/* 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
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
{
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);
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
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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__ */
#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')
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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__ */
/* 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
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 */
#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);
"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
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 */
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);
serialize_error:
{
GST_ELEMENT_ERROR (qtmux, STREAM, MUX, (NULL),
- ("Failed to serialize ftyp"));
+ ("Failed to serialize mdat"));
return GST_FLOW_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;
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
/* 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);
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;
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;
return ret;
}
-
gboolean
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")
/* 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
#include "fourcc.h"
#include "atoms.h"
+#include "atomsrecovery.h"
#include "gstqtmuxmap.h"
G_BEGIN_DECLS
/* fast start */
FILE *fast_start_file;
+ /* moov recovery */
+ FILE *moov_recov_file;
+
/* properties */
guint32 timescale;
AtomsTreeFlavor flavor;
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;
#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
--- /dev/null
+/* 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")