mkvtoolnix/r_avi.cpp

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