566 lines
13 KiB
C++
566 lines
13 KiB
C++
#include "demuxer.h"
|
|
#include "read.h"
|
|
#include "avi_reader.h"
|
|
|
|
static int GetStreamNumber(uint32_t id)
|
|
{
|
|
char *stream_data = (char *)(&id);
|
|
if (!isxdigit(stream_data[0]) || !isxdigit(stream_data[1]))
|
|
return -1;
|
|
|
|
stream_data[2] = 0;
|
|
int stream_number = strtoul(stream_data, 0, 16);
|
|
return stream_number;
|
|
}
|
|
|
|
nsavi::Demuxer::Demuxer(nsavi::avi_reader *_reader) : ParserBase(_reader)
|
|
{
|
|
movie_found = NOT_READ;
|
|
idx1_found = NOT_READ;
|
|
info_found = NOT_READ;
|
|
movie_start = 0;
|
|
index = 0;
|
|
info = 0;
|
|
}
|
|
|
|
// reads a chunk and updates parse state variable on error
|
|
static int ReadChunk(nsavi::avi_reader *reader, nsavi::riff_chunk *chunk, nsavi::ParseState &state, uint32_t *bytes_read)
|
|
{
|
|
int ret = nsavi::read_riff_chunk(reader, chunk, bytes_read);
|
|
if (ret == nsavi::READ_EOF)
|
|
{
|
|
state = nsavi::NOT_FOUND;
|
|
return nsavi::READ_NOT_FOUND;
|
|
}
|
|
else if (ret > nsavi::READ_OK)
|
|
{
|
|
state = nsavi::PARSE_ERROR;
|
|
return ret;
|
|
}
|
|
else if (ret < nsavi::READ_OK)
|
|
{ // pass-thru return value from avi_reader
|
|
state = nsavi::PARSE_RESYNC;
|
|
return ret;
|
|
}
|
|
|
|
return nsavi::READ_OK;
|
|
}
|
|
|
|
// skips a chunk and updates a parser state variable on error
|
|
static int SkipChunk(nsavi::avi_reader *reader, const nsavi::riff_chunk *chunk, nsavi::ParseState &state, uint32_t *bytes_read)
|
|
{
|
|
int ret = nsavi::skip_chunk(reader, chunk, bytes_read);
|
|
if (ret == nsavi::READ_EOF)
|
|
{
|
|
state = nsavi::NOT_FOUND;
|
|
return nsavi::READ_NOT_FOUND;
|
|
}
|
|
else if (ret > nsavi::READ_OK)
|
|
{
|
|
state = nsavi::PARSE_ERROR;
|
|
return ret;
|
|
}
|
|
else if (ret < nsavi::READ_OK)
|
|
{ // pass-thru return value from avi_reader
|
|
state = nsavi::PARSE_RESYNC;
|
|
return ret;
|
|
}
|
|
|
|
return nsavi::READ_OK;
|
|
}
|
|
|
|
static int Read(nsavi::avi_reader *reader, void *buffer, uint32_t size, nsavi::ParseState &state, uint32_t *out_bytes_read)
|
|
{
|
|
uint32_t bytes_read;
|
|
int ret = reader->Read(buffer, size, &bytes_read);
|
|
if (ret > nsavi::READ_OK)
|
|
{
|
|
state = nsavi::PARSE_ERROR;
|
|
return ret;
|
|
}
|
|
else if (ret < nsavi::READ_OK)
|
|
{ // pass-thru return value from avi_reader
|
|
state = nsavi::PARSE_RESYNC;
|
|
return ret;
|
|
}
|
|
else if (bytes_read != size)
|
|
{
|
|
state = nsavi::PARSE_ERROR;
|
|
return nsavi::READ_EOF;
|
|
}
|
|
*out_bytes_read = bytes_read;
|
|
return nsavi::READ_OK;
|
|
}
|
|
|
|
int nsavi::Demuxer::GetHeaderList(HeaderList *header_list)
|
|
{
|
|
if (riff_parsed != PARSED)
|
|
return READ_INVALID_CALL;
|
|
|
|
if (riff_parsed == PARSE_RESYNC)
|
|
reader->Seek(riff_start);
|
|
|
|
if (header_list_parsed == NOT_READ)
|
|
{
|
|
// first, see how far we are into the file to properly bound our reads
|
|
uint64_t start = reader->Tell();
|
|
uint32_t bytes_available = riff_header.size;
|
|
bytes_available -= (uint32_t)(start - riff_start);
|
|
|
|
while (bytes_available)
|
|
{
|
|
if (bytes_available < 8)
|
|
{
|
|
header_list_parsed = NOT_FOUND;
|
|
return READ_NOT_FOUND;
|
|
}
|
|
uint32_t bytes_read;
|
|
riff_chunk chunk;
|
|
int ret = ReadChunk(reader, &chunk, header_list_parsed, &bytes_read);
|
|
if (ret)
|
|
return ret;
|
|
|
|
bytes_available -= bytes_read;
|
|
if (bytes_available < chunk.size)
|
|
{
|
|
header_list_parsed = PARSE_ERROR;
|
|
return READ_INVALID_DATA;
|
|
}
|
|
switch(chunk.id)
|
|
{
|
|
case 'TSIL': // list chunk
|
|
switch(chunk.type)
|
|
{
|
|
case 'lrdh': // this is what we're looking for
|
|
ret = ParseHeaderList(chunk.size, &bytes_read);
|
|
if (ret == READ_OK)
|
|
{
|
|
header_list->avi_header = avi_header;
|
|
header_list->stream_list = stream_list;
|
|
header_list->stream_list_size = stream_list_size;
|
|
header_list->odml_header = odml_header;
|
|
}
|
|
return ret;
|
|
case 'OFNI': // INFO
|
|
if (!info)
|
|
{
|
|
info = new nsavi::Info();
|
|
if (!info)
|
|
{
|
|
header_list_parsed = PARSE_ERROR;
|
|
return READ_OUT_OF_MEMORY;
|
|
}
|
|
ret = info->Read(reader, chunk.size);
|
|
if (ret)
|
|
{
|
|
header_list_parsed = PARSE_ERROR;
|
|
return ret;
|
|
}
|
|
break;
|
|
}
|
|
// fall through
|
|
default: // skip anything we don't understand
|
|
ret = SkipChunk(reader, &chunk, header_list_parsed, &bytes_read);
|
|
if (ret)
|
|
return ret;
|
|
bytes_available -= bytes_read;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
default: // skip anything we don't understand
|
|
case 'KNUJ': // skip junk chunks
|
|
ret = SkipChunk(reader, &chunk, header_list_parsed, &bytes_read);
|
|
if (ret)
|
|
return ret;
|
|
bytes_available -= bytes_read;
|
|
break;
|
|
// TODO; case '1xdi': break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (header_list_parsed == PARSED)
|
|
{
|
|
header_list->avi_header = avi_header;
|
|
header_list->stream_list = stream_list;
|
|
header_list->stream_list_size = stream_list_size;
|
|
header_list->odml_header = odml_header;
|
|
return READ_OK;
|
|
}
|
|
|
|
return READ_INVALID_CALL;
|
|
}
|
|
|
|
int nsavi::Demuxer::FindMovieChunk()
|
|
{
|
|
if (riff_parsed != PARSED)
|
|
return READ_INVALID_CALL;
|
|
|
|
if (header_list_parsed != READ_OK)
|
|
return READ_INVALID_CALL;
|
|
|
|
if (movie_found == PARSED)
|
|
return READ_OK;
|
|
|
|
if (movie_found == NOT_READ)
|
|
{
|
|
// first, see how far we are into the file to properly bound our reads
|
|
uint64_t start = reader->Tell();
|
|
uint32_t bytes_available = riff_header.size;
|
|
bytes_available -= (uint32_t)(start - riff_start);
|
|
while (movie_found == NOT_READ)
|
|
{
|
|
if (bytes_available < 8)
|
|
{
|
|
header_list_parsed = NOT_FOUND;
|
|
return READ_NOT_FOUND;
|
|
}
|
|
uint32_t bytes_read;
|
|
int ret = ReadChunk(reader, &movi_header, movie_found, &bytes_read);
|
|
if (ret)
|
|
return ret;
|
|
|
|
bytes_available -= bytes_read;
|
|
if (bytes_available < movi_header.size)
|
|
{
|
|
movie_found = PARSE_ERROR;
|
|
return READ_INVALID_DATA;
|
|
}
|
|
switch(movi_header.id)
|
|
{
|
|
// TODO: parse any other interesting chunks along the way
|
|
case 'TSIL': // list chunk
|
|
switch(movi_header.type)
|
|
{
|
|
case 'ivom':
|
|
{
|
|
movie_found = PARSED;
|
|
movie_start = reader->Tell();
|
|
return READ_OK;
|
|
}
|
|
break;
|
|
case '1xdi': // index v1 chunk
|
|
if (!index)
|
|
{
|
|
index = (nsavi::IDX1 *)malloc(idx1_header.size + sizeof(uint32_t));
|
|
if (index)
|
|
{
|
|
ret = Read(reader, ((uint8_t *)index) + sizeof(uint32_t), idx1_header.size, idx1_found, &bytes_read);
|
|
if (ret)
|
|
return ret;
|
|
|
|
bytes_available-=bytes_read;
|
|
index->index_count = idx1_header.size / sizeof(IDX1_INDEX);
|
|
if ((idx1_header.size & 1) && bytes_available)
|
|
{
|
|
bytes_available--;
|
|
reader->Skip(1);
|
|
}
|
|
idx1_found = PARSED;
|
|
}
|
|
else
|
|
{
|
|
return READ_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ret = SkipChunk(reader, &movi_header, movie_found, &bytes_read);
|
|
if (ret)
|
|
return ret;
|
|
bytes_available -= bytes_read;
|
|
}
|
|
break;
|
|
case 'OFNI': // INFO
|
|
if (!info)
|
|
{
|
|
info = new nsavi::Info();
|
|
if (!info)
|
|
{
|
|
movie_found = PARSE_ERROR;
|
|
return READ_OUT_OF_MEMORY;
|
|
}
|
|
|
|
ret = info->Read(reader, movi_header.size);
|
|
if (ret)
|
|
{
|
|
movie_found = PARSE_ERROR;
|
|
return ret;
|
|
}
|
|
break;
|
|
}
|
|
// fall through
|
|
default: // skip anything we don't understand
|
|
ret = SkipChunk(reader, &movi_header, movie_found, &bytes_read);
|
|
if (ret)
|
|
return ret;
|
|
bytes_available -= bytes_read;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default: // skip anything we don't understand
|
|
case 'KNUJ': // skip junk chunks
|
|
ret = SkipChunk(reader, &movi_header, movie_found, &bytes_read);
|
|
if (ret)
|
|
return ret;
|
|
bytes_available -= bytes_read;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return nsavi::READ_NOT_FOUND; // TODO: not sure about this
|
|
}
|
|
int nsavi::Demuxer::SeekToMovieChunk(nsavi::avi_reader *reader)
|
|
{
|
|
return reader->Seek(movie_start);
|
|
}
|
|
|
|
int nsavi::Demuxer::GetNextMovieChunk(nsavi::avi_reader *reader, void **data, uint32_t *chunk_size, uint32_t *chunk_type, int limit_stream_num)
|
|
{
|
|
ParseState no_state;
|
|
if (movie_found == PARSED)
|
|
{
|
|
uint64_t start = reader->Tell();
|
|
uint32_t bytes_available = movi_header.size;
|
|
bytes_available -= (uint32_t)(start - movie_start);
|
|
|
|
uint32_t bytes_read;
|
|
riff_chunk chunk;
|
|
again:
|
|
int ret = ReadChunk(reader, &chunk, no_state, &bytes_read);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (chunk.id == 'TSIL' || chunk.id == 'FFIR')
|
|
{
|
|
goto again; // skip 'rec' chunk headers
|
|
}
|
|
if (chunk.id == 'KNUJ' || chunk.id == '1xdi')
|
|
{
|
|
SkipChunk(reader, &chunk, no_state, &bytes_read);
|
|
goto again;
|
|
|
|
}
|
|
if (limit_stream_num != 65536)
|
|
{
|
|
if (limit_stream_num != GetStreamNumber(chunk.id))
|
|
{
|
|
SkipChunk(reader, &chunk, no_state, &bytes_read);
|
|
goto again;
|
|
}
|
|
}
|
|
|
|
*data = malloc(chunk.size);
|
|
if (!*data)
|
|
return READ_OUT_OF_MEMORY;
|
|
*chunk_size = chunk.size;
|
|
*chunk_type = chunk.id;
|
|
|
|
|
|
ret = Read(reader, *data, chunk.size, no_state, &bytes_read);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if ((chunk.size & 1))
|
|
{
|
|
bytes_available--;
|
|
reader->Skip(1);
|
|
}
|
|
return READ_OK;
|
|
}
|
|
else
|
|
return READ_FAILED;
|
|
|
|
}
|
|
|
|
int nsavi::Demuxer::GetSeekTable(nsavi::IDX1 **out_index)
|
|
{
|
|
if (idx1_found == PARSED)
|
|
{
|
|
*out_index = index;
|
|
return READ_OK;
|
|
}
|
|
|
|
if (idx1_found == NOT_FOUND)
|
|
{
|
|
return READ_NOT_FOUND;
|
|
}
|
|
|
|
if (idx1_found != NOT_READ)
|
|
return READ_FAILED;
|
|
|
|
uint64_t old_position = reader->Tell();
|
|
|
|
if (movie_found == PARSED)
|
|
reader->Seek(movie_start+movi_header.size);
|
|
else
|
|
reader->Seek(riff_start);
|
|
|
|
uint64_t start = reader->Tell();
|
|
uint32_t bytes_available = riff_header.size;
|
|
bytes_available -= (uint32_t)(start - riff_start);
|
|
|
|
while (idx1_found == NOT_READ)
|
|
{
|
|
if (bytes_available < 8)
|
|
{
|
|
idx1_found = NOT_FOUND;
|
|
reader->Seek(old_position);
|
|
return READ_NOT_FOUND;
|
|
}
|
|
uint32_t bytes_read;
|
|
int ret = ReadChunk(reader, &idx1_header, idx1_found, &bytes_read);
|
|
if (ret)
|
|
return ret;
|
|
|
|
bytes_available -= bytes_read;
|
|
if (bytes_available == (idx1_header.size - 12)) // some stupid program has this bug
|
|
{
|
|
idx1_header.size-=12;
|
|
}
|
|
if (bytes_available < idx1_header.size)
|
|
{
|
|
idx1_found = PARSE_ERROR;
|
|
reader->Seek(old_position);
|
|
return READ_INVALID_DATA;
|
|
}
|
|
switch(idx1_header.id)
|
|
{
|
|
// TODO: parse any other interesting chunks along the way
|
|
case '1xdi': // index v1 chunk
|
|
index = (nsavi::IDX1 *)malloc(idx1_header.size + sizeof(uint32_t));
|
|
if (index)
|
|
{
|
|
ret = Read(reader, ((uint8_t *)index) + sizeof(uint32_t), idx1_header.size, idx1_found, &bytes_read);
|
|
if (ret)
|
|
{
|
|
reader->Seek(old_position);
|
|
return ret;
|
|
}
|
|
|
|
bytes_available-=bytes_read;
|
|
index->index_count = idx1_header.size / sizeof(IDX1_INDEX);
|
|
if ((idx1_header.size & 1) && bytes_available)
|
|
{
|
|
bytes_available--;
|
|
reader->Skip(1);
|
|
}
|
|
idx1_found = PARSED;
|
|
}
|
|
else
|
|
{
|
|
reader->Seek(old_position);
|
|
return READ_OUT_OF_MEMORY;
|
|
}
|
|
|
|
break;
|
|
default: // skip anything we don't understand
|
|
case 'KNUJ': // skip junk chunks
|
|
ret = SkipChunk(reader, &idx1_header, idx1_found, &bytes_read);
|
|
if (ret)
|
|
return ret;
|
|
bytes_available -= bytes_read;
|
|
break;
|
|
}
|
|
}
|
|
|
|
*out_index = index;
|
|
reader->Seek(old_position);
|
|
return READ_OK;
|
|
}
|
|
|
|
int nsavi::Demuxer::GetIndexChunk(nsavi::INDX **out_index, uint64_t offset)
|
|
{
|
|
nsavi::INDX *index = 0;
|
|
uint64_t old_position = reader->Tell();
|
|
reader->Seek(offset);
|
|
ParseState dummy;
|
|
uint32_t bytes_read;
|
|
riff_chunk chunk;
|
|
int ret = ReadChunk(reader, &chunk, dummy, &bytes_read);
|
|
if (ret)
|
|
return ret;
|
|
index = (nsavi::INDX *)malloc(sizeof(uint32_t) + chunk.size);
|
|
if (index)
|
|
{
|
|
ret = Read(reader, ((uint8_t *)index) + sizeof(uint32_t), chunk.size, dummy, &bytes_read);
|
|
if (ret)
|
|
{
|
|
reader->Seek(old_position);
|
|
return ret;
|
|
}
|
|
index->size_bytes=chunk.size;
|
|
}
|
|
else
|
|
{
|
|
reader->Seek(old_position);
|
|
return READ_OUT_OF_MEMORY;
|
|
}
|
|
|
|
*out_index = index;
|
|
reader->Seek(old_position);
|
|
return READ_OK;
|
|
}
|
|
|
|
static bool IsCodecChunk(uint32_t header)
|
|
{
|
|
char *blah = (char *)&header;
|
|
if (blah[0] != 'i' && !isxdigit(blah[0]))
|
|
return false;
|
|
if (blah[1] != 'x' && !isxdigit(blah[1]))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
int nsavi::Demuxer::Seek(uint64_t offset, bool absolute, nsavi::avi_reader *reader)
|
|
{
|
|
/* verify index by reading the riff chunk and comparing position->chunk_id and position->size with the read chunk
|
|
if it fails, we'll try the two following things
|
|
1) try again without the -4
|
|
2) try from the start of the file
|
|
3) try from riff_start
|
|
*/
|
|
uint32_t bytes_read;
|
|
uint32_t chunk_header=0;
|
|
if (!reader)
|
|
reader = this->reader;
|
|
if (absolute)
|
|
{
|
|
reader->Seek(offset - 8);
|
|
reader->Peek(&chunk_header, 4, &bytes_read);
|
|
if (!IsCodecChunk(chunk_header))
|
|
{
|
|
reader->Skip(4);
|
|
reader->Peek(&chunk_header, 4, &bytes_read);
|
|
if (!IsCodecChunk(chunk_header))
|
|
{
|
|
reader->Skip(4);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
reader->Seek(movie_start+offset - 4);
|
|
reader->Peek(&chunk_header, 4, &bytes_read);
|
|
if (!IsCodecChunk(chunk_header))
|
|
{
|
|
reader->Seek(offset);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
riff_chunk test;
|
|
ParseState blah;
|
|
uint32_t bytes_read;
|
|
ReadChunk(f, &test, blah, &bytes_read);
|
|
fseek64(f, movie_start+position->offset - 4, SEEK_SET);
|
|
*/
|
|
return READ_OK;
|
|
}
|