diff --git a/Makefile b/Makefile index 1698f8440..162177be0 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ LD=$(CXX) READERS_SOURCES=r_avi.cpp READERS_OBJECTS=$(patsubst %.cpp,%.o,$(READERS_SOURCES)) -OTHER_SOURCES=common.cpp +OTHER_SOURCES=common.cpp queue.cpp OTHER_OBJECTS=$(patsubst %.cpp,%.o,$(OTHER_SOURCES)) MKVMERGE_OBJECTS=$(READERS_OBJECTS) $(OTHER_OBJECTS) diff --git a/common.h b/common.h index 7898d8086..4270a077e 100644 --- a/common.h +++ b/common.h @@ -15,6 +15,49 @@ #ifndef __COMMON_H__ #define __COMMON_H__ +#define VERSION "0.0.1" +#define VERSIONINFO "mkvmerge v" VERSION + +#define DISPLAYPRIORITY_HIGH 10 +#define DISPLAYPRIORITY_MEDIUM 5 +#define DISPLAYPRIORITY_LOW 1 + +/* errors */ +#define EMOREDATA -1 +#define EMALLOC -2 +#define EBADHEADER -3 +#define EBADEVENT -4 +#define EOTHER -5 + +/* types */ +#define TYPEUNKNOWN 0 +#define TYPEOGM 1 +#define TYPEAVI 2 +#define TYPEWAV 3 +#define TYPESRT 4 +#define TYPEMP3 5 +#define TYPEAC3 6 +#define TYPECHAPTERS 7 +#define TYPEMICRODVD 8 +#define TYPEVOBSUB 9 + +#define FOURCC(a, b, c, d) (unsigned long)((((unsigned char)a) << 24) + \ + (((unsigned char)b) << 16) + \ + (((unsigned char)c) << 8) + \ + ((unsigned char)d)) +typedef struct { + int displacement; + double linear; +} audio_sync_t; + +typedef struct { + double start; + double end; +} range_t; + +typedef double stamp_t; +#define MAX_TIMESTAMP ((double)3.40282347e+38F) + #define die(s) _die(s, __FILE__, __LINE__) void _die(const char *s, const char *file, int line); diff --git a/error.h b/error.h new file mode 100644 index 000000000..6313669f5 --- /dev/null +++ b/error.h @@ -0,0 +1,30 @@ +/* + mkvmerge -- utility for splicing together matroska files + from component media subtypes + + error.h + class definitions for the error class + + Written by Moritz Bunkus + + Distributed under the GPL + see the file COPYING for details + or visit http://www.gnu.org/copyleft/gpl.html +*/ + +#ifndef __ERROR_H__ +#define __ERROR_H__ + +#include +#include + +class error_c { + private: + char *error; + public: + error_c(char *nerror) { error = strdup(nerror); }; + ~error_c() { free(error); }; + char *get_error() { return error; }; +}; + +#endif // __ERROR_H__ diff --git a/mkvmerge.cpp b/mkvmerge.cpp index 87306515d..51a6f7ea2 100644 --- a/mkvmerge.cpp +++ b/mkvmerge.cpp @@ -17,11 +17,19 @@ /*! \file - \version \$Id: mkvmerge.cpp,v 1.2 2003/02/16 00:47:52 mosu Exp $ + \version \$Id: mkvmerge.cpp,v 1.3 2003/02/16 11:44:19 mosu Exp $ \brief create matroska files from other media files, main file \author Moritz Bunkus */ +#include +#include +#include +#include +#include +#include +#include + #include #include "StdIOCallback.h" @@ -39,105 +47,864 @@ #include "KaxBlock.h" #include "KaxBlockAdditional.h" +#include "common.h" +#include "queue.h" +#include "r_avi.h" + +#ifdef DMALLOC +#include +#endif + using namespace LIBMATROSKA_NAMESPACE; -int main(int argc, char **argv) { - char *file1; - char *file2; - uint64 fsize; +typedef struct { + char *ext; + int type; + char *desc; +} file_type_t; + +typedef struct filelist_tag { + char *name; + FILE *fp; + + int type; + + int status; - if (argc < 2) { - fprintf(stderr, "(%s) Input, output missing\n", __FILE__); + packet_t *pack; + + generic_reader_c *reader; + + struct filelist_tag *next; +} filelist_t; + +char *outfile = NULL; +filelist_t *input; +int create_index = 0; +int force_flushing = 0; + +float video_fps = -1.0; + +/*int idx_num = 0; +int *idx_serials = NULL; +int *idx_num_entries = NULL; +idx_entry **idx_entries = NULL; +index_packetizer_c **idx_packetizers = NULL;*/ + +file_type_t file_types[] = + {{"---", TYPEUNKNOWN, ""}, + {"demultiplexers:", -1, ""}, + {"ogg", TYPEOGM, "general OGG media stream, Vorbis audio embedded in OGG"}, + {"avi", TYPEAVI, "AVI (Audio/Video Interleaved)"}, + {"wav", TYPEWAV, "WAVE (uncompressed PCM)"}, + {"srt", TYPEWAV, "SRT text subtitles"}, + {" ", TYPEMICRODVD, "MicroDVD text subtitles"}, + {"idx", TYPEVOBSUB, "VobSub subtitles"}, + {"mp3", TYPEMP3, "MPEG1 layer III audio (CBR and VBR/ABR)"}, + {"ac3", TYPEAC3, "A/52 (aka AC3)"}, + {"output modules:", -1, ""}, + {" ", -1, "Vorbis audio"}, + {" ", -1, "Video (not MPEG1/2)"}, + {" ", -1, "uncompressed PCM audio"}, + {" ", -1, "text subtitles"}, + {" ", -1, "VobSub subtitles"}, + {" ", -1, "MP3 audio"}, + {" ", -1, "AC3 audio"}, + {NULL, -1, NULL}}; + +static void usage(void) { + fprintf(stdout, + "mkvmerge -o out [global options] [options] [[options] ...]" + "\n\n Global options:\n" + " -v, --verbose verbose status\n" + " -q, --quiet suppress status output\n" + " -o, --output out Write to the file 'out'.\n" + " -i, --index Create index for the video streams.\n" + "\n Options for each input file:\n" + " -a, --astreams Copy the n'th audio stream, NOT the stream with" + "\n the serial number n. Default: copy all audio\n" + " streams.\n" + " -d, --vstreams Copy the n'th video stream, NOT the stream with" + "\n the serial number n. Default: copy all video\n" + " streams.\n" + " -t, --tstreams Copy the n'th text stream, NOT the stream with" + "\n the serial number n. Default: copy all text\n" + " streams.\n" + " -A, --noaudio Don't copy any audio stream from this file.\n" + " -D, --novideo Don't copy any video stream from this file.\n" + " -T, --notext Don't copy any text stream from this file.\n" + " -s, --sync Ssynchronize, delay the audio stream by d ms.\n" + " d > 0: Pad with silent samples.\n" + " d < 0: Remove samples from the beginning.\n" + " o/p: Adjust the timestamps by o/p to fix\n" + " linear drifts. p defaults to 1000 if\n" + " omitted. Both o and p can be floating point\n" + " numbers.\n" + " -r, --range Only process from start to end. Both values\n" + " take the form 'HH:MM:SS.mmm' or 'SS.mmm',\n" + " e.g. '00:01:00.500' or '60.500'. If one of\n" + " s or e is omitted then it defaults to 0 or\n" + " to end of the file respectively.\n" + " -c, --comment 'A=B#C=D' Set additional comment fields for the\n" + " streams. Sesitive values would be\n" + " 'LANGUAGE=English' or 'TITLE=Ally McBeal'.\n" + " -f, --fourcc Forces the FourCC to the specified value.\n" + " Works only for video streams.\n" + "\n" + " Other options:\n" + " -l, --list-types Lists supported input file types.\n" + " -h, --help Show this help.\n" + " -V, --version Show version information.\n" + ); +} + +static void set_defaults(void) { + /* set defaults */ + outfile = NULL; + input = NULL; + verbose = 1; +} + +static int get_type(char *filename) { + FILE *f = fopen(filename, "r"); + u_int64_t size; + + if (f == NULL) { + fprintf(stderr, "Error: could not open source file (%s).\n", filename); exit(1); } - file1 = argv[1]; - file2 = argv[2]; - - try { - // write the head of the file (with everything already configured) - StdIOCallback out_file(file2, MODE_CREATE); - - ///// Writing EBML test - EbmlHead FileHead; - - EDocType & MyDocType = GetChild(FileHead); - *static_cast(&MyDocType) = "matroska"; - - EDocTypeVersion & MyDocTypeVer = GetChild(FileHead); - *(static_cast(&MyDocTypeVer)) = 1; - - EDocTypeReadVersion & MyDocTypeReadVer = - GetChild(FileHead); - *(static_cast(&MyDocTypeReadVer)) = 1; - - FileHead.Render(out_file); - - KaxSegment FileSegment; - // size is unknown and will always be, we can render it right away - FileSegment.Render(out_file); - - KaxTracks & MyTracks = GetChild(FileSegment); - - // fill track 1 params - KaxTrackEntry & MyTrack1 = GetChild(MyTracks); - - KaxTrackNumber & MyTrack1Number = GetChild(MyTrack1); - *(static_cast(&MyTrack1Number)) = 1; - - *(static_cast(&GetChild(MyTrack1))) = - track_audio; - - KaxCodecID & MyTrack1CodecID = GetChild(MyTrack1); - MyTrack1CodecID.CopyBuffer((binary *)"Dummy Audio Codec", - countof("Dummy Audio Codec")); - - // audio specific params - KaxTrackAudio & MyTrack1Audio = GetChild(MyTrack1); - - KaxAudioSamplingFreq &MyTrack1Freq = - GetChild(MyTrack1Audio); - *(static_cast(&MyTrack1Freq)) = 44100.0; - MyTrack1Freq.ValidateSize(); - - KaxAudioChannels & MyTrack1Channels = - GetChild(MyTrack1Audio); - *(static_cast(&MyTrack1Channels)) = 2; - - MyTracks.Render(out_file); - - // creation of the original binary/raw files - StdIOCallback in_file(file1, MODE_READ); - - in_file.setFilePointer(0L, seek_end); - - fsize = (unsigned int)in_file.getFilePointer(); - - binary *buf_in = new binary[fsize]; - - // start muxing (2 binary frames for each text frame) - in_file.setFilePointer(0L, seek_beginning); - - in_file.read(buf_in, fsize); - - KaxCluster cluster; - KaxClusterTimecode & MyClusterTimecode = - GetChild(cluster); - *(static_cast(&MyClusterTimecode)) = 0; // base timecode - - KaxBlockGroup & MyBlockG1 = GetChild(cluster); - KaxBlock & MyBlock1 = GetChild(MyBlockG1); - - DataBuffer data1 = DataBuffer((binary *)buf_in, fsize); - MyBlock1.AddFrame(MyTrack1, 10, data1); - - cluster.Render(out_file); - - delete[] buf_in; - - out_file.close(); - } catch (std::exception & Ex) { - std::cout << Ex.what() << std::endl; + if (fseek(f, 0, SEEK_END) != 0) { + fprintf(stderr, "Error: could not seek to end of file (%s).\n", filename); + exit(1); } + size = ftell(f); + if (fseek(f, 0, SEEK_SET) != 0) { + fprintf(stderr, "Error: could not seek to beginning of file (%s).\n", + filename); + exit(1); + } + if (avi_reader_c::probe_file(f, size)) + return TYPEAVI; +/* else if (wav_reader_c::probe_file(f, size)) + return TYPEWAV; + else if (ogm_reader_c::probe_file(f, size)) + return TYPEOGM; + else if (srt_reader_c::probe_file(f, size)) + return TYPESRT; + else if (mp3_reader_c::probe_file(f, size)) + return TYPEMP3; + else if (ac3_reader_c::probe_file(f, size)) + return TYPEAC3; + else if (microdvd_reader_c::probe_file(f, size)) + return TYPEMICRODVD; + else if (vobsub_reader_c::probe_file(f, size)) + return TYPEVOBSUB; + else if (chapter_information_probe(f, size)) + return TYPECHAPTERS; + else*/ + return TYPEUNKNOWN; +} + +static void add_file(filelist_t *file) { + filelist_t *temp; + + if (input == NULL) { + input = file; + } else { + temp = input; + while (temp->next) temp = temp->next; + temp->next = file; + } +} + +static int display_counter = 1; + +void display_progress(int force) { + filelist_t *winner, *current; + + if (((display_counter % 500) == 0) || force) { + display_counter = 0; + winner = input; + current = winner->next; + while (current) { + if (current->reader->display_priority() > + winner->reader->display_priority()) + winner = current; + current = current->next; + } + winner->reader->display_progress(); + } + display_counter++; +} + +static unsigned char *parse_streams(char *s) { + char *c = s; + char *nstart; + int n, nstreams; + unsigned char *streams; + + nstart = NULL; + streams = NULL; + nstreams = 0; + while (*c) { + if ((*c >= '0') && (*c <= '9')) { + if (nstart == NULL) + nstart = c; + } else if (*c == ',') { + *c = 0; + if (nstart != NULL) { + n = atoi(nstart); + if ((n <= 0) || (n > 255)) { + fprintf(stderr, "Error: stream number out of range (1..255): %d\n", + n); + exit(1); + } + streams = (unsigned char *)realloc(streams, nstreams + 2); + if (streams == NULL) + die("malloc"); + streams[nstreams] = (unsigned char)n; + streams[nstreams + 1] = 0; + nstart = NULL; + nstreams++; + } else + fprintf(stderr, "Warning: useless use of ','\n"); + } else if (!isspace(*c)) { + fprintf(stderr, "Error: unrecognized character in stream list: '%c'\n", + *c); + exit(1); + } + c++; + } + + if (nstart != NULL) { + n = atoi(nstart); + if ((n <= 0) || (n > 255)) { + fprintf(stderr, "Error: stream number out of range (1..255): %d\n", + n); + exit(1); + } + streams = (unsigned char *)realloc(streams, nstreams + 2); + if (streams == NULL) + die("malloc"); + streams[nstreams] = (unsigned char)n; + streams[nstreams + 1] = 0; + nstart = NULL; + nstreams++; + } + + return streams; +} + +static void parse_sync(char *s, audio_sync_t *async) { + char *linear, *div; + double d1, d2; + + if ((linear = strchr(s, ',')) != NULL) { + *linear = 0; + linear++; + div = strchr(linear, '/'); + if (div == NULL) + async->linear = strtod(linear, NULL) / 1000.0; + else { + *div = 0; + div++; + d1 = strtod(linear, NULL); + d2 = strtod(div, NULL); + if (d2 == 0.0) { + fprintf(stderr, "Error: linear sync: division by zero?\n"); + exit(1); + } + async->linear = d1 / d2; + } + if (async->linear <= 0.0) { + fprintf(stderr, "Error: linear sync value may not be <= 0.\n"); + exit(1); + } + } else + async->linear = 1.0; + async->displacement = atoi(s); +} + +static double parse_time(char *s) { + char *c, *a, *dot; + int num_colons; + double seconds; + + dot = strchr(s, '.'); + if (dot != NULL) { + *dot = 0; + dot++; + } + for (c = s, num_colons = 0; *c; c++) { + if (*c == ':') + num_colons++; + else if ((*c < '0') || (*c > '9')) { + fprintf(stderr, "ERROR: illegal character '%c' in time range.\n", *c); + exit(1); + } + } + if (num_colons > 2) { + fprintf(stderr, "ERROR: illegal time range: %s.\n", s); + exit(1); + } + if (num_colons == 0) { + seconds = strtod(s, NULL); + if (dot != NULL) + seconds += strtod(dot, NULL) / 1000.0; + } + for (a = s, c = s, seconds = 0; *c; c++) { + if (*c == ':') { + *c = 0; + seconds *= 60; + seconds += atoi(a); + a = c + 1; + } + } + seconds *= 60; + seconds += atoi(a); + + if (dot != NULL) + seconds += strtod(dot, NULL) / 1000.0; + + return seconds; +} + +static void parse_range(char *s, range_t *range) { + char *end; + + end = strchr(s, '-'); + if (end != NULL) { + *end = 0; + end++; + range->end = parse_time(end); + } else + range->end = 0; + range->start = parse_time(s); + if ((range->end != 0) && (range->end < range->start)) { + fprintf(stderr, "ERROR: end time is set before start time.\n"); + exit(1); + } +} + +static void parse_args(int argc, char **argv) { + int i, j; + int noaudio, novideo, notext; + unsigned char *astreams, *vstreams, *tstreams; + filelist_t *file; + audio_sync_t async; + range_t range; + char **comments, *fourcc; +// vorbis_comment *chapters; + + noaudio = 0; + novideo = 0; + notext = 0; + astreams = NULL; + vstreams = NULL; + tstreams = NULL; + memset(&range, 0, sizeof(range_t)); + async.displacement = 0; + async.linear = 1.0; + comments = NULL; + fourcc = NULL; +// chapters = NULL; + for (i = 1; i < argc; i++) + if (!strcmp(argv[i], "-V") || !strcmp(argv[i], "--version")) { + fprintf(stdout, "mkvmerge v" VERSION "\n"); + exit(0); + } else if (!strcmp(argv[i], "-i") || !strcmp(argv[i], "--index")) + create_index = 1; + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-q")) + verbose = 0; + else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose")) + verbose = 2; + else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-?") || + !strcmp(argv[i], "--help")) { + usage(); + exit(0); + } else if (!strcmp(argv[i], "-o") || !strcmp(argv[i], "--output")) { + if ((i + 1) >= argc) { + fprintf(stderr, "Error: -o lacks a file name.\n"); + exit(1); + } else if (outfile != NULL) { + fprintf(stderr, "Error: only one output file allowed.\n"); + exit(1); + } + outfile = (char *)strdup(argv[i + 1]); + i++; + } else if (!strcmp(argv[i], "-l") || !strcmp(argv[i], "--list-types")) { + fprintf(stdout, "Known file types:\n ext description\n" \ + " --- --------------------------\n"); + for (j = 1; file_types[j].ext; j++) + fprintf(stdout, " %s %s\n", file_types[j].ext, file_types[j].desc); + exit(0); + } else if (!strcmp(argv[i], "-A") || !strcmp(argv[i], "--noaudio")) + noaudio = 1; + else if (!strcmp(argv[i], "-D") || !strcmp(argv[i], "--novideo")) + novideo = 1; + else if (!strcmp(argv[i], "-T") || !strcmp(argv[i], "--notext")) + notext = 1; + else if (!strcmp(argv[i], "-a") || !strcmp(argv[i], "--astreams")) { + if ((i + 1) >= argc) { + fprintf(stderr, "Error: -a lacks the stream number(s).\n"); + exit(1); + } + if (astreams != NULL) + free(astreams); + astreams = parse_streams(argv[i + 1]); + i++; + } else if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--vstreams")) { + if ((i + 1) >= argc) { + fprintf(stderr, "Error: -d lacks the stream number(s).\n"); + exit(1); + } + if (astreams != NULL) + free(vstreams); + vstreams = parse_streams(argv[i + 1]); + i++; + } else if (!strcmp(argv[i], "-t") || !strcmp(argv[i], "--tstreams")) { + if ((i + 1) >= argc) { + fprintf(stderr, "Error: -t lacks the stream number(s).\n"); + exit(1); + } + if (tstreams != NULL) + free(tstreams); + tstreams = parse_streams(argv[i + 1]); + i++; +/* } else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--comments")) { + if ((i + 1) >= argc) { + fprintf(stderr, "Error: -c lacks the comments.\n"); + exit(1); + } + if (argv[i + 1][0] == '@') + comments = append_comments_from_file(argv[i + 1], comments); + else + comments = unpack_append_comments(argv[i + 1], comments); +#ifdef DEBUG + dump_comments(comments); +#endif // DEBUG + i++;*/ + } else if (!strcmp(argv[i], "-f") || !strcmp(argv[i], "--fourcc")) { + if ((i + 1) >= argc) { + fprintf(stderr, "Error: -f lacks the FourCC.\n"); + exit(1); + } + fourcc = argv[i + 1]; + if (strlen(fourcc) != 4) { + fprintf(stderr, "Error: The FourCC must be exactly four chars long.\n"); + exit(1); + } + i++; + } else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--sync")) { + if ((i + 1) >= argc) { + fprintf(stderr, "Error: -s lacks the audio delay.\n"); + exit(1); + } + parse_sync(argv[i + 1], &async); + i++; + } else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--range")) { + if ((i + 1) >= argc) { + fprintf(stderr, "Error: -r lacks the range.\n"); + exit(1); + } + parse_range(argv[i + 1], &range); + i++; + } else if (!strcmp(argv[i], "-i") || !strcmp(argv[i], "--index")) + create_index = 1; + else { + if ((astreams != NULL) && noaudio) { + fprintf(stderr, "Error: -A and -a used on the same source file.\n"); + exit(1); + } + if ((vstreams != NULL) && novideo) { + fprintf(stderr, "Error: -D and -d used on the same source file.\n"); + exit(1); + } + if ((tstreams != NULL) && notext) { + fprintf(stderr, "Error: -T and -t used on the same source file.\n"); + exit(1); + } + if (noaudio) { + astreams = (unsigned char *)malloc(1); + if (astreams == NULL) + die("malloc"); + *astreams = 0; + } + if (novideo) { + vstreams = (unsigned char *)malloc(1); + if (vstreams == NULL) + die("malloc"); + *vstreams = 0; + } + if (notext) { + tstreams = (unsigned char *)malloc(1); + if (tstreams == NULL) + die("malloc"); + *tstreams = 0; + } + file = (filelist_t *)malloc(sizeof(filelist_t)); + if (file == NULL) + die("malloc"); + + file->name = argv[i]; + file->type = get_type(file->name); + + if (file->type == TYPEUNKNOWN) { + fprintf(stderr, "Error: File %s has unknown type. Please have a look " + "at the supported file types ('mkvmerge --list-types') and " + "contact me at moritz@bunkus.org if your file type is " + "supported but not recognized properly.\n", file->name); + exit(1); + } + + file->fp = NULL; + try { + switch (file->type) { +/* case TYPEOGM: + file->reader = new ogm_reader_c(file->name, astreams, vstreams, + tstreams, &async, &range, + comments, fourcc); + break;*/ + case TYPEAVI: + if (tstreams != NULL) + fprintf(stderr, "Warning: -t/-T are ignored for AVI files.\n"); + file->reader = new avi_reader_c(file->name);/*, astreams, vstreams, + &async, &range, comments, fourcc);*/ + break; +/* case TYPEWAV: + if ((astreams != NULL) || (vstreams != NULL) || + (tstreams != NULL)) + fprintf(stderr, "Warning: -a/-A/-d/-D/-t/-T are ignored for " \ + "WAVE files.\n"); + file->reader = new wav_reader_c(file->name, &async, &range, + comments); + break; + case TYPESRT: + if ((astreams != NULL) || (vstreams != NULL) || + (tstreams != NULL)) + fprintf(stderr, "Warning: -a/-A/-d/-D/-t/-T are ignored for " \ + "SRT files.\n"); + file->reader = new srt_reader_c(file->name, &async, &range, + comments); + break; + case TYPEMP3: + if ((astreams != NULL) || (vstreams != NULL) || + (tstreams != NULL)) + fprintf(stderr, "Warning: -a/-A/-d/-D/-t/-T are ignored for " \ + "MP3 files.\n"); + file->reader = new mp3_reader_c(file->name, &async, &range, + comments); + break; + case TYPEAC3: + if ((astreams != NULL) || (vstreams != NULL) || + (tstreams != NULL)) + fprintf(stderr, "Warning: -a/-A/-d/-D/-t/-T are ignored for " \ + "AC3 files.\n"); + file->reader = new ac3_reader_c(file->name, &async, &range, + comments); + break; + case TYPECHAPTERS: + if (chapters != NULL) { + fprintf(stderr, "Error: only one chapter file allowed.\n"); + exit(1); + } + chapters = chapter_information_read(file->name); + break; + case TYPEMICRODVD: + if ((astreams != NULL) || (vstreams != NULL) || + (tstreams != NULL)) + fprintf(stderr, "Warning: -a/-A/-d/-D/-t/-T are ignored for " \ + "MicroDVD files.\n"); + file->reader = new microdvd_reader_c(file->name, &async, &range, + comments); + break; + case TYPEVOBSUB: + if ((astreams != NULL) || (vstreams != NULL) || + (tstreams != NULL)) + fprintf(stderr, "Warning: -a/-A/-d/-D/-t/-T are ignored for " \ + "VobSub files.\n"); + file->reader = new vobsub_reader_c(file->name, &async, &range, + comments); + break;*/ + default: + fprintf(stderr, "EVIL internal bug! (unknown file type)\n"); + exit(1); + break; + } + } catch (error_c error) { + fprintf(stderr, "Demultiplexer failed to initialize:\n%s\n", + error.get_error()); + exit(1); + } + if (file->type != TYPECHAPTERS) { + file->status = EMOREDATA; + file->next = NULL; + file->pack = NULL; + + add_file(file); + } else + free(file); + +// free_comments(comments); +// comments = NULL; + fourcc = NULL; + noaudio = 0; + novideo = 0; + notext = 0; + if (astreams != NULL) { + free(astreams); + astreams = NULL; + } + if (vstreams != NULL) { + free(vstreams); + vstreams = NULL; + } + async.displacement = 0; + async.linear = 1.0; + memset(&range, 0, sizeof(range_t)); + } + } + + if (input == NULL) { + usage(); + exit(1); + } + if (outfile == NULL) { + fprintf(stderr, "Error: no output files given.\n"); + exit(1); + } +/* if (chapters != NULL) { + file = input; + while (file != NULL) { + file->reader->set_chapter_info(chapters); + file = file->next; + } + vorbis_comment_clear(chapters); + free(chapters); + chapters = NULL; + }*/ +} + +/*void add_index(int serial) { + int i, found = -1; + + if (!create_index) + return; + + for (i = 0; i < idx_num; i++) + if (idx_serials[i] == serial) { + found = i; + break; + } + + if (found == -1) { + idx_serials = (int *)realloc(idx_serials, (idx_num + 1) * sizeof(int)); + if (idx_serials == NULL) + die("realloc"); + idx_num_entries = (int *)realloc(idx_num_entries, (idx_num + 1) * + sizeof(int)); + if (idx_num_entries == NULL) + die("realloc"); + idx_packetizers = (index_packetizer_c **) + realloc(idx_packetizers, (idx_num + 1) * sizeof(index_packetizer_c)); + if (idx_packetizers == NULL) + die("realloc"); + idx_entries = (idx_entry **)realloc(idx_entries, (idx_num + 1) * + sizeof(idx_entry)); + if (idx_entries == NULL) + die("realloc"); + i = idx_num; + idx_serials[i] = serial; + idx_num_entries[i] = 0; + idx_entries[i] = NULL; + idx_num++; + try { + idx_packetizers[i] = new index_packetizer_c(serial); + } catch (error_c error) { + + } + } +} + +void add_to_index(int serial, ogg_int64_t granulepos, int64_t filepos) { + int i, found = -1; + + for (i = 0; i < idx_num; i++) + if (idx_serials[i] == serial) { + found = i; + break; + } + + if (found == -1) { + fprintf(stderr, "Internal error: add_to_index for a serial without " \ + "add_index for that serial.\n"); + exit(1); + } + + idx_entries[i] = (idx_entry *)realloc(idx_entries[i], sizeof(idx_entry) * + (idx_num_entries[i] + 1)); + if (idx_entries[i] == NULL) + die("realloc"); + + idx_entries[i][idx_num_entries[i]].granulepos = granulepos; + idx_entries[i][idx_num_entries[i]].filepos = filepos; + idx_num_entries[i]++; +}*/ + +int write_packet(packet_t *pack, filelist_t *file) { +/* ogg_page *page; + off_t bytes; + + page = mpage->og; + if (verbose > 1) + fprintf(stdout, "%f (timestamp) written %spage for %s\n", + mpage->timestamp, s, (file != NULL) ? file->name : "an index"); + bytes = fwrite(page->header, 1, page->header_len, out); + if (bytes != page->header_len) { + fprintf(stderr, "Error: Output error writing to %s: %d (%s).\n", + outfile, errno, strerror(errno)); + return 1; + } + bytes = fwrite(page->body, 1, page->body_len, out); + if (bytes != page->body_len) { + fprintf(stderr, "Error: Output error writing to %s: %d (%s).\n", + outfile, errno, strerror(errno)); + return 1; + }*/ + + free(pack->data); + free(pack); + + return 0; +} + +void render_head(StdIOCallback *out) { + EbmlHead head; + + EDocType &doc_type = GetChild(head); + *static_cast(&doc_type) = "matroska"; + EDocTypeVersion &doc_type_ver = GetChild(head); + *(static_cast(&doc_type_ver)) = 1; + EDocTypeReadVersion &doc_type_read_ver = + GetChild(head); + *(static_cast(&doc_type_read_ver)) = 1; + + head.Render(static_cast(*out)); +} + +int main(int argc, char **argv) { + KaxSegment kax_segment; +// KaxTracks &kax_tracks; + filelist_t *file, *winner; + int first_pages = 1; + packet_t *pack; + int i, result; + StdIOCallback *out; + + nice(2); + + set_defaults(); + parse_args(argc, argv); + + /* open output file */ + try { + out = new StdIOCallback(outfile, MODE_CREATE); + } catch (std::exception &ex) { + fprintf(stderr, "Error: Couldn't open output file %s (%s).\n", outfile, + strerror(errno)); + exit(1); + } + try { + render_head(out); + kax_segment.Render(static_cast(*out)); + } catch (std::exception &ex) { + fprintf(stderr, "Error: Could not render the file header.\n"); + exit(1); + } + + /* let her rip! */ + while (1) { + /* Step 1: make sure an ogg page is available for each input + ** as long we havne't already processed the last one + */ + file = input; + while (file) { + if (file->status == EMOREDATA) + file->status = file->reader->read(); + file = file->next; + } + + file = input; + while (file) { + if (file->pack == NULL) + file->pack = file->reader->get_packet(); + file = file->next; + } + + /* Step 2: Pick the page with the lowest timestamp and + ** stuff it into the Matroska file + */ + file = input; + winner = file; + file = file->next; + while (file) { + if (file->pack != NULL) { + if (winner->pack == NULL) + winner = file; + else if (file->pack && + (file->pack->timestamp < winner->pack->timestamp)) + winner = file; + } + file = file->next; + } + if (winner->pack != NULL) + pack = winner->pack; + else /* exit if there are no more packets */ + break; + + /* Step 3: Write out the winning packet */ +/* if (create_index && (mpage->index_serial != -1)) + add_to_index(mpage->index_serial, ogg_page_granulepos(page), ftell(out)); + if ((result = write_ogg_page(mpage, "", file)) != 0) + exit(result);*/ + if ((result = write_packet(pack, file)) != 0) + exit(result); + + winner->pack = NULL; + + /* display some progress information */ + if (verbose == 1) + display_progress(0); + } + + if (verbose == 1) { + display_progress(1); + fprintf(stdout, "\n"); + } + + file = input; + while (file) { + filelist_t *next = file->next; + if (file->reader) + delete file->reader; + free(file); + file = next; + } + +/* if (create_index) { + for (i = 0; i < idx_num; i++) { + fprintf(stdout, "serial %d num_entries %d size %d\n", idx_serials[i], + idx_num_entries[i], idx_num_entries[i] * sizeof(idx_entry)); + idx_packetizers[i]->process(&idx_entries[i][0], idx_num_entries[i]); + while ((mpage = idx_packetizers[i]->get_page()) != NULL) + if ((result = write_ogg_page(mpage, "", NULL)) != 0) + exit(result); + delete idx_packetizers[i]; + } + }*/ + + delete out; return 0; } diff --git a/p_generic.h b/p_generic.h new file mode 100644 index 000000000..ef2383946 --- /dev/null +++ b/p_generic.h @@ -0,0 +1,37 @@ +/* + mkvmerge -- utility for splicing together matroska files + from component media subtypes + + p_generic.h + class definition for the generic packetizer + + Written by Moritz Bunkus + + Distributed under the GPL + see the file COPYING for details + or visit http://www.gnu.org/copyleft/gpl.html +*/ + +#ifndef __P_GENERIC_H__ +#define __P_GENERIC_H__ + +typedef class generic_packetizer_c { +// protected: +// int serialno; +// vorbis_comment *comments; + public: + generic_packetizer_c() {}; + virtual ~generic_packetizer_c() {}; +// virtual int page_available() = 0; +// virtual stamp_t make_timestamp(ogg_int64_t granulepos) = 0; +// virtual int serial_in_use(int serial); +// virtual int flush_pages(int header_page = 0) = 0; +// virtual int queue_pages(int header_page = 0) = 0; +// virtual stamp_t get_smallest_timestamp() = 0; +// virtual void produce_eos_packet() = 0; +// virtual void produce_header_packets() = 0; +// virtual void reset() = 0; +// virtual void set_comments(vorbis_comment *ncomments); +} generic_packetizer_c; + +#endif // __P_GENERIC_H__ diff --git a/queue.cpp b/queue.cpp new file mode 100644 index 000000000..7b0f06179 --- /dev/null +++ b/queue.cpp @@ -0,0 +1,122 @@ +/* + mkvmerge -- utility for splicing together matroska files + from component media subtypes + + queue.cpp + queueing class used by every packetizer + + Written by Moritz Bunkus + + Distributed under the GPL + see the file COPYING for details + or visit http://www.gnu.org/copyleft/gpl.html +*/ + +#include +#include +#include +#include +#include + +#include "common.h" +#include "queue.h" + +#ifdef DMALLOC +#include +#endif + +q_c::q_c() throw (error_c) : generic_packetizer_c() { + first = NULL; + current = NULL; +} + +q_c::~q_c() { + q_page_t *qpage, *tmppage; + + qpage = first; + while (qpage) { + if (qpage->pack != NULL) { + if (qpage->pack->data != NULL) + free(qpage->pack->data); + free(qpage->pack); + } + tmppage = qpage->next; + free(qpage); + qpage = tmppage; + } +} + +int q_c::add_packet(char *data, int length, u_int64_t timestamp, + int is_key) { + q_page_t *qpage; + + if (data == NULL) + return -1; + qpage = (q_page_t *)malloc(sizeof(q_page_t)); + if (qpage == NULL) + die("malloc"); + qpage->pack = (packet_t *)malloc(sizeof(packet_t)); + if (qpage->pack == NULL) + die("malloc"); + qpage->pack->data = (char *)malloc(length); + if (qpage->pack->data == NULL) + die("malloc"); + memcpy(qpage->pack->data, data, length); + qpage->pack->length = length; + qpage->pack->timestamp = timestamp; + qpage->pack->is_key = is_key; + qpage->next = NULL; + if (current != NULL) + current->next = qpage; + if (first == NULL) + first = qpage; + current = qpage; + + return 0; +} + +packet_t *q_c::get_packet() { + packet_t *pack; + q_page_t *qpage; + + if (first && first->pack) { + pack = first->pack; + qpage = first->next; + if (qpage == NULL) + current = NULL; + free(first); + first = qpage; + return pack; + } + + return NULL; +} + +int q_c::packet_available() { + if ((first == NULL) || (first->pack == NULL)) + return 0; + else + return 1; +} + +stamp_t q_c::get_smallest_timestamp() { + if (first != NULL) + return first->pack->timestamp; + else + return MAX_TIMESTAMP; +} + +long q_c::get_queued_bytes() { + long bytes; + q_page_t *cur; + + bytes = 0; + cur = first; + while (cur != NULL) { + if (cur->pack != NULL) + bytes += cur->pack->length; + cur = cur->next; + } + + return bytes; +} diff --git a/queue.h b/queue.h new file mode 100644 index 000000000..8467931c3 --- /dev/null +++ b/queue.h @@ -0,0 +1,46 @@ +/* + mkvmerge -- utility for splicing together matroska files + from component media subtypes + + queue.cpp + class definitions for the queueing class used by every packetizer + + Written by Moritz Bunkus + + Distributed under the GPL + see the file COPYING for details + or visit http://www.gnu.org/copyleft/gpl.html +*/ + +#ifndef __QUEUE_H__ +#define __QUEUE_H__ + +#include "error.h" +#include "r_generic.h" +#include "p_generic.h" + +// q_page_t is used internally only +typedef struct q_page { + packet_t *pack; + struct q_page *next; +} q_page_t; + +class q_c: public generic_packetizer_c { + private: + struct q_page *first; + struct q_page *current; + + public: + q_c() throw (error_c); + virtual ~q_c(); + + virtual int add_packet(char *data, int lenth, + u_int64_t timestamp, int is_key); + virtual packet_t *get_packet(); + virtual int packet_available(); + virtual stamp_t get_smallest_timestamp(); + + virtual long get_queued_bytes(); +}; + +#endif /* __QUEUE_H__ */ diff --git a/r_avi.h b/r_avi.h index e3b002877..beb23efa8 100644 --- a/r_avi.h +++ b/r_avi.h @@ -22,18 +22,19 @@ extern "C" { } #include "r_generic.h" +#include "p_generic.h" +#include "common.h" #define RAVI_UNKNOWN 0 #define RAVI_DIVX3 1 #define RAVI_MPEG4 2 typedef struct avi_demuxer_t { -// generic_packetizer_c *packetizer; + generic_packetizer_c *packetizer; int channels, bits_per_sample, samples_per_second; int aid; int eos; u_int64_t bytes_processed; -// ogg_int64_t bytes_processed; avi_demuxer_t *next; } avi_demuxer_t; @@ -49,8 +50,8 @@ class avi_reader_c: public generic_reader_c { char **comments; int max_frame_size; int act_wchar; -// audio_sync_t async; -// range_t range; + audio_sync_t async; + range_t range; char *old_chunk; int old_key, old_nread; int video_done, maxframes; @@ -64,23 +65,18 @@ class avi_reader_c: public generic_reader_c { avi_reader_c(char *fname) throw (error_c); virtual ~avi_reader_c(); - virtual int read(); -// virtual int serial_in_use(int); -// virtual ogmmerge_page_t *get_page(); -// virtual ogmmerge_page_t *get_header_page(int header_type = -// PACKET_TYPE_HEADER); - -// virtual void reset(); - virtual int display_priority(); - virtual void display_progress(); + virtual int read(); + virtual packet_t *get_packet(); + virtual int display_priority(); + virtual void display_progress(); - static int probe_file(FILE *file, u_int64_t size); + static int probe_file(FILE *file, u_int64_t size); private: - virtual int add_audio_demuxer(avi_t *avi, int aid); - virtual int is_keyframe(unsigned char *data, long size, - int suggestion); + virtual int add_audio_demuxer(avi_t *avi, int aid); + virtual int is_keyframe(unsigned char *data, long size, + int suggestion); }; #endif /* __R_AVI_H__*/ diff --git a/r_generic.h b/r_generic.h index 0679c433e..da7435fb6 100644 --- a/r_generic.h +++ b/r_generic.h @@ -11,36 +11,18 @@ see the file COPYING for details or visit http://www.gnu.org/copyleft/gpl.html */ + #ifndef __R_GENERIC_H__ #define __R_GENERIC_H__ -#include #include -#include - -#define DISPLAYPRIORITY_HIGH 10 -#define DISPLAYPRIORITY_MEDIUM 5 -#define DISPLAYPRIORITY_LOW 1 typedef struct { - int displacement; - double linear; -} audio_sync_t; - -typedef struct { - double start; - double end; -} range_t; - -typedef double stamp_t; -#define MAX_TIMESTAMP ((double)3.40282347e+38F) - -typedef struct { -// ogg_page *og; - stamp_t timestamp; - int header_page; - int index_serial; -} ogmmerge_page_t; + char *data; + int length; + u_int64_t timestamp; + int is_key; +} packet_t; typedef class generic_reader_c { // protected: @@ -49,45 +31,10 @@ typedef class generic_reader_c { generic_reader_c() {}; virtual ~generic_reader_c() {}; virtual int read() = 0; -/* virtual int serial_in_use(int serial) = 0; - virtual ogmmerge_page_t *get_page() = 0; - virtual ogmmerge_page_t *get_header_page(int header_type = - PACKET_TYPE_HEADER) = 0; - virtual void reset() = 0;*/ + virtual packet_t *get_packet() = 0; virtual int display_priority() = 0; virtual void display_progress() = 0; // virtual void set_chapter_info(vorbis_comment *info); } generic_reader_c; -/*typedef class generic_packetizer_c { - protected: - int serialno; - vorbis_comment *comments; - public: - generic_packetizer_c(); - virtual ~generic_packetizer_c() {}; - virtual int page_available() = 0; - virtual ogmmerge_page_t *get_page() = 0; - virtual ogmmerge_page_t *get_header_page(int header_type = - PACKET_TYPE_HEADER) = 0; - virtual stamp_t make_timestamp(ogg_int64_t granulepos) = 0; - virtual int serial_in_use(int serial); - virtual int flush_pages(int header_page = 0) = 0; - virtual int queue_pages(int header_page = 0) = 0; - virtual stamp_t get_smallest_timestamp() = 0; - virtual void produce_eos_packet() = 0; - virtual void produce_header_packets() = 0; - virtual void reset() = 0; - virtual void set_comments(vorbis_comment *ncomments); -} generic_packetizer_c;*/ - -class error_c { - private: - char *error; - public: - error_c(char *nerror) { error = strdup(nerror); }; - ~error_c() { free(error); }; - char *get_error() { return error; }; -}; - #endif /* __R_GENERIC_H__ */