| /* 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 MAX_CHUNK_SIZE (1024 * 1024) /* 1MB */ |
| |
| #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; |
| |
| if (!gst_buffer_map (prefix, &map, GST_MAP_READ)) { |
| return FALSE; |
| } |
| 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; |
| } |
| |
| static gboolean |
| mdat_recov_file_find_mdat (FILE * file, GError ** err) |
| { |
| guint32 fourcc = 0, size = 0; |
| gboolean failure = FALSE; |
| while (fourcc != FOURCC_mdat && !failure) { |
| if (!read_atom_header (file, &fourcc, &size)) { |
| goto parse_error; |
| } |
| switch (fourcc) { |
| /* skip these atoms */ |
| case FOURCC_ftyp: |
| case FOURCC_free: |
| case FOURCC_udta: |
| if (fseek (file, size - 8, SEEK_CUR) != 0) { |
| goto file_seek_error; |
| } |
| break; |
| case FOURCC_mdat: |
| break; |
| default: |
| GST_ERROR ("Unexpected atom in headers %" GST_FOURCC_FORMAT, |
| GST_FOURCC_ARGS (fourcc)); |
| failure = TRUE; |
| break; |
| } |
| } |
| |
| if (!failure) { |
| /* Reverse to mdat start */ |
| if (fseek (file, -8, SEEK_CUR) != 0) |
| goto file_seek_error; |
| } |
| |
| return !failure; |
| |
| parse_error: |
| g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE, |
| "Failed to parse atom"); |
| return FALSE; |
| |
| file_seek_error: |
| g_set_error (err, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE, |
| "Failed to seek to start of the file"); |
| return FALSE; |
| |
| } |
| |
| MdatRecovFile * |
| mdat_recov_file_create (FILE * file, gboolean datafile, GError ** err) |
| { |
| MdatRecovFile *mrf = g_new0 (MdatRecovFile, 1); |
| |
| 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 (!mdat_recov_file_find_mdat (file, err)) { |
| goto fail; |
| } |
| |
| /* 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; |
| |
| 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; |
| |
| if (fseek (moovrf->file, |
| (long int) trakrd->mdia_file_offset + trakrd->mdia_size, |
| SEEK_SET) != 0) |
| return FALSE; |
| |
| trakrd->extra_atoms_offset = ftell (moovrf->file); |
| trakrd->extra_atoms_size = size - (trakrd->extra_atoms_offset - offset); |
| |
| 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; |
| } |
| |
| static gboolean |
| copy_data_from_file_to_file (FILE * from, guint position, guint size, FILE * to, |
| GError ** err) |
| { |
| guint8 *data = NULL; |
| |
| if (fseek (from, position, SEEK_SET) != 0) |
| goto fail; |
| data = g_malloc (size); |
| if (fread (data, 1, size, from) != size) { |
| goto fail; |
| } |
| if (fwrite (data, 1, size, to) != size) { |
| ATOMS_RECOV_OUTPUT_WRITE_ERROR (err); |
| goto fail; |
| } |
| |
| g_free (data); |
| return TRUE; |
| |
| fail: |
| g_free (data); |
| return FALSE; |
| } |
| |
| gboolean |
| moov_recov_write_file (MoovRecovFile * moovrf, MdatRecovFile * mdatrf, |
| FILE * outf, GError ** err, GError ** warn) |
| { |
| 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; |
| guint remaining; |
| |
| /* 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]); |
| /* 8 or 16 for the mdat header */ |
| gint64 offset = moov_size + ftell (outf) + mdatrf->mdat_header_size; |
| 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; |
| |
| /* Copy the extra atoms after 'minf' */ |
| if (!copy_data_from_file_to_file (moovrf->file, trak->extra_atoms_offset, |
| trak->extra_atoms_size, outf, err)) |
| goto fail; |
| } |
| |
| /* write the mdat */ |
| /* write the header first */ |
| if (mdatrf->mdat_header_size == 16) { |
| GST_WRITE_UINT32_BE (auxdata, 1); |
| GST_WRITE_UINT32_LE (auxdata + 4, FOURCC_mdat); |
| GST_WRITE_UINT64_BE (auxdata + 8, mdatrf->mdat_size); |
| } else if (mdatrf->mdat_header_size == 8) { |
| GST_WRITE_UINT32_BE (auxdata, mdatrf->mdat_size); |
| GST_WRITE_UINT32_LE (auxdata + 4, FOURCC_mdat); |
| } else { |
| GST_ERROR ("Unexpected atom size: %u", mdatrf->mdat_header_size); |
| g_assert_not_reached (); |
| goto fail; |
| } |
| |
| if (fwrite (auxdata, 1, mdatrf->mdat_header_size, |
| outf) != mdatrf->mdat_header_size) { |
| 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; |
| |
| remaining = mdatrf->mdat_size - mdatrf->mdat_header_size; |
| data = g_malloc (MAX_CHUNK_SIZE); |
| while (!feof (mdatrf->file) && remaining > 0) { |
| gint read, write, readsize; |
| |
| readsize = MIN (MAX_CHUNK_SIZE, remaining); |
| |
| read = fread (data, 1, readsize, mdatrf->file); |
| write = fwrite (data, 1, read, outf); |
| remaining -= read; |
| |
| 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); |
| |
| if (remaining) { |
| g_set_error (warn, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE, |
| "Samples in recovery file were not present on headers." |
| " Bytes lost: %u", remaining); |
| } else if (!feof (mdatrf->file)) { |
| g_set_error (warn, ATOMS_RECOV_QUARK, ATOMS_RECOV_ERR_FILE, |
| "Samples in headers were not found in data file."); |
| GST_FIXME ("Rewrite mdat size if we reach this to make the file" |
| " fully correct"); |
| } |
| |
| return TRUE; |
| |
| fail: |
| g_free (stbl_children); |
| g_free (mvhd_data); |
| g_free (prefix_data); |
| g_free (trak_data); |
| g_free (data); |
| return FALSE; |
| } |