diff --git a/gst/quicktime/atoms.c b/gst/quicktime/atoms.c index d625823307..bcb71cad2a 100644 --- a/gst/quicktime/atoms.c +++ b/gst/quicktime/atoms.c @@ -1,5 +1,5 @@ /* Quicktime muxer plugin for GStreamer - * Copyright (C) 2008 Thiago Sousa Santos + * Copyright (C) 2008-2010 Thiago Santos * Copyright (C) 2008 Mark Nauwelaerts * * This library is free software; you can redistribute it and/or @@ -49,38 +49,6 @@ #include -/* 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; diff --git a/gst/quicktime/atoms.h b/gst/quicktime/atoms.h index 6d704a60fe..8090fcfa41 100644 --- a/gst/quicktime/atoms.h +++ b/gst/quicktime/atoms.h @@ -1,5 +1,5 @@ /* Quicktime muxer plugin for GStreamer - * Copyright (C) 2008 Thiago Sousa Santos + * Copyright (C) 2008-2010 Thiago Santos * * 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 index 0000000000..9e1ba6798a --- /dev/null +++ b/gst/quicktime/atomsrecovery.c @@ -0,0 +1,1087 @@ +/* Quicktime muxer plugin for GStreamer + * Copyright (C) 2010 Thiago Santos + * + * 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 index 0000000000..4dffc48d33 --- /dev/null +++ b/gst/quicktime/atomsrecovery.h @@ -0,0 +1,159 @@ +/* Quicktime muxer plugin for GStreamer + * Copyright (C) 2010 Thiago Santos + * + * 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 +#include +#include +#include + +#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__ */ diff --git a/gst/quicktime/fourcc.h b/gst/quicktime/fourcc.h index 05817938f7..f859e0ccb7 100644 --- a/gst/quicktime/fourcc.h +++ b/gst/quicktime/fourcc.h @@ -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 index 0000000000..bddfa145cc --- /dev/null +++ b/gst/quicktime/gstqtmoovrecover.c @@ -0,0 +1,391 @@ +/* Quicktime muxer plugin for GStreamer + * Copyright (C) 2010 Thiago Santos + * + * 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 + * + * + * + * This element recovers quicktime files created with qtmux using the moov recovery feature. + * + * Example pipelines + * + * + * TODO + * + * + * + * Last reviewed on 2010-02-01 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#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 "); + +/* 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 index 0000000000..07dc9d9379 --- /dev/null +++ b/gst/quicktime/gstqtmoovrecover.h @@ -0,0 +1,88 @@ +/* Quicktime muxer plugin for GStreamer + * Copyright (C) 2010 Thiago Santos + * + * 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 + +#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__ */ diff --git a/gst/quicktime/gstqtmux.c b/gst/quicktime/gstqtmux.c index d44cd9a233..2de1ba9367 100644 --- a/gst/quicktime/gstqtmux.c +++ b/gst/quicktime/gstqtmux.c @@ -1,5 +1,5 @@ /* Quicktime muxer plugin for GStreamer - * Copyright (C) 2008 Thiago Sousa Santos + * Copyright (C) 2008-2010 Thiago Santos * Copyright (C) 2008 Mark Nauwelaerts * * 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,26 +1203,48 @@ serialize_error: } } +static void +gst_qt_mux_prepare_ftyp (GstQTMux * qtmux, AtomFTYP ** p_ftyp, + GstBuffer ** p_prefix) +{ + GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux)); + guint32 major, version; + GList *comp; + GstBuffer *prefix = NULL; + AtomFTYP *ftyp = NULL; + + GST_DEBUG_OBJECT (qtmux, "Preparing ftyp and possible prefix atom"); + + /* init and send context and ftyp based on current property state */ + gst_qt_mux_map_format_to_header (qtmux_klass->format, &prefix, &major, + &version, &comp, qtmux->moov, qtmux->longest_chunk, + qtmux->fast_start_file != NULL); + 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; - GstQTMuxClass *qtmux_klass = (GstQTMuxClass *) (G_OBJECT_GET_CLASS (qtmux)); - guint32 major, version; - GList *comp; - GstBuffer *prefix; + 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) + 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); - if (comp) - g_list_free (comp); + 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) @@ -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") diff --git a/gst/quicktime/gstqtmux.h b/gst/quicktime/gstqtmux.h index 6dc0de8d1a..707231d01b 100644 --- a/gst/quicktime/gstqtmux.h +++ b/gst/quicktime/gstqtmux.h @@ -1,5 +1,5 @@ /* Quicktime muxer plugin for GStreamer - * Copyright (C) 2008 Thiago Sousa Santos + * Copyright (C) 2008-2010 Thiago Santos * * 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 index 0000000000..4350b1faf2 --- /dev/null +++ b/gst/quicktime/gstqtmuxplugin.c @@ -0,0 +1,67 @@ +/* Quicktime muxer plugin for GStreamer + * Copyright (C) 2008-2010 Thiago Santos + * Copyright (C) 2008 Mark Nauwelaerts + * + * 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")