mkvtoolnix/librmff/rmff.c

1312 lines
38 KiB
C
Raw Normal View History

2004-03-17 19:51:30 +00:00
/*
2004-03-20 18:57:24 +00:00
rmff.c
Copyright (C) Moritz Bunkus - March 2004
librmff is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1, or (at your option)
any later version.
librmff 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 General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this library; see the file COPYING. If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
$Id$
2004-03-17 19:51:30 +00:00
*/
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "librmff.h"
2004-03-20 10:50:34 +00:00
typedef struct rmff_file_internal_t {
uint32_t max_bit_rate;
uint32_t avg_bit_rate;
uint32_t max_packet_size;
uint32_t avg_packet_size;
uint32_t highest_timecode;
uint32_t num_packets;
uint32_t data_offset;
2004-03-20 18:57:24 +00:00
uint32_t next_data_offset;
uint32_t data_contents_size;
2004-03-20 10:50:34 +00:00
uint32_t index_offset;
2004-03-20 18:57:24 +00:00
int num_index_chunks;
2004-03-21 10:04:19 +00:00
uint32_t total_bytes;
2004-03-20 10:50:34 +00:00
} rmff_file_internal_t;
typedef struct rmff_track_internal_t {
uint32_t max_bit_rate;
uint32_t avg_bit_rate;
uint32_t max_packet_size;
uint32_t avg_packet_size;
uint32_t highest_timecode;
uint32_t num_packets;
2004-03-20 18:57:24 +00:00
int index_this;
2004-03-21 10:04:19 +00:00
uint32_t last_timecode;
uint32_t bytes_since_timecode_change;
uint32_t total_bytes;
2004-03-20 10:50:34 +00:00
} rmff_track_internal_t;
2004-03-17 19:51:30 +00:00
int rmff_last_error = RMFF_ERR_OK;
const char *rmff_last_error_msg = NULL;
static const char *rmff_std_error_messages[] = {
"No error",
"File is not a RealMedia file",
"Inconsistent data found in file",
"End of file reached",
"Input/output error",
"Invalid parameters"
};
const char *
rmff_get_error_str(int code) {
code *= -1;
if ((code >= 0) && (code <= 5))
return rmff_std_error_messages[code];
else
return "Unknown error";
}
static int
set_error(int error_number,
const char *error_msg,
int return_value) {
rmff_last_error = error_number;
if (error_msg == NULL)
rmff_last_error_msg = rmff_get_error_str(error_number);
else
rmff_last_error_msg = error_msg;
return return_value;
}
2004-03-20 18:57:24 +00:00
#define clear_error() set_error(RMFF_ERR_OK, NULL, RMFF_ERR_OK)
2004-03-18 20:47:24 +00:00
uint16_t
rmff_get_uint16_be(const void *buf) {
2004-03-17 19:51:30 +00:00
uint16_t ret;
unsigned char *tmp;
tmp = (unsigned char *) buf;
ret = tmp[0] & 0xff;
ret = (ret << 8) + (tmp[1] & 0xff);
return ret;
}
2004-03-18 20:47:24 +00:00
uint32_t
rmff_get_uint32_be(const void *buf) {
2004-03-17 19:51:30 +00:00
uint32_t ret;
unsigned char *tmp;
tmp = (unsigned char *) buf;
ret = tmp[0] & 0xff;
ret = (ret << 8) + (tmp[1] & 0xff);
ret = (ret << 8) + (tmp[2] & 0xff);
ret = (ret << 8) + (tmp[3] & 0xff);
return ret;
}
2004-03-18 20:47:24 +00:00
void
2004-03-20 10:50:34 +00:00
rmff_put_uint16_be(void *buf,
uint16_t value) {
2004-03-17 19:51:30 +00:00
unsigned char *tmp;
tmp = (unsigned char *) buf;
tmp[1] = value & 0xff;
tmp[0] = (value >>= 8) & 0xff;
}
2004-03-18 20:47:24 +00:00
void
2004-03-20 10:50:34 +00:00
rmff_put_uint32_be(void *buf,
uint32_t value) {
2004-03-17 19:51:30 +00:00
unsigned char *tmp;
tmp = (unsigned char *) buf;
tmp[3] = value & 0xff;
tmp[2] = (value >>= 8) & 0xff;
tmp[1] = (value >>= 8) & 0xff;
tmp[0] = (value >>= 8) & 0xff;
}
#define read_uint8() file_read_uint8(io, fh)
#define read_uint16_be() file_read_uint16_be(io, fh)
#define read_uint32_be() file_read_uint32_be(io, fh)
2004-03-20 10:50:34 +00:00
#define read_uint16_be_to(addr) rmff_put_uint16_be(addr, read_uint16_be())
#define read_uint32_be_to(addr) rmff_put_uint32_be(addr, read_uint32_be())
#define write_uint8(v) file_write_uint8(io, fh, v)
#define write_uint16_be(v) file_write_uint16_be(io, fh, v)
#define write_uint32_be(v) file_write_uint32_be(io, fh, v)
#define write_uint16_be_from(addr) write_uint16_be(rmff_get_uint16_be(addr))
#define write_uint32_be_from(addr) write_uint32_be(rmff_get_uint32_be(addr))
2004-03-18 20:47:24 +00:00
#define get_fourcc(b) rmff_get_uint32_be(b)
2004-03-17 19:51:30 +00:00
static uint8_t
file_read_uint8(mb_file_io_t *io,
void *fh) {
unsigned char tmp;
io->read(fh, &tmp, 1);
return tmp;
}
static uint16_t
file_read_uint16_be(mb_file_io_t *io,
void *fh) {
unsigned char tmp[2];
io->read(fh, tmp, 2);
2004-03-18 20:47:24 +00:00
return rmff_get_uint16_be(tmp);
2004-03-17 19:51:30 +00:00
}
static uint32_t
file_read_uint32_be(mb_file_io_t *io,
void *fh) {
unsigned char tmp[4];
io->read(fh, tmp, 4);
2004-03-18 20:47:24 +00:00
return rmff_get_uint32_be(tmp);
2004-03-17 19:51:30 +00:00
}
2004-03-20 10:50:34 +00:00
static int
file_write_uint8(mb_file_io_t *io,
void *fh,
uint8_t value) {
return io->write(fh, &value, 1);
}
static int
file_write_uint16_be(mb_file_io_t *io,
void *fh,
uint16_t value) {
unsigned char tmp[2];
rmff_put_uint16_be(tmp, value);
return io->write(fh, &tmp, 2);
}
static int
file_write_uint32_be(mb_file_io_t *io,
void *fh,
uint32_t value) {
unsigned char tmp[4];
rmff_put_uint32_be(tmp, value);
return io->write(fh, &tmp, 4);
}
2004-03-17 19:51:30 +00:00
void
die(const char *fmt,
...) {
va_list ap;
fprintf(stderr, "'die' called: ");
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
exit(1);
}
#define safestrdup(s) _safestrdup(s, __FILE__, __LINE__)
#define safememdup(s, b) _safememdup(s, b, __FILE__, __LINE__)
#define safemalloc(s) _safemalloc(s, __FILE__, __LINE__)
#define safecalloc(s) _safecalloc(s, __FILE__, __LINE__)
#define saferealloc(s, b) _saferealloc(s, b, __FILE__, __LINE__)
#define safefree(s) { if ((s) != NULL) free(s); }
static char *
_safestrdup(const char *s,
const char *file,
int line) {
char *copy;
if (s == NULL)
return NULL;
copy = strdup(s);
if (copy == NULL)
die("safestrdup() called from file %s, line %d: strdup() "
"returned NULL for '%s'.", file, line, s);
return copy;
}
2004-03-18 20:47:24 +00:00
static void *
_safememdup(const void *s,
size_t size,
const char *file,
int line) {
void *copy;
2004-03-17 19:51:30 +00:00
2004-03-18 20:47:24 +00:00
if (s == NULL)
return NULL;
2004-03-17 19:51:30 +00:00
2004-03-18 20:47:24 +00:00
copy = malloc(size);
if (copy == NULL)
die("safememdup() called from file %s, line %d: malloc() "
"returned NULL for a size of %d bytes.", file, line, size);
memcpy(copy, s, size);
2004-03-17 19:51:30 +00:00
2004-03-18 20:47:24 +00:00
return copy;
}
2004-03-17 19:51:30 +00:00
static void *
_safemalloc(size_t size,
const char *file,
int line) {
void *mem;
mem = malloc(size);
if (mem == NULL)
die("safemalloc() called from file %s, line %d: malloc() "
"returned NULL for a size of %d bytes.", file, line, size);
return mem;
}
static void *_safecalloc(size_t size,
const char *file,
int line) {
void *mem;
mem = calloc(size, 1);
if (mem == NULL)
die("safemalloc() called from file %s, line %d: malloc() "
"returned NULL for a size of %d bytes.", file, line, size);
return mem;
}
static void *
_saferealloc(void *mem,
size_t size,
const char *file,
int line) {
mem = realloc(mem, size);
if (mem == NULL)
die("saferealloc() called from file %s, line %d: realloc() "
"returned NULL for a size of %d bytes.", file, line, size);
return mem;
}
2004-03-18 19:01:35 +00:00
static rmff_file_t *
open_file_for_reading(const char *path,
mb_file_io_t *io) {
2004-03-17 19:51:30 +00:00
rmff_file_t *file;
void *file_h;
2004-03-18 19:01:35 +00:00
char signature[5];
2004-03-17 19:51:30 +00:00
2004-03-18 19:01:35 +00:00
file_h = io->open(path, MB_OPEN_MODE_READING);
2004-03-17 19:51:30 +00:00
if (file_h == NULL)
return NULL;
2004-03-18 19:01:35 +00:00
signature[4] = 0;
if ((io->read(file_h, signature, 4) != 4) ||
strcmp(signature, ".RMF")) {
2004-03-17 19:51:30 +00:00
io->close(file_h);
return (rmff_file_t *)set_error(RMFF_ERR_NOT_RMFF, NULL, 0);
}
file = (rmff_file_t *)safecalloc(sizeof(rmff_file_t));
file->handle = file_h;
file->name = safestrdup(path);
file->io = io;
io->seek(file_h, 0, SEEK_END);
file->size = io->tell(file_h);
io->seek(file_h, 4, SEEK_SET);
2004-03-18 19:01:35 +00:00
file->open_mode = RMFF_OPEN_MODE_READING;
2004-03-20 10:50:34 +00:00
file->internal = safecalloc(sizeof(rmff_file_internal_t));
2004-03-18 19:01:35 +00:00
2004-03-20 18:57:24 +00:00
clear_error();
2004-03-18 19:01:35 +00:00
return file;
}
static rmff_file_t *
open_file_for_writing(const char *path,
mb_file_io_t *io) {
rmff_file_t *file;
void *file_h;
const char *signature = ".RMF";
file_h = io->open(path, MB_OPEN_MODE_WRITING);
if (file_h == NULL)
return NULL;
if (io->write(file_h, signature, 4) != 4) {
io->close(file_h);
return (rmff_file_t *)set_error(RMFF_ERR_IO, NULL, 0);
}
file = (rmff_file_t *)safecalloc(sizeof(rmff_file_t));
file->handle = file_h;
file->name = safestrdup(path);
file->io = io;
file->size = -1;
file->open_mode = RMFF_OPEN_MODE_WRITING;
2004-03-20 10:50:34 +00:00
file->internal = safecalloc(sizeof(rmff_file_internal_t));
2004-03-17 19:51:30 +00:00
2004-03-21 10:04:19 +00:00
/* save allowed & perfect play */
rmff_put_uint16_be(&file->prop_header.flags, 1 | 2);
2004-03-20 18:57:24 +00:00
clear_error();
2004-03-17 19:51:30 +00:00
return file;
}
2004-03-18 19:01:35 +00:00
rmff_file_t *
rmff_open_file(const char *path,
int mode) {
return rmff_open_file_with_io(path, mode, &std_mb_file_io);
}
rmff_file_t *
rmff_open_file_with_io(const char *path,
int mode,
mb_file_io_t *io) {
if ((path == NULL) || (io == NULL) ||
((mode != RMFF_OPEN_MODE_READING) && (mode != RMFF_OPEN_MODE_WRITING)))
return (rmff_file_t *)set_error(RMFF_ERR_PARAMETERS, NULL, 0);
if (mode == RMFF_OPEN_MODE_READING)
return open_file_for_reading(path, io);
else
return open_file_for_writing(path, io);
}
2004-03-17 19:51:30 +00:00
void
rmff_free_track_data(rmff_track_t *track) {
if (track == NULL)
return;
safefree(track->mdpr_header.name);
safefree(track->mdpr_header.mime_type);
safefree(track->mdpr_header.type_specific_data);
2004-03-20 10:50:34 +00:00
safefree(track->internal);
2004-03-17 19:51:30 +00:00
}
void
rmff_close_file(rmff_file_t *file) {
int i;
if (file == NULL)
return;
safefree(file->name);
for (i = 0; i < file->num_tracks; i++)
rmff_free_track_data(&file->tracks[i]);
safefree(file->tracks);
safefree(file->cont_header.title);
safefree(file->cont_header.author);
safefree(file->cont_header.copyright);
safefree(file->cont_header.comment);
2004-03-20 10:50:34 +00:00
safefree(file->internal);
2004-03-17 19:51:30 +00:00
file->io->close(file->handle);
safefree(file);
}
#define skip(num) { if (io->seek(fh, num, SEEK_CUR) != 0) \
return set_error(RMFF_ERR_IO, NULL, -1); }
2004-03-20 18:57:24 +00:00
static void
add_to_index(rmff_track_t *track,
int64_t pos,
uint32_t timecode,
uint32_t packet_number) {
if ((track->index != NULL) &&
(track->index[track->num_index_entries - 1].timecode == timecode))
return;
track->index = (rmff_index_entry_t *)
saferealloc(track->index, (track->num_index_entries + 1) *
sizeof(rmff_index_entry_t));
track->index[track->num_index_entries].pos = pos;
track->index[track->num_index_entries].timecode = timecode;
track->index[track->num_index_entries].packet_number = packet_number;
track->num_index_entries++;
}
static void
read_index(rmff_file_t *file,
int64_t pos) {
void *fh;
mb_file_io_t *io;
rmff_track_t *track;
uint32_t object_id, object_size, object_version, num_entries;
uint32_t next_header_offset, i, timecode, offset, packet_number;
uint16_t id;
fh = file->handle;
io = file->io;
rmff_last_error = RMFF_ERR_OK;
io->seek(fh, pos, SEEK_SET);
object_id = read_uint32_be();
object_size = read_uint32_be();
object_version = read_uint16_be();
if (rmff_last_error != RMFF_ERR_OK)
return;
if (object_id != rmffFOURCC('I', 'N', 'D', 'X'))
return;
num_entries = read_uint32_be();
id = read_uint16_be();
track = rmff_find_track_with_id(file, id);
if (track == NULL)
return;
next_header_offset = read_uint32_be();
track->num_index_entries = 0;
safefree(track->index);
track->index = NULL;
for (i = 0; i < num_entries; i++) {
if (read_uint16_be() != 0) /* version */
return;
timecode = read_uint32_be();
offset = read_uint32_be();
packet_number = read_uint32_be();
add_to_index(track, timecode, offset, packet_number);
}
if (next_header_offset > 0)
read_index(file, next_header_offset);
}
2004-03-17 19:51:30 +00:00
int
rmff_read_headers(rmff_file_t *file) {
mb_file_io_t *io;
void *fh;
uint32_t object_id, object_size, size;
uint16_t object_version;
rmff_prop_t *prop;
rmff_cont_t *cont;
rmff_mdpr_t *mdpr;
rmff_track_t track;
real_video_props_t *rvp;
real_audio_v4_props_t *ra4p;
2004-03-20 18:57:24 +00:00
rmff_file_internal_t *fint;
2004-03-18 19:01:35 +00:00
int prop_header_found;
2004-03-20 18:57:24 +00:00
int64_t old_pos;
2004-03-17 19:51:30 +00:00
2004-03-18 19:01:35 +00:00
if ((file == NULL) || (file->open_mode != RMFF_OPEN_MODE_READING))
2004-03-17 19:51:30 +00:00
return set_error(RMFF_ERR_PARAMETERS, NULL, RMFF_ERR_PARAMETERS);
if (file->headers_read)
return 0;
io = file->io;
fh = file->handle;
2004-03-20 18:57:24 +00:00
fint = (rmff_file_internal_t *)file->internal;
2004-03-17 19:51:30 +00:00
if (io->seek(fh, 4, SEEK_SET))
return set_error(RMFF_ERR_IO, NULL, -1);
skip(4); /* header_size */
skip(2); /* object_version */
skip(4); /* file_version */
skip(4); /* num_headers */
prop = &file->prop_header;
2004-03-18 19:01:35 +00:00
prop_header_found = 0;
2004-03-17 19:51:30 +00:00
cont = &file->cont_header;
while (1) {
rmff_last_error = RMFF_ERR_OK;
object_id = read_uint32_be();
object_size = read_uint32_be();
object_version = read_uint16_be();
if (rmff_last_error != RMFF_ERR_OK)
break;
if (object_id == rmffFOURCC('P', 'R', 'O', 'P')) {
read_uint32_be_to(&prop->max_bit_rate);
read_uint32_be_to(&prop->avg_bit_rate);
read_uint32_be_to(&prop->max_packet_size);
read_uint32_be_to(&prop->avg_packet_size);
read_uint32_be_to(&prop->num_packets);
read_uint32_be_to(&prop->duration);
read_uint32_be_to(&prop->preroll);
read_uint32_be_to(&prop->index_offset);
read_uint32_be_to(&prop->data_offset);
read_uint16_be_to(&prop->num_streams);
read_uint16_be_to(&prop->flags);
2004-03-18 19:01:35 +00:00
prop_header_found = 1;
2004-03-17 19:51:30 +00:00
} else if (object_id == rmffFOURCC('C', 'O', 'N', 'T')) {
2004-03-18 19:01:35 +00:00
if (file->cont_header_present) {
2004-03-17 19:51:30 +00:00
safefree(cont->title);
safefree(cont->author);
safefree(cont->copyright);
safefree(cont->comment);
}
memset(cont, 0, sizeof(rmff_cont_t));
size = read_uint16_be(); /* title_len */
if (size > 0) {
2004-03-20 10:50:34 +00:00
cont->title = (char *)safecalloc(size + 1);
2004-03-17 19:51:30 +00:00
io->read(fh, cont->title, size);
}
size = read_uint16_be(); /* author_len */
if (size > 0) {
2004-03-20 10:50:34 +00:00
cont->author = (char *)safecalloc(size + 1);
2004-03-17 19:51:30 +00:00
io->read(fh, cont->author, size);
}
size = read_uint16_be(); /* copyright_len */
if (size > 0) {
2004-03-20 10:50:34 +00:00
cont->copyright = (char *)safecalloc(size + 1);
2004-03-17 19:51:30 +00:00
io->read(fh, cont->copyright, size);
}
size = read_uint16_be(); /* comment_len */
if (size > 0) {
2004-03-20 10:50:34 +00:00
cont->comment = (char *)safecalloc(size + 1);
2004-03-17 19:51:30 +00:00
io->read(fh, cont->comment, size);
}
2004-03-18 19:01:35 +00:00
file->cont_header_present = 1;
2004-03-17 19:51:30 +00:00
} else if (object_id == rmffFOURCC('M', 'D', 'P', 'R')) {
memset(&track, 0, sizeof(rmff_track_t));
track.file = (struct rmff_file_t *)file;
mdpr = &track.mdpr_header;
read_uint16_be_to(&mdpr->id);
2004-03-18 20:47:24 +00:00
track.id = rmff_get_uint16_be(&mdpr->id);
2004-03-17 19:51:30 +00:00
read_uint32_be_to(&mdpr->max_bit_rate);
read_uint32_be_to(&mdpr->avg_bit_rate);
read_uint32_be_to(&mdpr->max_packet_size);
read_uint32_be_to(&mdpr->avg_packet_size);
read_uint32_be_to(&mdpr->start_time);
read_uint32_be_to(&mdpr->preroll);
read_uint32_be_to(&mdpr->duration);
size = read_uint8(); /* stream_name_len */
if (size > 0) {
mdpr->name = (char *)safemalloc(size + 1);
io->read(fh, mdpr->name, size);
}
size = read_uint8(); /* mime_type_len */
if (size > 0) {
mdpr->mime_type = (char *)safemalloc(size + 1);
io->read(fh, mdpr->mime_type, size);
}
size = read_uint32_be(); /* type_specific_size */
2004-03-20 10:50:34 +00:00
rmff_put_uint32_be(&mdpr->type_specific_size, size);
2004-03-17 19:51:30 +00:00
if (size > 0) {
mdpr->type_specific_data = (unsigned char *)safemalloc(size);
io->read(fh, mdpr->type_specific_data, size);
}
rvp = (real_video_props_t *)mdpr->type_specific_data;
ra4p = (real_audio_v4_props_t *)mdpr->type_specific_data;
if ((size >= sizeof(real_video_props_t)) &&
(get_fourcc(&rvp->fourcc1) == rmffFOURCC('V', 'I', 'D', 'O')))
track.type = RMFF_TRACK_TYPE_VIDEO;
else if ((size >= sizeof(real_audio_v4_props_t)) &&
(get_fourcc(&ra4p->fourcc1) ==
rmffFOURCC('.', 'r', 'a', 0xfd))) {
track.type = RMFF_TRACK_TYPE_AUDIO;
2004-03-18 20:47:24 +00:00
if ((rmff_get_uint16_be(&ra4p->version1) == 5) &&
2004-03-17 19:51:30 +00:00
(size < sizeof(real_audio_v5_props_t)))
return set_error(RMFF_ERR_DATA, "RealAudio v5 data indicated but "
"data too small", RMFF_ERR_DATA);
}
2004-03-20 10:50:34 +00:00
track.internal = safecalloc(sizeof(rmff_track_internal_t));
2004-03-17 19:51:30 +00:00
file->tracks =
(rmff_track_t *)saferealloc(file->tracks, (file->num_tracks + 1) *
sizeof(rmff_track_t));
file->tracks[file->num_tracks] = track;
file->num_tracks++;
} else if (object_id == rmffFOURCC('D', 'A', 'T', 'A')) {
2004-03-20 18:57:24 +00:00
fint->data_offset = io->tell(fh) - (4 + 4 + 2);
2004-03-17 19:51:30 +00:00
file->num_packets_in_chunk = read_uint32_be();
2004-03-20 18:57:24 +00:00
fint->next_data_offset = read_uint32_be();
2004-03-17 19:51:30 +00:00
break;
} else {
/* Unknown header type */
return set_error(RMFF_ERR_DATA, NULL, RMFF_ERR_DATA);
}
}
2004-03-20 18:57:24 +00:00
if (prop_header_found && (fint->data_offset > 0)) {
if (rmff_get_uint32_be(&file->prop_header.index_offset) > 0) {
old_pos = io->tell(fh);
read_index(file, rmff_get_uint32_be(&file->prop_header.index_offset));
io->seek(fh, old_pos, SEEK_SET);
}
2004-03-17 19:51:30 +00:00
file->headers_read = 1;
return 0;
}
return set_error(RMFF_ERR_DATA, NULL, RMFF_ERR_DATA);
}
int
rmff_get_next_frame_size(rmff_file_t *file) {
uint16_t object_version, length;
uint32_t object_id;
mb_file_io_t *io;
void *fh;
int result;
int64_t old_pos;
if ((file == NULL) || (!file->headers_read) || (file->io == NULL) ||
2004-03-18 19:01:35 +00:00
(file->handle == NULL) || (file->open_mode != RMFF_OPEN_MODE_READING))
2004-03-17 19:51:30 +00:00
return set_error(RMFF_ERR_PARAMETERS, NULL, RMFF_ERR_PARAMETERS);
io = file->io;
fh = file->handle;
old_pos = io->tell(fh);
if ((file->size - old_pos) < 12)
return set_error(RMFF_ERR_EOF, NULL, RMFF_ERR_EOF);
object_version = read_uint16_be();
length = read_uint16_be();
object_id = ((uint32_t)object_version) << 16 | length;
if (object_id == rmffFOURCC('D', 'A', 'T', 'A')) {
skip(4 + 4); /* packets_in_chunk, next_data_header */
result = rmff_get_next_frame_size(file);
io->seek(fh, old_pos, SEEK_SET);
return result;
}
io->seek(fh, old_pos, SEEK_SET);
if (object_id == rmffFOURCC('I', 'N', 'D', 'X'))
return set_error(RMFF_ERR_EOF, NULL, RMFF_ERR_EOF);
return length - 12;
}
rmff_frame_t *
rmff_read_next_frame(rmff_file_t *file,
void *buffer) {
rmff_frame_t *frame;
2004-03-20 18:57:24 +00:00
rmff_file_internal_t *fint;
2004-03-17 19:51:30 +00:00
uint16_t object_version, length;
uint32_t object_id;
mb_file_io_t *io;
void *fh;
if ((file == NULL) || (!file->headers_read) || (file->io == NULL) ||
2004-03-18 19:01:35 +00:00
(file->handle == NULL) || (file->open_mode != RMFF_OPEN_MODE_READING))
2004-03-17 19:51:30 +00:00
return (rmff_frame_t *)set_error(RMFF_ERR_PARAMETERS, NULL, 0);
io = file->io;
fh = file->handle;
2004-03-20 18:57:24 +00:00
fint = (rmff_file_internal_t *)file->internal;
2004-03-17 19:51:30 +00:00
if ((file->size - io->tell(fh)) < 12)
return (rmff_frame_t *)set_error(RMFF_ERR_EOF, NULL, 0);
object_version = read_uint16_be();
length = read_uint16_be();
object_id = ((uint32_t)object_version) << 16 | length;
if (object_id == rmffFOURCC('D', 'A', 'T', 'A')) {
file->num_packets_in_chunk = read_uint32_be();
2004-03-20 18:57:24 +00:00
fint->next_data_offset = read_uint32_be();
2004-03-17 19:51:30 +00:00
file->num_packets_read = 0;
return rmff_read_next_frame(file, buffer);
}
if ((file->num_packets_read >= file->num_packets_in_chunk) ||
(object_id == rmffFOURCC('I', 'N', 'D', 'X')))
return (rmff_frame_t *)set_error(RMFF_ERR_EOF, NULL, 0);
frame = (rmff_frame_t *)safecalloc(sizeof(rmff_frame_t));
if (buffer == NULL) {
buffer = safemalloc(length);
frame->allocated_by_rmff = 1;
}
frame->data = buffer;
frame->size = length - 12;
frame->id = read_uint16_be();
frame->timecode = read_uint32_be();
frame->reserved = read_uint8();
frame->flags = read_uint8();
if (io->read(fh, frame->data, frame->size) != frame->size) {
rmff_release_frame(frame);
return (rmff_frame_t *)set_error(RMFF_ERR_EOF, NULL, 0);
}
file->num_packets_read++;
return frame;
}
2004-03-18 20:47:24 +00:00
rmff_frame_t *
rmff_allocate_frame(uint32_t size,
void *buffer) {
rmff_frame_t *frame;
if (size == 0)
return (rmff_frame_t *)set_error(RMFF_ERR_PARAMETERS, NULL, 0);
frame = (rmff_frame_t *)safecalloc(sizeof(rmff_frame_t));
if (buffer == NULL) {
buffer = safemalloc(size);
frame->allocated_by_rmff = 1;
}
frame->size = size;
frame->data = (unsigned char *)buffer;
return frame;
}
2004-03-17 19:51:30 +00:00
void
rmff_release_frame(rmff_frame_t *frame) {
if (frame == NULL)
return;
if (frame->allocated_by_rmff)
safefree(frame->data);
safefree(frame);
}
2004-03-18 19:01:35 +00:00
void
rmff_set_cont_header(rmff_file_t *file,
const char *title,
const char *author,
const char *copyright,
const char *comment) {
if (file == NULL)
return;
safefree(file->cont_header.title);
safefree(file->cont_header.author);
safefree(file->cont_header.copyright);
safefree(file->cont_header.comment);
file->cont_header.title = safestrdup(title);
file->cont_header.author = safestrdup(author);
file->cont_header.copyright = safestrdup(copyright);
file->cont_header.comment = safestrdup(comment);
}
2004-03-18 20:47:24 +00:00
void
rmff_set_track_data(rmff_track_t *track,
const char *name,
const char *mime_type) {
if (track == NULL)
return;
if (name != track->mdpr_header.name) {
safefree(track->mdpr_header.name);
track->mdpr_header.name = safestrdup(name);
}
if (mime_type != track->mdpr_header.mime_type) {
safefree(track->mdpr_header.mime_type);
track->mdpr_header.mime_type = safestrdup(mime_type);
}
}
void
rmff_set_track_specific_data(rmff_track_t *track,
const unsigned char *data,
uint32_t size) {
if (track == NULL)
return;
if (data != track->mdpr_header.type_specific_data) {
safefree(track->mdpr_header.type_specific_data);
track->mdpr_header.type_specific_data =
(unsigned char *)safememdup(data, size);
2004-03-20 10:50:34 +00:00
rmff_put_uint32_be(&track->mdpr_header.type_specific_size, size);
}
}
rmff_track_t *
2004-03-20 18:57:24 +00:00
rmff_add_track(rmff_file_t *file,
int create_index) {
2004-03-20 10:50:34 +00:00
rmff_track_t track;
2004-03-20 18:57:24 +00:00
rmff_track_internal_t *tint;
2004-03-20 10:50:34 +00:00
int i, id, found;
if ((file == NULL) || (file->open_mode != RMFF_OPEN_MODE_WRITING))
return (rmff_track_t *)set_error(RMFF_ERR_PARAMETERS, NULL, 0);
id = 0;
do {
found = 0;
for (i = 0; i < file->num_tracks; i++)
if (file->tracks[i].id == id) {
found = 1;
id++;
break;
}
} while (found);
memset(&track, 0, sizeof(rmff_track_t));
track.id = id;
track.file = file;
2004-03-20 18:57:24 +00:00
tint = (rmff_track_internal_t *)safecalloc(sizeof(rmff_track_internal_t));
tint->index_this = create_index;
track.internal = tint;
2004-03-20 10:50:34 +00:00
file->tracks =
(rmff_track_t *)saferealloc(file->tracks, (file->num_tracks + 1) *
sizeof(rmff_track_t));
file->tracks[file->num_tracks] = track;
file->num_tracks++;
return &file->tracks[file->num_tracks - 1];
}
void
rmff_set_std_audio_v4_values(real_audio_v4_props_t *props) {
2004-03-21 10:04:19 +00:00
if (props == NULL)
return;
memset(props, 0, sizeof(real_audio_v4_props_t));
rmff_put_uint32_be(&props->fourcc1, rmffFOURCC('.', 'r', 'a', 0xfd));
rmff_put_uint16_be(&props->version1, 4);
rmff_put_uint32_be(&props->fourcc2, rmffFOURCC('.', 'r', 'a', '4'));
rmff_put_uint32_be(&props->stream_length, 0x10000000);
rmff_put_uint16_be(&props->version2, 4);
rmff_put_uint16_be(&props->header_size, 0x4e);
2004-03-20 10:50:34 +00:00
}
void
rmff_set_std_audio_v5_values(real_audio_v5_props_t *props) {
}
void
rmff_set_std_video_values(real_video_props_t *props) {
}
static int
write_prop_header(rmff_file_t *file) {
void *fh;
mb_file_io_t *io;
int bw;
io = file->io;
fh = file->handle;
/* Write the PROP header. */
bw = write_uint32_be(rmffFOURCC('P', 'R', 'O', 'P'));
bw += write_uint32_be(0x32); /* object_size */
bw += write_uint16_be(0); /* object_version */
bw += write_uint32_be_from(&file->prop_header.max_bit_rate);
bw += write_uint32_be_from(&file->prop_header.avg_bit_rate);
bw += write_uint32_be_from(&file->prop_header.max_packet_size);
bw += write_uint32_be_from(&file->prop_header.avg_packet_size);
bw += write_uint32_be_from(&file->prop_header.num_packets);
bw += write_uint32_be_from(&file->prop_header.duration);
bw += write_uint32_be_from(&file->prop_header.preroll);
bw += write_uint32_be_from(&file->prop_header.index_offset);
bw += write_uint32_be_from(&file->prop_header.data_offset);
bw += write_uint16_be_from(&file->prop_header.num_streams);
bw += write_uint16_be_from(&file->prop_header.flags);
return bw;
}
static int
write_cont_header(rmff_file_t *file) {
void *fh;
mb_file_io_t *io;
int bw, wanted_len, title_len, author_len, copyright_len, comment_len;
io = file->io;
fh = file->handle;
if (file->cont_header.title == NULL)
title_len = 0;
else
title_len = strlen(file->cont_header.title);
if (file->cont_header.author == NULL)
author_len = 0;
else
author_len = strlen(file->cont_header.author);
if (file->cont_header.copyright == NULL)
copyright_len = 0;
else
copyright_len = strlen(file->cont_header.copyright);
if (file->cont_header.comment == NULL)
comment_len = 0;
else
comment_len = strlen(file->cont_header.comment);
wanted_len = 4 + 4 + 2 + 4 * 2 + title_len + author_len + copyright_len +
comment_len;
/* Write the CONT header. */
bw = write_uint32_be(rmffFOURCC('C', 'O', 'N', 'T'));
bw += write_uint32_be(wanted_len); /* object_size */
bw += write_uint16_be(0); /* object_version */
bw += write_uint16_be(title_len);
if (file->cont_header.title != NULL)
bw += io->write(fh, file->cont_header.title, title_len);
bw += write_uint16_be(author_len);
if (file->cont_header.author != NULL)
bw += io->write(fh, file->cont_header.author, author_len);
bw += write_uint16_be(copyright_len);
if (file->cont_header.copyright != NULL)
bw += io->write(fh, file->cont_header.copyright, copyright_len);
bw += write_uint16_be(comment_len);
if (file->cont_header.comment != NULL)
bw += io->write(fh, file->cont_header.comment, comment_len);
if (bw == wanted_len)
return 0;
return set_error(RMFF_ERR_IO, "Could not write the CONT header",
RMFF_ERR_IO);
}
static int
write_mdpr_header(rmff_track_t *track) {
void *fh;
mb_file_io_t *io;
int bw, wanted_len, name_len, mime_type_len;
io = track->file->io;
fh = track->file->handle;
rmff_put_uint16_be(&track->mdpr_header.id, track->id);
if (track->mdpr_header.name == NULL)
name_len = 0;
else
name_len = strlen(track->mdpr_header.name);
if (track->mdpr_header.mime_type == NULL)
mime_type_len = 0;
else
mime_type_len = strlen(track->mdpr_header.mime_type);
wanted_len = 4 + 4 + 2 + 2 + 7 * 4 + 1 + name_len + 1 + mime_type_len +
4 + rmff_get_uint32_be(&track->mdpr_header.type_specific_size);
/* Write the MDPR header. */
bw = write_uint32_be(rmffFOURCC('M', 'D', 'P', 'R'));
bw += write_uint32_be(wanted_len); /* object_size */
bw += write_uint16_be(0); /* object_version */
bw += write_uint16_be_from(&track->mdpr_header.id);
bw += write_uint32_be_from(&track->mdpr_header.max_bit_rate);
bw += write_uint32_be_from(&track->mdpr_header.avg_bit_rate);
bw += write_uint32_be_from(&track->mdpr_header.max_packet_size);
bw += write_uint32_be_from(&track->mdpr_header.avg_packet_size);
bw += write_uint32_be_from(&track->mdpr_header.start_time);
bw += write_uint32_be_from(&track->mdpr_header.preroll);
bw += write_uint32_be_from(&track->mdpr_header.duration);
bw += write_uint8(name_len);
if (track->mdpr_header.name != NULL)
bw += io->write(fh, track->mdpr_header.name, name_len);
bw += write_uint8(mime_type_len);
if (track->mdpr_header.mime_type != NULL)
bw += io->write(fh, track->mdpr_header.mime_type, mime_type_len);
bw += write_uint32_be_from(&track->mdpr_header.type_specific_size);
if (track->mdpr_header.type_specific_data != NULL)
bw +=
io->write(fh, track->mdpr_header.type_specific_data,
rmff_get_uint32_be(&track->mdpr_header.type_specific_size));
if (wanted_len != bw)
return set_error(RMFF_ERR_IO, "Could not write the MDPR header",
RMFF_ERR_IO);
2004-03-20 18:57:24 +00:00
return clear_error();
}
static int
write_data_header(rmff_file_t *file) {
void *fh;
mb_file_io_t *io;
rmff_file_internal_t *fint;
int bw;
io = file->io;
fh = file->handle;
fint = (rmff_file_internal_t *)file->internal;
bw = write_uint32_be(rmffFOURCC('D', 'A', 'T', 'A'));
bw += write_uint32_be(fint->data_contents_size + 4 + 4 + 2 + 4 + 4 + 4);
bw += write_uint16_be(0); /* object_version */
bw += write_uint32_be(fint->num_packets); /* num_packets_in_chunk */
bw += write_uint32_be(0); /* next_data_header_offset */
if (bw != 18)
return set_error(RMFF_ERR_IO, "Could not write the DATA header",
RMFF_ERR_IO);
return clear_error();
2004-03-20 10:50:34 +00:00
}
int
rmff_write_headers(rmff_file_t *file) {
void *fh;
mb_file_io_t *io;
2004-03-20 18:57:24 +00:00
int i, bw, num_headers;
2004-03-20 10:50:34 +00:00
rmff_file_internal_t *fint;
const char *signature = ".RMF";
if ((file == NULL) || (file->open_mode != RMFF_OPEN_MODE_WRITING))
return set_error(RMFF_ERR_PARAMETERS, NULL, RMFF_ERR_PARAMETERS);
io = file->io;
fh = file->handle;
io->seek(fh, 0, SEEK_SET);
2004-03-20 18:57:24 +00:00
fint = (rmff_file_internal_t *)file->internal;
num_headers = 1 + /* PROP */
1 + /* DATA */
file->num_tracks + /* MDPR */
fint->num_index_chunks; /* INDX */
if (file->cont_header_present)
num_headers++;
2004-03-20 10:50:34 +00:00
/* Write the file header. */
bw = io->write(fh, signature, 4);
bw += write_uint32_be(0x12); /* header_size */
bw += write_uint16_be(1); /* object_version */
bw += write_uint32_be(0); /* file_version */
2004-03-20 18:57:24 +00:00
bw += write_uint32_be(num_headers);
2004-03-20 10:50:34 +00:00
if (bw != 18)
return set_error(RMFF_ERR_IO, "Could not write the file header",
RMFF_ERR_IO);
bw = write_prop_header(file);
if (bw != 0x32)
return set_error(RMFF_ERR_IO, "Could not write the PROP header",
RMFF_ERR_IO);
if (file->cont_header_present) {
bw = write_cont_header(file);
if (bw != RMFF_ERR_OK)
return bw;
}
for (i = 0; i < file->num_tracks; i++) {
bw = write_mdpr_header(&file->tracks[i]);
if (bw < RMFF_ERR_OK)
return bw;
2004-03-18 20:47:24 +00:00
}
2004-03-20 10:50:34 +00:00
fint->data_offset = io->tell(fh);
2004-03-20 18:57:24 +00:00
bw = write_data_header(file);
if (bw < RMFF_ERR_OK)
return bw;
return clear_error();
2004-03-20 10:50:34 +00:00
}
int
rmff_fix_headers(rmff_file_t *file) {
2004-03-20 18:57:24 +00:00
rmff_prop_t *prop;
rmff_mdpr_t *mdpr;
rmff_track_t *track;
rmff_file_internal_t *fint;
rmff_track_internal_t *tint;
int i;
if ((file == NULL) || (file->open_mode != RMFF_OPEN_MODE_WRITING))
return set_error(RMFF_ERR_PARAMETERS, NULL, RMFF_ERR_PARAMETERS);
fint = (rmff_file_internal_t *)file->internal;
2004-03-21 10:04:19 +00:00
if (fint->highest_timecode > 0)
fint->avg_bit_rate = (int64_t)fint->total_bytes * 8 * 1000 /
fint->highest_timecode;
2004-03-20 18:57:24 +00:00
prop = &file->prop_header;
rmff_put_uint32_be(&prop->max_bit_rate, fint->max_bit_rate);
rmff_put_uint32_be(&prop->avg_bit_rate, fint->avg_bit_rate);
rmff_put_uint32_be(&prop->max_packet_size, fint->max_packet_size);
rmff_put_uint32_be(&prop->avg_packet_size, fint->avg_packet_size);
rmff_put_uint32_be(&prop->num_packets, fint->num_packets);
rmff_put_uint32_be(&prop->duration, fint->highest_timecode);
rmff_put_uint32_be(&prop->index_offset, fint->index_offset);
rmff_put_uint32_be(&prop->data_offset, fint->data_offset);
rmff_put_uint16_be(&prop->num_streams, file->num_tracks);
for (i = 0; i < file->num_tracks; i++) {
track = &file->tracks[i];
mdpr = &track->mdpr_header;
tint = (rmff_track_internal_t *)track->internal;
2004-03-21 10:04:19 +00:00
if (tint->highest_timecode > 0)
tint->avg_bit_rate = (int64_t)tint->total_bytes * 8 * 1000 /
tint->highest_timecode;
2004-03-20 18:57:24 +00:00
rmff_put_uint16_be(&mdpr->id, track->id);
rmff_put_uint32_be(&mdpr->max_bit_rate, tint->max_bit_rate);
rmff_put_uint32_be(&mdpr->avg_bit_rate, tint->avg_bit_rate);
rmff_put_uint32_be(&mdpr->max_packet_size, tint->max_packet_size);
rmff_put_uint32_be(&mdpr->avg_packet_size, tint->avg_packet_size);
rmff_put_uint32_be(&mdpr->duration, tint->highest_timecode);
}
return rmff_write_headers(file);
2004-03-20 10:50:34 +00:00
}
void
rmff_copy_track_headers(rmff_track_t *dst,
rmff_track_t *src) {
if ((dst == NULL) || (src == NULL))
return;
2004-03-20 18:57:24 +00:00
safefree(dst->mdpr_header.name);
safefree(dst->mdpr_header.mime_type);
safefree(dst->mdpr_header.type_specific_data);
2004-03-20 10:50:34 +00:00
memcpy(&dst->mdpr_header, &src->mdpr_header, sizeof(rmff_mdpr_t));
dst->mdpr_header.name = safestrdup(src->mdpr_header.name);
dst->mdpr_header.mime_type = safestrdup(src->mdpr_header.mime_type);
dst->mdpr_header.type_specific_data = (unsigned char *)
safememdup(src->mdpr_header.type_specific_data,
rmff_get_uint32_be(&src->mdpr_header.type_specific_size));
dst->type = src->type;
2004-03-18 20:47:24 +00:00
}
2004-03-20 18:57:24 +00:00
int
rmff_write_frame(rmff_track_t *track,
rmff_frame_t *frame) {
void *fh;
mb_file_io_t *io;
rmff_file_internal_t *fint;
rmff_track_internal_t *tint;
int bw, wanted_len;
2004-03-21 10:04:19 +00:00
uint32_t bit_rate;
2004-03-20 18:57:24 +00:00
int64_t pos;
if ((track == NULL) || (frame == NULL) || (frame->data == NULL) ||
(track->file->open_mode != RMFF_OPEN_MODE_WRITING))
return set_error(RMFF_ERR_PARAMETERS, NULL, RMFF_ERR_PARAMETERS);
io = track->file->io;
fh = track->file->handle;
fint = (rmff_file_internal_t *)track->file->internal;
tint = (rmff_track_internal_t *)track->internal;
pos = io->tell(fh);
if (tint->index_this && ((frame->flags & 2) == 2))
add_to_index(track, pos, frame->timecode, fint->num_packets);
wanted_len = 2 + 2 + 2 + 4 + 1 + 1 + frame->size;
bw = write_uint16_be(0); /* object_version */
bw += write_uint16_be(wanted_len);
bw += write_uint16_be(track->id);
bw += write_uint32_be(frame->timecode);
bw += write_uint8(0); /* reserved */
bw += write_uint8(frame->flags);
bw += io->write(fh, frame->data, frame->size);
if (bw != wanted_len)
return set_error(RMFF_ERR_IO, "Could not write the frame", RMFF_ERR_IO);
if (wanted_len > fint->max_packet_size)
fint->max_packet_size = wanted_len;
fint->avg_packet_size = (fint->avg_packet_size * fint->num_packets +
wanted_len) / (fint->num_packets + 1);
fint->num_packets++;
2004-03-21 10:04:19 +00:00
fint->total_bytes += frame->size;
2004-03-20 18:57:24 +00:00
if (frame->timecode > fint->highest_timecode)
fint->highest_timecode = frame->timecode;
fint->data_contents_size += wanted_len;
if (wanted_len > tint->max_packet_size)
tint->max_packet_size = wanted_len;
tint->avg_packet_size = (tint->avg_packet_size * tint->num_packets +
wanted_len) / (tint->num_packets + 1);
tint->num_packets++;
2004-03-21 10:04:19 +00:00
tint->total_bytes += frame->size;
/* Update the max_bit_rates. Crude... */
2004-03-20 18:57:24 +00:00
if (frame->timecode > tint->highest_timecode)
tint->highest_timecode = frame->timecode;
2004-03-21 10:04:19 +00:00
if (frame->timecode != tint->last_timecode) {
if (tint->last_timecode != 0) {
bit_rate = (int64_t)tint->bytes_since_timecode_change * 8 * 1000 /
(frame->timecode - tint->last_timecode);
if (bit_rate > tint->max_bit_rate)
tint->max_bit_rate = bit_rate;
if (bit_rate > fint->max_bit_rate)
fint->max_bit_rate = bit_rate;
}
tint->last_timecode = frame->timecode;
tint->bytes_since_timecode_change = frame->size;
} else
tint->bytes_since_timecode_change += frame->size;
2004-03-20 18:57:24 +00:00
return clear_error();
}
rmff_track_t *
rmff_find_track_with_id(rmff_file_t *file,
uint16_t id) {
int i;
clear_error();
if (file == 0)
return (rmff_track_t *)set_error(RMFF_ERR_PARAMETERS, NULL, 0);
for (i = 0; i < file->num_tracks; i++)
if (file->tracks[i].id == id)
return &file->tracks[i];
return NULL;
}
int
rmff_write_index(rmff_file_t *file) {
void *fh;
mb_file_io_t *io;
rmff_file_internal_t *fint;
rmff_track_internal_t *tint;
rmff_track_t *track;
int i, j, bw, wanted_len;
int64_t pos;
if ((file == NULL) || (file->open_mode != RMFF_OPEN_MODE_WRITING))
return set_error(RMFF_ERR_PARAMETERS, NULL, RMFF_ERR_PARAMETERS);
fh = file->handle;
io = file->io;
fint = (rmff_file_internal_t *)file->internal;
fint->num_index_chunks = 0;
for (i = 0; i < file->num_tracks; i++)
if (file->tracks[i].num_index_entries > 0)
fint->num_index_chunks++;
if (fint->num_index_chunks == 0)
return clear_error();
io->seek(fh, 0, SEEK_END);
for (i = 0; i < file->num_tracks; i++) {
track = &file->tracks[i];
tint = (rmff_track_internal_t *)track->internal;
if (track->num_index_entries > 0) {
pos = io->tell(fh);
if (fint->index_offset == 0)
fint->index_offset = pos;
wanted_len = 4 + 4 + 2 + /* normal chunk header */
4 + /* num_entries */
2 + /* track_id */
4 + /* next_header_pos */
(2 + /* version */
4 + /* timecode */
4 + /* offset */
4) * /* packet_number */
track->num_index_entries;
bw = write_uint32_be(rmffFOURCC('I', 'N', 'D', 'X'));
bw += write_uint32_be(wanted_len);
bw += write_uint16_be(0); /* object_version */
bw += write_uint32_be(track->num_index_entries);
bw += write_uint16_be(track->id);
if ((i + 1) < fint->num_index_chunks)
bw += write_uint32_be(pos + wanted_len); /* next_indx_offset */
else
bw += write_uint32_be(0); /* no next_indx_chunk */
for (j = 0; j < track->num_index_entries; j++) {
bw += write_uint16_be(0); /* version */
bw += write_uint32_be(track->index[j].timecode);
bw += write_uint32_be(track->index[j].pos);
bw += write_uint32_be(track->index[j].packet_number);
}
if (bw != wanted_len)
return set_error(RMFF_ERR_IO, "Could not write the INDX chunk",
RMFF_ERR_IO);
}
}
return clear_error();
}