mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-27 20:21:24 +00:00
1108 lines
30 KiB
C
1108 lines
30 KiB
C
/* 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., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, 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-1.0 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) {
|
|
GstMapInfo map;
|
|
|
|
gst_buffer_map (prefix, &map, GST_MAP_READ);
|
|
if (fwrite (map.data, 1, map.size, f) != map.size) {
|
|
gst_buffer_unmap (prefix, &map);
|
|
return FALSE;
|
|
}
|
|
gst_buffer_unmap (prefix, &map);
|
|
}
|
|
if (!atom_ftyp_copy_data (ftyp, &data, &size, &offset)) {
|
|
return FALSE;
|
|
}
|
|
if (fwrite (data, 1, offset, f) != offset) {
|
|
g_free (data);
|
|
return FALSE;
|
|
}
|
|
g_free (data);
|
|
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;
|
|
}
|
|
|
|
/* sanity check */
|
|
if (moovrf->num_traks > 1024) {
|
|
g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_PARSING,
|
|
"Unsupported 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->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;
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
static 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, write;
|
|
|
|
read = fread (data, 1, 4096, mdatrf->file);
|
|
write = fwrite (data, 1, read, outf);
|
|
|
|
if (write != read) {
|
|
g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE,
|
|
"Failed to copy data to output file: %s", g_strerror (errno));
|
|
goto fail;
|
|
}
|
|
}
|
|
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;
|
|
}
|