gstreamer/gst/isomp4/atomsrecovery.c
Jan Schmidt 1d058c7d8a isomp4: Implement robust muxing using ping-pong strategy
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.
2015-06-08 14:49:11 +10:00

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;
}