mirror of
https://gitlab.com/mbunkus/mkvtoolnix.git
synced 2024-12-25 12:27:21 +00:00
522 lines
14 KiB
C++
522 lines
14 KiB
C++
/*
|
|
mkvmerge -- utility for splicing together matroska files
|
|
from component media subtypes
|
|
|
|
r_avi.h
|
|
|
|
Written by Moritz Bunkus <moritz@bunkus.org>
|
|
|
|
Distributed under the GPL
|
|
see the file COPYING for details
|
|
or visit http://www.gnu.org/copyleft/gpl.html
|
|
*/
|
|
|
|
/*!
|
|
\file
|
|
\version \$Id: r_avi.cpp,v 1.36 2003/06/12 23:05:49 mosu Exp $
|
|
\brief AVI demultiplexer module
|
|
\author Moritz Bunkus <moritz@bunkus.org>
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
extern "C" {
|
|
#include <avilib.h>
|
|
}
|
|
|
|
#include "mkvmerge.h"
|
|
#include "common.h"
|
|
#include "error.h"
|
|
#include "r_avi.h"
|
|
#include "p_video.h"
|
|
#include "p_pcm.h"
|
|
#include "p_mp3.h"
|
|
#include "p_ac3.h"
|
|
|
|
int avi_reader_c::probe_file(mm_io_c *mm_io, int64_t size) {
|
|
unsigned char data[12];
|
|
|
|
if (size < 12)
|
|
return 0;
|
|
try {
|
|
mm_io->setFilePointer(0, seek_beginning);
|
|
if (mm_io->read(data, 12) != 12)
|
|
return 0;
|
|
mm_io->setFilePointer(0, seek_beginning);
|
|
} catch (exception &ex) {
|
|
return 0;
|
|
}
|
|
if(strncasecmp((char *)data, "RIFF", 4) ||
|
|
strncasecmp((char *)data+8, "AVI ", 4))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* allocates and initializes local storage for a particular
|
|
* substream conversion.
|
|
*/
|
|
avi_reader_c::avi_reader_c(track_info_t *nti) throw (error_c):
|
|
generic_reader_c(nti) {
|
|
int fsize, i, extract_video = 1;
|
|
int64_t size;
|
|
mm_io_c *mm_io;
|
|
avi_demuxer_t *demuxer;
|
|
char *codec;
|
|
|
|
try {
|
|
mm_io = new mm_io_c(ti->fname, MODE_READ);
|
|
mm_io->setFilePointer(0, seek_end);
|
|
size = mm_io->getFilePointer();
|
|
mm_io->setFilePointer(0, seek_beginning);
|
|
if (!avi_reader_c::probe_file(mm_io, size))
|
|
throw error_c("avi_reader: Source is not a valid AVI file.");
|
|
delete mm_io;
|
|
} catch (exception &ex) {
|
|
throw error_c("avi_reader: Could not read the source file.");
|
|
}
|
|
|
|
if (verbose)
|
|
fprintf(stdout, "Using AVI demultiplexer for %s. Opening file. This "
|
|
"may take some time depending on the file's size.\n", ti->fname);
|
|
rederive_keyframes = 0;
|
|
if ((avi = AVI_open_input_file(ti->fname, 1)) == NULL) {
|
|
const char *msg = "avi_reader: Could not initialize AVI source. Reason: ";
|
|
char *s, *error;
|
|
error = AVI_strerror();
|
|
s = (char *)safemalloc(strlen(msg) + strlen(error) + 1);
|
|
sprintf(s, "%s%s", msg, error);
|
|
throw error_c(s);
|
|
}
|
|
|
|
fps = AVI_frame_rate(avi);
|
|
if (video_fps < 0)
|
|
video_fps = fps;
|
|
frames = 0;
|
|
fsize = 0;
|
|
maxframes = AVI_video_frames(avi);
|
|
for (i = 0; i < maxframes; i++)
|
|
if (AVI_frame_size(avi, i) > fsize)
|
|
fsize = AVI_frame_size(avi, i);
|
|
max_frame_size = fsize;
|
|
|
|
if (ti->vtracks != NULL) {
|
|
extract_video = 0;
|
|
for (i = 0; i < strlen((char *)ti->vtracks); i++) {
|
|
if (ti->vtracks[i] > 0)
|
|
fprintf(stderr, "Warning: avi_reader: only one video stream per AVI " \
|
|
"is supported. Will ignore -d %d.\n", ti->vtracks[i]);
|
|
else if (ti->vtracks[i] == 1)
|
|
extract_video = 1;
|
|
}
|
|
}
|
|
if (extract_video) {
|
|
codec = AVI_video_compressor(avi);
|
|
if (!strcasecmp(codec, "DIV3") ||
|
|
!strcasecmp(codec, "AP41") || // Angel Potion
|
|
!strcasecmp(codec, "MPG3") ||
|
|
!strcasecmp(codec, "MP43"))
|
|
is_divx = RAVI_DIVX3;
|
|
else if (!strcasecmp(codec, "MP42") ||
|
|
!strcasecmp(codec, "DIV2") ||
|
|
!strcasecmp(codec, "DIVX") ||
|
|
!strcasecmp(codec, "XVID") ||
|
|
!strcasecmp(codec, "DX50"))
|
|
is_divx = RAVI_MPEG4;
|
|
else
|
|
is_divx = 0;
|
|
|
|
ti->private_data = (unsigned char *)avi->bitmap_info_header;
|
|
if (ti->private_data != NULL)
|
|
ti->private_size = avi->bitmap_info_header->bi_size;
|
|
if (ti->fourcc[0] == 0) {
|
|
memcpy(ti->fourcc, codec, 4);
|
|
ti->fourcc[4] = 0;
|
|
} else
|
|
memcpy(&avi->bitmap_info_header->bi_compression, ti->fourcc, 4);
|
|
vpacketizer = new video_packetizer_c(this, AVI_frame_rate(avi),
|
|
AVI_video_width(avi),
|
|
AVI_video_height(avi),
|
|
24, // fixme!
|
|
1, ti);
|
|
if (verbose)
|
|
fprintf(stdout, "+-> Using video output module for video stream.\n");
|
|
} else
|
|
vpacketizer = NULL;
|
|
|
|
ademuxers = NULL;
|
|
if (ti->atracks != NULL) { // use only specific audio tracks or none at all
|
|
for (i = 0; i < strlen((char *)ti->atracks); i++) {
|
|
if (ti->atracks[i] >= AVI_audio_tracks(avi))
|
|
fprintf(stderr, "Warning: avi_reader: the AVI does not contain an " \
|
|
"audio stream with the id %d. Number of audio tracks: %d\n",
|
|
ti->atracks[i], AVI_audio_tracks(avi));
|
|
else {
|
|
int already_extracting = 0;
|
|
avi_demuxer_t *demuxer = ademuxers;
|
|
|
|
while (demuxer) {
|
|
if (demuxer->aid == ti->atracks[i]) {
|
|
already_extracting = 1;
|
|
break;
|
|
}
|
|
demuxer = demuxer->next;
|
|
}
|
|
if (already_extracting)
|
|
fprintf(stderr, "Warning: avi_reader: already extracting audio " \
|
|
"stream number %d. Will only do this once.\n",
|
|
ti->atracks[i]);
|
|
else
|
|
add_audio_demuxer(avi, ti->atracks[i]);
|
|
}
|
|
}
|
|
} else // use all audio tracks (no parameter specified)
|
|
for (i = 0; i < AVI_audio_tracks(avi); i++)
|
|
add_audio_demuxer(avi, i);
|
|
|
|
demuxer = ademuxers;
|
|
while (demuxer != NULL) {
|
|
long bps = demuxer->samples_per_second * demuxer->channels *
|
|
demuxer->bits_per_sample / 8;
|
|
if (bps > fsize)
|
|
fsize = bps;
|
|
demuxer = demuxer->next;
|
|
}
|
|
max_frame_size = fsize;
|
|
chunk = (unsigned char *)safemalloc(fsize < 16384 ? 16384 : fsize);
|
|
act_wchar = 0;
|
|
old_key = 0;
|
|
old_chunk = NULL;
|
|
video_done = 0;
|
|
}
|
|
|
|
avi_reader_c::~avi_reader_c() {
|
|
struct avi_demuxer_t *demuxer, *tmp;
|
|
|
|
if (avi != NULL)
|
|
AVI_close(avi);
|
|
if (chunk != NULL)
|
|
safefree(chunk);
|
|
if (vpacketizer != NULL)
|
|
delete vpacketizer;
|
|
|
|
demuxer = ademuxers;
|
|
while (demuxer) {
|
|
if (demuxer->packetizer != NULL)
|
|
delete demuxer->packetizer;
|
|
tmp = demuxer->next;
|
|
safefree(demuxer);
|
|
demuxer = tmp;
|
|
}
|
|
|
|
if (old_chunk != NULL)
|
|
safefree(old_chunk);
|
|
|
|
ti->private_data = NULL;
|
|
}
|
|
|
|
int avi_reader_c::add_audio_demuxer(avi_t *avi, int aid) {
|
|
avi_demuxer_t *demuxer, *append_to;
|
|
WAVEFORMATEX *wfe;
|
|
|
|
append_to = ademuxers;
|
|
while ((append_to != NULL) && (append_to->next != NULL))
|
|
append_to = append_to->next;
|
|
AVI_set_audio_track(avi, aid);
|
|
demuxer = (avi_demuxer_t *)safemalloc(sizeof(avi_demuxer_t));
|
|
memset(demuxer, 0, sizeof(avi_demuxer_t));
|
|
demuxer->aid = aid;
|
|
wfe = avi->wave_format_ex[aid];
|
|
ti->private_data = (unsigned char *)wfe;
|
|
if (wfe != NULL)
|
|
ti->private_size = wfe->cb_size + sizeof(WAVEFORMATEX);
|
|
else
|
|
ti->private_size = 0;
|
|
switch (AVI_audio_format(avi)) {
|
|
case 0x0001: // raw PCM audio
|
|
if (verbose)
|
|
fprintf(stdout, "+-> Using PCM output module for audio stream %d.\n",
|
|
aid);
|
|
demuxer->samples_per_second = AVI_audio_rate(avi);
|
|
demuxer->channels = AVI_audio_channels(avi);
|
|
demuxer->bits_per_sample = AVI_audio_bits(avi);
|
|
demuxer->packetizer = new pcm_packetizer_c(this,
|
|
demuxer->samples_per_second,
|
|
demuxer->channels,
|
|
demuxer->bits_per_sample, ti);
|
|
break;
|
|
case 0x0055: // MP3
|
|
if (verbose)
|
|
fprintf(stdout, "+-> Using MP3 output module for audio stream %d.\n",
|
|
aid);
|
|
demuxer->samples_per_second = AVI_audio_rate(avi);
|
|
demuxer->channels = AVI_audio_channels(avi);
|
|
demuxer->bits_per_sample = AVI_audio_mp3rate(avi);
|
|
demuxer->packetizer = new mp3_packetizer_c(this,
|
|
demuxer->samples_per_second,
|
|
demuxer->channels, ti);
|
|
break;
|
|
case 0x2000: // AC3
|
|
if (verbose)
|
|
fprintf(stdout, "+-> Using AC3 output module for audio stream %d.\n",
|
|
aid);
|
|
demuxer->samples_per_second = AVI_audio_rate(avi);
|
|
demuxer->channels = AVI_audio_channels(avi);
|
|
demuxer->bits_per_sample = AVI_audio_mp3rate(avi);
|
|
demuxer->packetizer = new ac3_packetizer_c(this,
|
|
demuxer->samples_per_second,
|
|
demuxer->channels, ti);
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Error: Unknown audio format 0x%04x for audio stream " \
|
|
"%d.\n", AVI_audio_format(avi), aid);
|
|
return -1;
|
|
}
|
|
|
|
if (append_to == NULL)
|
|
ademuxers = demuxer;
|
|
else
|
|
append_to->next = demuxer;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int avi_reader_c::is_keyframe(unsigned char *data, long size, int suggestion) {
|
|
int i;
|
|
|
|
if (!rederive_keyframes)
|
|
return suggestion;
|
|
|
|
switch (is_divx) {
|
|
case RAVI_DIVX3:
|
|
i = *((int *)data);
|
|
return ((i & 0x40000000) ? 0 : 1);
|
|
case RAVI_MPEG4:
|
|
for (i = 0; i < size - 5; i++) {
|
|
if ((data[i] == 0x00) && (data[i + 1] == 0x00) &&
|
|
(data[i + 2] == 0x01) && (data[i + 3] == 0xb6)) {
|
|
if ((data[i + 4] & 0xc0) == 0x00)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
default:
|
|
return suggestion;
|
|
}
|
|
}
|
|
|
|
int avi_reader_c::read() {
|
|
int nread, key, last_frame;
|
|
avi_demuxer_t *demuxer;
|
|
int need_more_data;
|
|
int done, frames_read, size;
|
|
|
|
debug_enter("avi_reader_c::read (video)");
|
|
|
|
need_more_data = 0;
|
|
if ((vpacketizer != NULL) && !video_done) {
|
|
last_frame = 0;
|
|
done = 0;
|
|
|
|
// Make sure we have a frame to work with.
|
|
if (old_chunk == NULL) {
|
|
debug_enter("AVI_read_frame");
|
|
nread = AVI_read_frame(avi, (char *)chunk, &key);
|
|
debug_leave("AVI_read_frame");
|
|
if (nread < 0) {
|
|
frames = maxframes + 1;
|
|
done = 1;
|
|
} else {
|
|
key = is_keyframe(chunk, nread, key);
|
|
old_chunk = (unsigned char *)safememdup(chunk, nread);
|
|
old_key = key;
|
|
old_nread = nread;
|
|
frames++;
|
|
}
|
|
}
|
|
if (!done) {
|
|
frames_read = 1;
|
|
done = 0;
|
|
// Check whether we have identical frames
|
|
while (!done && (frames <= (maxframes - 1))) {
|
|
debug_enter("AVI_read_frame");
|
|
nread = AVI_read_frame(avi, (char *)chunk, &key);
|
|
debug_leave("AVI_read_frame");
|
|
if (nread < 0) {
|
|
vpacketizer->process(old_chunk, old_nread, -1, -1,
|
|
old_key ? -1 : 0);
|
|
frames = maxframes + 1;
|
|
break;
|
|
}
|
|
key = is_keyframe(chunk, nread, key);
|
|
if (frames == (maxframes - 1)) {
|
|
last_frame = 1;
|
|
done = 1;
|
|
}
|
|
if (nread == 0)
|
|
frames_read++;
|
|
else if (nread > 0)
|
|
done = 1;
|
|
frames++;
|
|
}
|
|
if (nread > 0) {
|
|
vpacketizer->process(old_chunk, old_nread, -1, -1,
|
|
old_key ? -1 : 0);
|
|
if (! last_frame) {
|
|
if (old_chunk != NULL)
|
|
safefree(old_chunk);
|
|
if (nread == 0)
|
|
fprintf(stdout, "hmm\n");
|
|
old_chunk = (unsigned char *)safememdup(chunk, nread);
|
|
old_key = key;
|
|
old_nread = nread;
|
|
} else if (nread > 0)
|
|
vpacketizer->process(chunk, nread, -1, -1,
|
|
key ? -1 : 0);
|
|
}
|
|
}
|
|
|
|
if (last_frame) {
|
|
frames = maxframes + 1;
|
|
video_done = 1;
|
|
} else if (frames != (maxframes + 1))
|
|
need_more_data = 1;
|
|
}
|
|
|
|
debug_leave("avi_reader_c::read (video)");
|
|
debug_enter("avi_reader_c::read (audio)");
|
|
|
|
demuxer = ademuxers;
|
|
while (demuxer != NULL) {
|
|
if (demuxer->packetizer->packet_available() >= 2) {
|
|
demuxer = demuxer->next;
|
|
continue;
|
|
}
|
|
|
|
AVI_set_audio_track(avi, demuxer->aid);
|
|
if (AVI_audio_format(avi) == 0x0001)
|
|
size = demuxer->channels * demuxer->bits_per_sample *
|
|
demuxer->samples_per_second / 8;
|
|
else
|
|
size = 16384;
|
|
|
|
debug_enter("AVI_read_audio");
|
|
nread = AVI_read_audio(avi, (char *)chunk, size);
|
|
debug_leave("AVI_read_audio");
|
|
if (nread > 0) {
|
|
if (nread < size)
|
|
demuxer->eos = 1;
|
|
else
|
|
demuxer->eos = 0;
|
|
demuxer->packetizer->process(chunk, nread);
|
|
}
|
|
if (!demuxer->eos)
|
|
need_more_data = 1;
|
|
demuxer = demuxer->next;
|
|
}
|
|
|
|
debug_leave("avi_reader_c::read (audio)");
|
|
|
|
if (need_more_data)
|
|
return EMOREDATA;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
packet_t *avi_reader_c::get_packet() {
|
|
generic_packetizer_c *winner;
|
|
avi_demuxer_t *demuxer;
|
|
|
|
winner = NULL;
|
|
|
|
if ((vpacketizer != NULL) && (vpacketizer->packet_available()))
|
|
winner = vpacketizer;
|
|
|
|
demuxer = ademuxers;
|
|
while (demuxer != NULL) {
|
|
if (winner == NULL) {
|
|
if (demuxer->packetizer->packet_available())
|
|
winner = demuxer->packetizer;
|
|
} else if (winner->packet_available() &&
|
|
(winner->get_smallest_timecode() >
|
|
demuxer->packetizer->get_smallest_timecode()))
|
|
winner = demuxer->packetizer;
|
|
demuxer = demuxer->next;
|
|
}
|
|
|
|
if (winner != NULL)
|
|
return winner->get_packet();
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
int avi_reader_c::display_priority() {
|
|
if (vpacketizer != NULL)
|
|
return DISPLAYPRIORITY_HIGH;
|
|
else
|
|
return DISPLAYPRIORITY_LOW;
|
|
}
|
|
|
|
static char wchar[] = "-\\|/-\\|/-";
|
|
|
|
void avi_reader_c::display_progress() {
|
|
if (vpacketizer != NULL) {
|
|
int myframes = frames;
|
|
if (frames == (maxframes + 1))
|
|
myframes--;
|
|
fprintf(stdout, "progress: %d/%ld frames (%ld%%)\r",
|
|
myframes, AVI_video_frames(avi),
|
|
myframes * 100 / AVI_video_frames(avi));
|
|
} else {
|
|
fprintf(stdout, "working... %c\r", wchar[act_wchar]);
|
|
act_wchar++;
|
|
if (act_wchar == strlen(wchar))
|
|
act_wchar = 0;
|
|
}
|
|
fflush(stdout);
|
|
}
|
|
|
|
void avi_reader_c::set_headers() {
|
|
avi_demuxer_t *demuxer;
|
|
|
|
if (vpacketizer != NULL)
|
|
vpacketizer->set_headers();
|
|
|
|
demuxer = ademuxers;
|
|
while (demuxer != NULL) {
|
|
demuxer->packetizer->set_headers();
|
|
demuxer = demuxer->next;
|
|
}
|
|
}
|
|
|
|
void avi_reader_c::identify() {
|
|
int i;
|
|
const char *type;
|
|
|
|
fprintf(stdout, "File '%s': container: AVI\nTrack ID 0: video (%s)\n",
|
|
ti->fname, AVI_video_compressor(avi));
|
|
for (i = 0; i < AVI_audio_tracks(avi); i++) {
|
|
AVI_set_audio_track(avi, i);
|
|
switch (AVI_audio_format(avi)) {
|
|
case 0x0001:
|
|
type = "PCM";
|
|
break;
|
|
case 0x0055:
|
|
type = "MP3";
|
|
break;
|
|
case 0x2000:
|
|
type = "AC3";
|
|
break;
|
|
default:
|
|
type = "unknown";
|
|
}
|
|
fprintf(stdout, "Track ID %d: audio (%s)\n", i + 1, type);
|
|
}
|
|
}
|