winamp/Src/nsavi/demuxer.cpp

566 lines
13 KiB
C++
Raw Permalink Normal View History

2024-09-24 13:54:57 +01:00
#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;
}