mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-09-08 21:28:45 +00:00
Implement a robust recording mode, where the output file is always in a playable state, seeking and rewriting the moov header at a configurable interval. Rewriting moov is done using reserved space at the start of the file, and a ping-pong strategy where the moov is replaced atomically so it's never invalid. Track when tags have actually changed, and don't write them into the moov unless they've changed. Clear any existing tags when re-writing them, so we can do progressive moov updating in robust recording mode. Write placeholder mdat as a free atom plus a 32-bit mdat with '0' size, which means "rest of the file" in the spec. Re-write it later to a full 64-bit extended size atom if needed.
1109 lines
30 KiB
C
1109 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 */
|
|
if (fseek (moovrf->file, 68, SEEK_CUR) != 0)
|
|
return FALSE;
|
|
|
|
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_set_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;
|
|
}
|