mirror of
https://github.com/WinampDesktop/winamp.git
synced 2025-06-17 17:15:45 -04:00
Initial community commit
This commit is contained in:
493
Src/nsavi/ParserBase.cpp
Normal file
493
Src/nsavi/ParserBase.cpp
Normal file
@ -0,0 +1,493 @@
|
||||
#include "ParserBase.h"
|
||||
|
||||
nsavi::ParserBase::ParserBase(nsavi::avi_reader *_reader)
|
||||
{
|
||||
reader = _reader;
|
||||
riff_parsed = NOT_READ;
|
||||
header_list_parsed = NOT_READ;
|
||||
riff_start = 0;
|
||||
avi_header = 0;
|
||||
stream_list = 0;
|
||||
stream_list_size = 0;
|
||||
odml_header = 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;
|
||||
}
|
||||
|
||||
int nsavi::ParserBase::GetRIFFType(uint32_t *type)
|
||||
{
|
||||
if (riff_parsed == PARSE_RESYNC)
|
||||
reader->Seek(0); // seek to the beginning if we need to
|
||||
|
||||
if (riff_parsed == NOT_READ)
|
||||
{
|
||||
uint32_t bytes_read;
|
||||
// assume we are at the beginning of the file
|
||||
int ret = ReadChunk(reader, &riff_header, riff_parsed, &bytes_read);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!riff_header.type)
|
||||
{
|
||||
riff_parsed = PARSE_ERROR;
|
||||
return READ_INVALID_DATA;
|
||||
}
|
||||
|
||||
riff_start = reader->Tell();
|
||||
riff_parsed = PARSED;
|
||||
}
|
||||
|
||||
if (riff_parsed == PARSED)
|
||||
{
|
||||
*type = riff_header.type;
|
||||
return READ_OK;
|
||||
}
|
||||
|
||||
// we'll only get here if GetRIFFType was called a second time after an initial failure
|
||||
return READ_INVALID_CALL;
|
||||
}
|
||||
|
||||
|
||||
// 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::ParserBase::ParseStreamList(uint32_t chunk_size, STRL *stream, uint32_t *out_bytes_read)
|
||||
{
|
||||
uint32_t bytes_available = chunk_size;
|
||||
uint32_t stream_number = 0;
|
||||
while (bytes_available)
|
||||
{
|
||||
if (bytes_available < 8)
|
||||
{
|
||||
header_list_parsed = PARSE_ERROR;
|
||||
return READ_INVALID_DATA;
|
||||
}
|
||||
|
||||
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 'hrts': // strh
|
||||
|
||||
free(stream->stream_header);
|
||||
stream->stream_header = (nsavi::STRH *)malloc(chunk.size + sizeof(uint32_t));
|
||||
if (stream->stream_header)
|
||||
{
|
||||
ret = Read(reader, ((uint8_t *)stream->stream_header) + sizeof(uint32_t), chunk.size, header_list_parsed, &bytes_read);
|
||||
if (ret)
|
||||
return ret;
|
||||
bytes_available-=bytes_read;
|
||||
stream->stream_header->size_bytes = chunk.size;
|
||||
if ((chunk.size & 1) && bytes_available)
|
||||
{
|
||||
bytes_available--;
|
||||
reader->Skip(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return READ_OUT_OF_MEMORY;
|
||||
}
|
||||
break;
|
||||
case 'frts': // strf
|
||||
free(stream->stream_format);
|
||||
stream->stream_format = (nsavi::STRF *)malloc(chunk.size + sizeof(uint32_t));
|
||||
if (stream->stream_format)
|
||||
{
|
||||
ret = Read(reader, ((uint8_t *)stream->stream_format) + sizeof(uint32_t), chunk.size, header_list_parsed, &bytes_read);
|
||||
if (ret)
|
||||
return ret;
|
||||
bytes_available-=bytes_read;
|
||||
stream->stream_format->size_bytes = chunk.size;
|
||||
if ((chunk.size & 1) && bytes_available)
|
||||
{
|
||||
bytes_available--;
|
||||
reader->Skip(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return READ_OUT_OF_MEMORY;
|
||||
}
|
||||
break;
|
||||
case 'drts': // strd
|
||||
free(stream->stream_data);
|
||||
stream->stream_data = (nsavi::STRD *)malloc(chunk.size + sizeof(uint32_t));
|
||||
if (stream->stream_data)
|
||||
{
|
||||
ret = Read(reader, ((uint8_t *)stream->stream_data) + sizeof(uint32_t), chunk.size, header_list_parsed, &bytes_read);
|
||||
if (ret)
|
||||
return ret;
|
||||
bytes_available-=bytes_read;
|
||||
stream->stream_data->size_bytes = chunk.size;
|
||||
if ((chunk.size & 1) && bytes_available)
|
||||
{
|
||||
bytes_available--;
|
||||
reader->Skip(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return READ_OUT_OF_MEMORY;
|
||||
}
|
||||
break;
|
||||
case 'nrts': // strn
|
||||
free(stream->stream_name);
|
||||
stream->stream_name = (nsavi::STRN *)malloc(chunk.size + sizeof(uint32_t));
|
||||
if (stream->stream_name)
|
||||
{
|
||||
ret = Read(reader, ((uint8_t *)stream->stream_name) + sizeof(uint32_t), chunk.size, header_list_parsed, &bytes_read);
|
||||
if (ret)
|
||||
return ret;
|
||||
bytes_available-=bytes_read;
|
||||
stream->stream_name->size_bytes = chunk.size;
|
||||
if ((chunk.size & 1) && bytes_available)
|
||||
{
|
||||
bytes_available--;
|
||||
reader->Skip(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return READ_OUT_OF_MEMORY;
|
||||
}
|
||||
break;
|
||||
case 'xdni': // indx
|
||||
free(stream->stream_index);
|
||||
stream->stream_index = (nsavi::INDX *)malloc(chunk.size + sizeof(uint32_t));
|
||||
if (stream->stream_index)
|
||||
{
|
||||
ret = Read(reader, &stream->stream_index->entry_size, chunk.size, header_list_parsed, &bytes_read);
|
||||
if (ret)
|
||||
return ret;
|
||||
bytes_available-=bytes_read;
|
||||
stream->stream_index->size_bytes = chunk.size;
|
||||
if ((chunk.size & 1) && bytes_available)
|
||||
{
|
||||
bytes_available--;
|
||||
reader->Skip(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return READ_OUT_OF_MEMORY;
|
||||
}
|
||||
break;
|
||||
case nsaviFOURCC('v','p','r','p'):
|
||||
free(stream->video_properties);
|
||||
stream->video_properties = (nsavi::VPRP *)malloc(chunk.size + sizeof(uint32_t));
|
||||
if (stream->video_properties)
|
||||
{
|
||||
ret = Read(reader, &stream->video_properties->video_format_token, chunk.size, header_list_parsed, &bytes_read);
|
||||
if (ret)
|
||||
return ret;
|
||||
bytes_available-=bytes_read;
|
||||
stream->video_properties->size_bytes = chunk.size;
|
||||
if ((chunk.size & 1) && bytes_available)
|
||||
{
|
||||
bytes_available--;
|
||||
reader->Skip(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return READ_OUT_OF_MEMORY;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ret = SkipChunk(reader, &chunk, header_list_parsed, &bytes_read);
|
||||
if (ret)
|
||||
return ret;
|
||||
bytes_available -= bytes_read;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((chunk_size & 1) && bytes_available)
|
||||
{
|
||||
bytes_available--;
|
||||
reader->Skip(1);
|
||||
}
|
||||
*out_bytes_read = chunk_size - bytes_available;
|
||||
|
||||
// TODO: see what we managed to collect and return an error code accordingly
|
||||
return READ_OK;
|
||||
}
|
||||
|
||||
int nsavi::ParserBase::ParseODML(uint32_t chunk_size, uint32_t *out_bytes_read)
|
||||
{
|
||||
uint32_t bytes_available = chunk_size;
|
||||
uint32_t stream_number = 0;
|
||||
while (bytes_available)
|
||||
{
|
||||
if (bytes_available < 8)
|
||||
{
|
||||
header_list_parsed = PARSE_ERROR;
|
||||
return READ_INVALID_DATA;
|
||||
}
|
||||
|
||||
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 'hlmd': // dmlh
|
||||
|
||||
free(odml_header);
|
||||
odml_header = (nsavi::DMLH *)malloc(chunk.size + sizeof(uint32_t));
|
||||
if (odml_header)
|
||||
{
|
||||
ret = Read(reader, ((uint8_t *)odml_header) + sizeof(uint32_t), chunk.size, header_list_parsed, &bytes_read);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
bytes_available-=bytes_read;
|
||||
odml_header->size_bytes = chunk.size;
|
||||
if ((chunk.size & 1) && bytes_available)
|
||||
{
|
||||
bytes_available--;
|
||||
reader->Skip(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return READ_OUT_OF_MEMORY;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ret = SkipChunk(reader, &chunk, header_list_parsed, &bytes_read);
|
||||
if (ret)
|
||||
return ret;
|
||||
bytes_available -= bytes_read;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((chunk_size & 1) && bytes_available)
|
||||
{
|
||||
bytes_available--;
|
||||
reader->Skip(1);
|
||||
}
|
||||
*out_bytes_read = chunk_size - bytes_available;
|
||||
|
||||
// TODO: see what we managed to collect and return an error code accordingly
|
||||
return READ_OK;
|
||||
}
|
||||
|
||||
int nsavi::ParserBase::ParseHeaderList(uint32_t chunk_size, uint32_t *out_bytes_read)
|
||||
{
|
||||
chunk_size = (chunk_size+1) & ~1;
|
||||
uint32_t bytes_available = chunk_size;
|
||||
uint32_t stream_number = 0;
|
||||
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 'hiva': // avih
|
||||
free(avi_header);
|
||||
avi_header = (nsavi::AVIH *)malloc(chunk.size + sizeof(uint32_t));
|
||||
if (avi_header)
|
||||
{
|
||||
ret = Read(reader, ((uint8_t *)avi_header) + sizeof(uint32_t), chunk.size, header_list_parsed, &bytes_read);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
bytes_available-=bytes_read;
|
||||
avi_header->size_bytes = chunk.size;
|
||||
if ((chunk.size & 1) && bytes_available)
|
||||
{
|
||||
bytes_available--;
|
||||
reader->Skip(1);
|
||||
}
|
||||
if (avi_header->streams && !stream_list)
|
||||
{
|
||||
// if we fail to allocate, no major worry (maybe avi_header->streams was incorrect and some huge value
|
||||
// we'll just dynamically allocate as needed
|
||||
|
||||
stream_list_size = 0;
|
||||
if (avi_header->streams < 65536) /* set a reasonable upper bound */
|
||||
{
|
||||
stream_list = (STRL *)calloc(avi_header->streams, sizeof(STRL));
|
||||
if (stream_list)
|
||||
{
|
||||
stream_list_size = avi_header->streams;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
header_list_parsed = PARSE_ERROR;
|
||||
return READ_OUT_OF_MEMORY;
|
||||
}
|
||||
break;
|
||||
case 'TSIL':
|
||||
switch(chunk.type)
|
||||
{
|
||||
case 'lrts':
|
||||
{
|
||||
if (stream_list_size <= stream_number)
|
||||
{
|
||||
stream_list = (STRL *)realloc(stream_list, (stream_number+1) * sizeof(STRL));
|
||||
if (!stream_list)
|
||||
{
|
||||
header_list_parsed = PARSE_ERROR;
|
||||
return READ_OUT_OF_MEMORY;
|
||||
}
|
||||
stream_list_size = stream_number+1;
|
||||
}
|
||||
|
||||
STRL &stream = stream_list[stream_number];
|
||||
memset(&stream, 0, sizeof(STRL));
|
||||
ret = ParseStreamList(chunk.size, &stream, &bytes_read);
|
||||
if (ret)
|
||||
return ret;
|
||||
stream_number++;
|
||||
bytes_available-=bytes_read;
|
||||
if ((chunk.size & 1) && bytes_available)
|
||||
{
|
||||
bytes_available--;
|
||||
reader->Skip(1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'lmdo':
|
||||
ret = ParseODML(chunk.size, &bytes_read);
|
||||
if (ret)
|
||||
return ret;
|
||||
bytes_available -= bytes_read;
|
||||
break;
|
||||
default:
|
||||
ret = SkipChunk(reader, &chunk, header_list_parsed, &bytes_read);
|
||||
if (ret)
|
||||
return ret;
|
||||
bytes_available -= bytes_read;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = SkipChunk(reader, &chunk, header_list_parsed, &bytes_read);
|
||||
if (ret)
|
||||
return ret;
|
||||
bytes_available -= bytes_read;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ((chunk_size & 1) && bytes_available)
|
||||
{
|
||||
bytes_available--;
|
||||
reader->Skip(1);
|
||||
}
|
||||
stream_list_size = stream_number;
|
||||
*out_bytes_read = chunk_size - bytes_available;
|
||||
return READ_OK;
|
||||
// TODO: see what we managed to collect and return an error code accordingly
|
||||
}
|
45
Src/nsavi/ParserBase.h
Normal file
45
Src/nsavi/ParserBase.h
Normal file
@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
#include "read.h"
|
||||
#include "avi_header.h"
|
||||
#include "avi_reader.h"
|
||||
#include "info.h"
|
||||
|
||||
namespace nsavi
|
||||
{
|
||||
|
||||
|
||||
struct HeaderList
|
||||
{
|
||||
const AVIH *avi_header;
|
||||
const STRL *stream_list;
|
||||
size_t stream_list_size;
|
||||
const DMLH *odml_header;
|
||||
};
|
||||
|
||||
class ParserBase
|
||||
{
|
||||
public:
|
||||
ParserBase(nsavi::avi_reader *_reader);
|
||||
int GetRIFFType(uint32_t *type);
|
||||
|
||||
|
||||
protected:
|
||||
int ParseHeaderList(uint32_t chunk_size, uint32_t *out_bytes_read);
|
||||
int ParseStreamList(uint32_t chunk_size, STRL *stream, uint32_t *out_bytes_read);
|
||||
int ParseODML(uint32_t chunk_size, uint32_t *out_bytes_read);
|
||||
|
||||
nsavi::avi_reader *reader;
|
||||
|
||||
/* RIFF header (12 bytes at start of file) */
|
||||
ParseState riff_parsed;
|
||||
riff_chunk riff_header;
|
||||
uint64_t riff_start; // should normally be 12
|
||||
|
||||
/* header list */
|
||||
ParseState header_list_parsed;
|
||||
AVIH *avi_header;
|
||||
STRL *stream_list;
|
||||
size_t stream_list_size;
|
||||
DMLH *odml_header;
|
||||
};
|
||||
}
|
248
Src/nsavi/avi_header.h
Normal file
248
Src/nsavi/avi_header.h
Normal file
@ -0,0 +1,248 @@
|
||||
#pragma once
|
||||
#include <bfc/platform/types.h>
|
||||
|
||||
namespace nsavi
|
||||
{
|
||||
#pragma pack(push, 1)
|
||||
static const uint32_t avi_header_flags_has_index = 0x10;
|
||||
static const uint32_t avi_header_flags_must_use_index = 0x20;
|
||||
static const uint32_t avi_header_flags_is_interleaved = 0x100;
|
||||
static const uint32_t avi_header_trust_ck_type = 0x800; // benski> i have no fucking clue
|
||||
static const uint32_t avi_header_flags_was_capture_file = 0x10000;
|
||||
static const uint32_t avi_header_flags_copyrighted = 0x20000;
|
||||
|
||||
static const uint32_t stream_type_audio = 0x73647561;
|
||||
static const uint32_t stream_type_video = 0x73646976;
|
||||
|
||||
const uint16_t audio_format_pcm = 1;
|
||||
const uint16_t audio_format_ms_adpcm = 2;
|
||||
const uint16_t audio_format_alaw = 6;
|
||||
const uint16_t audio_format_ulaw = 7;
|
||||
const uint16_t audio_format_ima_adpcm = 17;
|
||||
const uint16_t audio_format_truespeech = 34;
|
||||
const uint16_t audio_format_mp2 = 80;
|
||||
const uint16_t audio_format_mp3 = 85;
|
||||
const uint16_t audio_format_a52 = 8192;
|
||||
const uint16_t audio_format_aac = 255;
|
||||
const uint16_t audio_format_vorbis = 26447;
|
||||
const uint16_t audio_format_speex = 41225;
|
||||
const uint16_t audio_format_extensible = 65534; // aka WAVE_FORMAT_EXTENSIBLE
|
||||
const uint16_t audio_format_dts = 8193;
|
||||
|
||||
static uint32_t video_format_rgb = 0;
|
||||
static uint32_t video_format_rle8 = 1;
|
||||
static uint32_t video_format_rle4 = 2;
|
||||
|
||||
static const uint32_t idx1_flags_keyframe = 0x10;
|
||||
static const uint32_t idx1_flags_no_duration= 0x100;
|
||||
|
||||
struct AVIH
|
||||
{
|
||||
uint32_t size_bytes;
|
||||
uint32_t microseconds_per_frame;
|
||||
uint32_t max_bytes_per_second;
|
||||
uint32_t padding_granularity;
|
||||
uint32_t flags;
|
||||
uint32_t total_frames;
|
||||
uint32_t initial_frames;
|
||||
uint32_t streams;
|
||||
uint32_t suggested_buffer_size;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint8_t reserved[16];
|
||||
};
|
||||
|
||||
struct STRH
|
||||
{
|
||||
uint32_t size_bytes;
|
||||
uint32_t stream_type;
|
||||
uint32_t fourcc;
|
||||
uint32_t flags;
|
||||
uint16_t priority;
|
||||
uint16_t language;
|
||||
uint32_t initial_frames;
|
||||
uint32_t scale;
|
||||
uint32_t rate;
|
||||
uint32_t start;
|
||||
uint32_t length;
|
||||
uint32_t suggested_buffer_size;
|
||||
uint32_t quality;
|
||||
uint32_t sample_size;
|
||||
int16_t left;
|
||||
int16_t top;
|
||||
int16_t right;
|
||||
int16_t bottom;
|
||||
};
|
||||
|
||||
struct VIDEO_FIELD_DESC
|
||||
{
|
||||
uint32_t compressed_height;
|
||||
uint32_t compressed_width;
|
||||
uint32_t valid_height;
|
||||
uint32_t valid_width;
|
||||
uint32_t valid_x_offset;
|
||||
uint32_t valid_y_offset;
|
||||
uint32_t x_offset;
|
||||
uint32_t valid_y_start_line;
|
||||
};
|
||||
|
||||
struct VPRP
|
||||
{
|
||||
uint32_t size_bytes;
|
||||
uint32_t video_format_token;
|
||||
uint32_t video_standard;
|
||||
uint32_t vertical_refresh_rate;
|
||||
uint32_t horizontal_total;
|
||||
uint32_t vertical_total;
|
||||
uint32_t aspect_ratio;
|
||||
uint32_t frame_width;
|
||||
uint32_t frame_height;
|
||||
uint32_t field_info_size;
|
||||
VIDEO_FIELD_DESC field_info[1];
|
||||
};
|
||||
|
||||
struct STRF
|
||||
{
|
||||
uint32_t size_bytes;
|
||||
};
|
||||
|
||||
struct STRD
|
||||
{
|
||||
uint32_t size_bytes;
|
||||
};
|
||||
|
||||
struct STRN
|
||||
{
|
||||
uint32_t size_bytes;
|
||||
};
|
||||
|
||||
struct DMLH
|
||||
{
|
||||
uint32_t size_bytes;
|
||||
uint32_t total_frames;
|
||||
};
|
||||
|
||||
struct IDX1_INDEX
|
||||
{
|
||||
uint32_t chunk_id;
|
||||
uint32_t flags;
|
||||
uint32_t offset;
|
||||
uint32_t chunk_size;
|
||||
};
|
||||
|
||||
struct IDX1
|
||||
{
|
||||
uint32_t index_count;
|
||||
IDX1_INDEX indices[1];
|
||||
};
|
||||
|
||||
static const uint8_t indx_type_master = 0x0;
|
||||
static const uint8_t indx_type_chunk = 0x1;
|
||||
static const uint8_t indx_type_data = 0x80;
|
||||
static const uint8_t indx_subtype_field = 0x1;
|
||||
|
||||
|
||||
|
||||
struct INDX
|
||||
{
|
||||
uint32_t size_bytes;
|
||||
uint16_t entry_size;
|
||||
uint8_t index_sub_type;
|
||||
uint8_t index_type;
|
||||
uint32_t number_of_entries;
|
||||
uint32_t chunk_id;
|
||||
};
|
||||
|
||||
struct INDX_MASTER_ENTRY
|
||||
{
|
||||
uint64_t offset;
|
||||
uint32_t index_size;
|
||||
uint32_t duration;
|
||||
};
|
||||
|
||||
struct AVISUPERINDEX
|
||||
{
|
||||
INDX indx;
|
||||
uint32_t reserved[3];
|
||||
INDX_MASTER_ENTRY entries[1]; // actual size determined by indx.number_of_entries
|
||||
};
|
||||
|
||||
struct INDX_CHUNK_ENTRY
|
||||
{
|
||||
uint32_t offset;
|
||||
uint32_t size ;// bit 31 is set if this is NOT a keyframe
|
||||
};
|
||||
|
||||
struct AVISTDINDEX
|
||||
{
|
||||
INDX indx;
|
||||
uint64_t base_offset;
|
||||
uint32_t reserved;
|
||||
INDX_CHUNK_ENTRY entries[1]; // actual size determined by indx.number_of_entries
|
||||
};
|
||||
|
||||
struct INDX_FIELD_ENTRY
|
||||
{
|
||||
uint32_t offset;
|
||||
uint32_t size; // size of all fields. bit 31 set for NON-keyframes
|
||||
uint32_t offset_field2; // offset to second field
|
||||
};
|
||||
|
||||
struct AVIFIELDINDEX
|
||||
{
|
||||
INDX indx;
|
||||
uint64_t base_offset;
|
||||
uint32_t reserved;
|
||||
INDX_FIELD_ENTRY entries[1]; // actual size determined by indx.number_of_entries
|
||||
};
|
||||
|
||||
struct video_format
|
||||
{
|
||||
uint32_t size_bytes;
|
||||
uint32_t video_format_size_bytes; // redundant, I know
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
uint16_t planes;
|
||||
uint16_t bits_per_pixel;
|
||||
uint32_t compression;
|
||||
uint32_t image_size;
|
||||
int32_t x_pixels_per_meter;
|
||||
int32_t y_pixels_per_meter;
|
||||
uint32_t color_used;
|
||||
uint32_t color_important;
|
||||
};
|
||||
|
||||
|
||||
struct audio_format
|
||||
{
|
||||
uint32_t size_bytes;
|
||||
uint16_t format;
|
||||
uint16_t channels;
|
||||
uint32_t sample_rate;
|
||||
uint32_t average_bytes_per_second;
|
||||
uint16_t block_align;
|
||||
uint16_t bits_per_sample;
|
||||
uint16_t extra_size_bytes;
|
||||
};
|
||||
|
||||
struct mp3_format
|
||||
{
|
||||
audio_format format;
|
||||
uint16_t id;
|
||||
uint32_t flags;
|
||||
uint16_t block_size;
|
||||
uint16_t frames_per_block;
|
||||
uint16_t codec_delay;
|
||||
};
|
||||
|
||||
struct STRL
|
||||
{
|
||||
STRH *stream_header;
|
||||
STRF *stream_format;
|
||||
STRD *stream_data;
|
||||
STRN *stream_name;
|
||||
INDX *stream_index;
|
||||
VPRP *video_properties;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
}
|
45
Src/nsavi/avi_reader.h
Normal file
45
Src/nsavi/avi_reader.h
Normal file
@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
#include <bfc/platform/types.h>
|
||||
#include <stdio.h>
|
||||
#include "read.h" // for the error codes
|
||||
|
||||
|
||||
namespace nsavi
|
||||
{
|
||||
// return codes from avi_reader functions
|
||||
|
||||
enum
|
||||
{
|
||||
READ_OK = 0,
|
||||
READ_EOF = 1,
|
||||
READ_FAILED = 2,
|
||||
READ_INVALID_DATA = 3, // read was successful but data didn't make any sense
|
||||
READ_INVALID_CALL = 4, // wrong time to call this function
|
||||
READ_NOT_FOUND = 5, // requested item doesn't exist in the file
|
||||
READ_OUT_OF_MEMORY = 6, // some malloc failed and so we're aborting
|
||||
READ_DISCONNECT = 7,
|
||||
};
|
||||
|
||||
class avi_reader
|
||||
{
|
||||
public:
|
||||
virtual int Read(void *buffer, uint32_t read_length, uint32_t *bytes_read)=0;
|
||||
|
||||
// TODO: need to put an upper bound on Peek buffer sizes
|
||||
virtual int Peek(void *buffer, uint32_t read_length, uint32_t *bytes_read)=0;
|
||||
|
||||
// in_avi will call this before descending into certain chunks that will be read entirely (e.g. avih)
|
||||
// you aren't required to do anything in response
|
||||
virtual void OverlappedHint(uint32_t read_length){}
|
||||
|
||||
virtual int Seek(uint64_t position)=0;
|
||||
|
||||
virtual uint64_t Tell()=0;
|
||||
|
||||
// skip ahead a certain number of bytes. equivalent to fseek(..., SEEK_CUR)
|
||||
virtual int Skip(uint32_t skip_bytes)=0;
|
||||
virtual uint64_t GetContentLength()=0;
|
||||
virtual void GetFilename(wchar_t *fn, size_t len)=0;
|
||||
};
|
||||
|
||||
}
|
565
Src/nsavi/demuxer.cpp
Normal file
565
Src/nsavi/demuxer.cpp
Normal file
@ -0,0 +1,565 @@
|
||||
#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;
|
||||
}
|
36
Src/nsavi/demuxer.h
Normal file
36
Src/nsavi/demuxer.h
Normal file
@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
/* this parser is meant for actual playback */
|
||||
#include "read.h"
|
||||
#include "avi_header.h"
|
||||
#include "avi_reader.h"
|
||||
#include "info.h"
|
||||
#include "ParserBase.h"
|
||||
namespace nsavi
|
||||
{
|
||||
class Demuxer : public ParserBase
|
||||
{
|
||||
public:
|
||||
Demuxer(nsavi::avi_reader *_reader);
|
||||
int GetNextMovieChunk(nsavi::avi_reader *reader, void **data, uint32_t *chunk_size, uint32_t *chunk_type, int limit_stream_num=65536);
|
||||
int GetSeekTable(nsavi::IDX1 **out_index); // get the idx1 chunk
|
||||
int GetIndexChunk(nsavi::INDX **out_index, uint64_t offset); // get the INDX/##ix/##ix chunk at the given position
|
||||
int Seek(uint64_t offset, bool absolute, nsavi::avi_reader *reader);
|
||||
int GetHeaderList(HeaderList *header_list);
|
||||
int FindMovieChunk();
|
||||
int SeekToMovieChunk(nsavi::avi_reader *reader);
|
||||
private:
|
||||
/* movie chunk */
|
||||
ParseState movie_found;
|
||||
riff_chunk movi_header;
|
||||
uint64_t movie_start;
|
||||
|
||||
/* idx1 seektable */
|
||||
ParseState idx1_found;
|
||||
riff_chunk idx1_header; // dunno if we really need it
|
||||
nsavi::IDX1 *index;
|
||||
|
||||
/* INFO */
|
||||
Info *info;
|
||||
ParseState info_found;
|
||||
};
|
||||
}
|
172
Src/nsavi/duration.cpp
Normal file
172
Src/nsavi/duration.cpp
Normal file
@ -0,0 +1,172 @@
|
||||
#include "duration.h"
|
||||
|
||||
nsavi::Duration::Duration(nsavi::avi_reader *_reader) : ParserBase(_reader)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
// 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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
int nsavi::Duration::GetDuration(int *time_ms)
|
||||
{
|
||||
uint32_t riff_type;
|
||||
int ret = GetRIFFType(&riff_type);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (riff_type != ' IVA')
|
||||
return READ_INVALID_DATA;
|
||||
|
||||
nsavi::HeaderList header_list;
|
||||
ret = GetHeaderList(&header_list);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
int duration=-1;
|
||||
for (uint32_t i=0;i!=header_list.stream_list_size;i++)
|
||||
{
|
||||
const nsavi::STRL &stream = header_list.stream_list[i];
|
||||
if (stream.stream_header)
|
||||
{
|
||||
if (stream.stream_header->stream_type == nsavi::stream_type_audio && stream.stream_header->rate)
|
||||
{
|
||||
if (stream.stream_header->length && !stream.stream_header->sample_size)
|
||||
duration = (uint64_t)stream.stream_header->length * (uint64_t)stream.stream_header->scale * 1000ULL / (uint64_t)stream.stream_header->rate;
|
||||
}
|
||||
else if (stream.stream_header->stream_type == nsavi::stream_type_video && stream.stream_header->rate)
|
||||
{
|
||||
if (duration == -1)
|
||||
duration = (uint64_t)stream.stream_header->length * (uint64_t)stream.stream_header->scale * 1000ULL / (uint64_t)stream.stream_header->rate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*time_ms = duration;
|
||||
|
||||
return nsavi::READ_OK;
|
||||
}
|
||||
|
||||
int nsavi::Duration::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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
18
Src/nsavi/duration.h
Normal file
18
Src/nsavi/duration.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
/* this parser is meant for retrieving duration */
|
||||
#include "read.h"
|
||||
#include "avi_header.h"
|
||||
#include "avi_reader.h"
|
||||
#include "ParserBase.h"
|
||||
|
||||
namespace nsavi
|
||||
{
|
||||
class Duration : public ParserBase
|
||||
{
|
||||
public:
|
||||
Duration(nsavi::avi_reader *_reader);
|
||||
int GetDuration(int *time_ms);
|
||||
int GetHeaderList(HeaderList *header_list);
|
||||
|
||||
};
|
||||
};
|
43
Src/nsavi/file_avi_reader.cpp
Normal file
43
Src/nsavi/file_avi_reader.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
#include "file_avi_reader.h"
|
||||
|
||||
AVIReaderFILE::AVIReaderFILE(const wchar_t *filename)
|
||||
{
|
||||
f = _wfopen(filename, L"rb");
|
||||
}
|
||||
|
||||
int AVIReaderFILE::Read(void *buffer, uint32_t read_length, uint32_t *bytes_read)
|
||||
{
|
||||
*bytes_read = fread(buffer, 1, read_length, f);
|
||||
return nsavi::READ_OK;
|
||||
}
|
||||
|
||||
int AVIReaderFILE::Peek(void *buffer, uint32_t read_length, uint32_t *bytes_read)
|
||||
{
|
||||
*bytes_read = fread(buffer, 1, read_length, f);
|
||||
fseek(f, -read_length, SEEK_CUR);
|
||||
return nsavi::READ_OK;
|
||||
}
|
||||
|
||||
int AVIReaderFILE::Seek(uint64_t position)
|
||||
{
|
||||
fsetpos(f, (const fpos_t *)&position);
|
||||
return nsavi::READ_OK;
|
||||
}
|
||||
|
||||
uint64_t AVIReaderFILE::Tell()
|
||||
{
|
||||
uint64_t pos;
|
||||
fgetpos(f, (fpos_t *)&pos);
|
||||
return pos;
|
||||
}
|
||||
|
||||
int AVIReaderFILE::Skip(uint32_t skip_bytes)
|
||||
{
|
||||
fseek(f, skip_bytes, SEEK_CUR);
|
||||
return nsavi::READ_OK;
|
||||
}
|
||||
|
||||
AVIReaderFILE::~AVIReaderFILE()
|
||||
{
|
||||
fclose(f);
|
||||
}
|
24
Src/nsavi/file_avi_reader.h
Normal file
24
Src/nsavi/file_avi_reader.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include "avi_reader.h"
|
||||
#include <stdio.h>
|
||||
|
||||
class AVIReaderFILE : public nsavi::avi_reader
|
||||
{
|
||||
public:
|
||||
AVIReaderFILE(const wchar_t *filename);
|
||||
~AVIReaderFILE();
|
||||
|
||||
/* avi_reader implementation */
|
||||
int Read(void *buffer, uint32_t read_length, uint32_t *bytes_read);
|
||||
int Peek(void *buffer, uint32_t read_length, uint32_t *bytes_read);
|
||||
int Seek(uint64_t position);
|
||||
uint64_t Tell();
|
||||
int Skip(uint32_t skip_bytes);
|
||||
void GetFilename(wchar_t *fn, size_t fn_len) {}
|
||||
uint64_t GetContentLength()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
private:
|
||||
FILE *f;
|
||||
};
|
68
Src/nsavi/info.cpp
Normal file
68
Src/nsavi/info.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
#include "info.h"
|
||||
#include "read.h"
|
||||
|
||||
nsavi::Info::Info()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
nsavi::Info::~Info()
|
||||
{
|
||||
for (auto itr = this->begin(); itr != this->end(); itr++)
|
||||
{
|
||||
free((void*)itr->second);
|
||||
}
|
||||
}
|
||||
|
||||
int nsavi::Info::Read(nsavi::avi_reader* reader, uint32_t data_len)
|
||||
{
|
||||
while (data_len)
|
||||
{
|
||||
riff_chunk chunk;
|
||||
uint32_t bytes_read = 0;
|
||||
nsavi::read_riff_chunk(reader, &chunk, &bytes_read);
|
||||
data_len -= bytes_read;
|
||||
size_t malloc_size = chunk.size + 1;
|
||||
if (malloc_size == 0)
|
||||
return READ_INVALID_DATA;
|
||||
|
||||
char* str = (char*)calloc(malloc_size, sizeof(char));
|
||||
if (!str)
|
||||
return READ_OUT_OF_MEMORY;
|
||||
|
||||
reader->Read(str, chunk.size, &bytes_read);
|
||||
str[chunk.size] = 0;
|
||||
data_len -= bytes_read;
|
||||
|
||||
Set(chunk.id, str);
|
||||
|
||||
if (chunk.size & 1)
|
||||
{
|
||||
reader->Skip(1);
|
||||
data_len--;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void nsavi::Info::Set(uint32_t chunk_id, const char* data)
|
||||
{
|
||||
auto it = this->find(chunk_id);
|
||||
if (this->end() == it)
|
||||
{
|
||||
this->insert({ chunk_id, data });
|
||||
}
|
||||
else
|
||||
{
|
||||
it->second = data;
|
||||
}
|
||||
}
|
||||
const char* nsavi::Info::GetMetadata(uint32_t id)
|
||||
{
|
||||
InfoMap::iterator itr = InfoMap::find(id);
|
||||
if (itr != InfoMap::end())
|
||||
{
|
||||
return itr->second;
|
||||
}
|
||||
return 0;
|
||||
}
|
18
Src/nsavi/info.h
Normal file
18
Src/nsavi/info.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include "avi_reader.h"
|
||||
#include <map>
|
||||
|
||||
namespace nsavi
|
||||
{
|
||||
typedef std::map<uint32_t, const char*> InfoMap;
|
||||
class Info : public InfoMap
|
||||
{
|
||||
public:
|
||||
Info();
|
||||
~Info();
|
||||
int Read(avi_reader* reader, uint32_t data_len);
|
||||
const char* GetMetadata(uint32_t id);
|
||||
|
||||
void Set(uint32_t chunk_id, const char* data);
|
||||
};
|
||||
};
|
269
Src/nsavi/main.cpp
Normal file
269
Src/nsavi/main.cpp
Normal file
@ -0,0 +1,269 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <windows.h> // evil, i know
|
||||
#include <bfc/platform/types.h>
|
||||
#include "avi_header.h"
|
||||
#include "file_avi_reader.h"
|
||||
#include "read.h"
|
||||
#include "Info.h"
|
||||
|
||||
using namespace nsavi;
|
||||
|
||||
void printf_riff_chunk(const riff_chunk *chunk, int indent)
|
||||
{
|
||||
char cc[5];
|
||||
memcpy(cc, &chunk->id, 4);
|
||||
cc[4]=0;
|
||||
if (chunk->type)
|
||||
{
|
||||
char type[5];
|
||||
memcpy(type, &chunk->type, 4);
|
||||
type[4]=0;
|
||||
printf("%*sID: %4s%*sSIZE: %10u TYPE: %s\r\n", indent, "", cc,8-indent, "",chunk->size, type);
|
||||
}
|
||||
else
|
||||
printf("%*sID: %4s%*sSIZE: %10u\r\n", indent, "", cc, 8-indent, "", chunk->size);
|
||||
}
|
||||
|
||||
|
||||
|
||||
uint32_t ParseLIST(nsavi::avi_reader *reader, const riff_chunk *parse_chunk, int indent);
|
||||
uint32_t ParseHDRL(nsavi::avi_reader *reader, uint32_t chunk_size, int indent)
|
||||
{
|
||||
uint32_t total_bytes_read=0;
|
||||
uint32_t bytes_read=0;
|
||||
|
||||
riff_chunk chunk;
|
||||
while ((chunk_size - total_bytes_read) >= 8
|
||||
&& total_bytes_read < chunk_size // seems redundant, but since the above is unsigned math, we won't get negative values
|
||||
&& (read_riff_chunk(reader, &chunk, &bytes_read) == READ_OK))
|
||||
{
|
||||
total_bytes_read += bytes_read;
|
||||
printf_riff_chunk(&chunk, indent);
|
||||
if (chunk.id == 'TSIL')
|
||||
{
|
||||
bytes_read = ParseLIST(reader, &chunk, indent+1);
|
||||
if (!bytes_read)
|
||||
return 0;
|
||||
total_bytes_read += bytes_read;
|
||||
}
|
||||
else if (chunk.id == 'hiva')
|
||||
{
|
||||
nsavi::AVIH *header = (nsavi::AVIH *)malloc(chunk.size + sizeof(uint32_t));
|
||||
if (header)
|
||||
{
|
||||
reader->Read(((uint8_t *)header) + sizeof(uint32_t), chunk.size, &bytes_read);
|
||||
if (bytes_read != chunk.size)
|
||||
return 0;
|
||||
total_bytes_read+=bytes_read;
|
||||
header->size_bytes = chunk.size;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (skip_chunk(reader, &chunk, &bytes_read) != READ_OK)
|
||||
return 0;
|
||||
|
||||
total_bytes_read += bytes_read;
|
||||
}
|
||||
|
||||
}
|
||||
return total_bytes_read;
|
||||
}
|
||||
|
||||
uint32_t ParseSTRL(nsavi::avi_reader *reader, uint32_t chunk_size, int indent)
|
||||
{
|
||||
uint32_t total_bytes_read=0;
|
||||
uint32_t bytes_read=0;
|
||||
|
||||
riff_chunk chunk;
|
||||
while ((chunk_size - total_bytes_read) >= 8
|
||||
&& total_bytes_read < chunk_size // seems redundant, but since the above is unsigned math, we won't get negative values
|
||||
&& (read_riff_chunk(reader, &chunk, &bytes_read) == READ_OK))
|
||||
{
|
||||
total_bytes_read += bytes_read;
|
||||
printf_riff_chunk(&chunk, indent);
|
||||
if (chunk.id == 'TSIL')
|
||||
{
|
||||
bytes_read = ParseLIST(reader, &chunk, indent+1);
|
||||
if (!bytes_read)
|
||||
return 0;
|
||||
total_bytes_read += bytes_read;
|
||||
}
|
||||
else if (chunk.id == 'hrts')
|
||||
{
|
||||
nsavi::STRH *header = (nsavi::STRH *)malloc(chunk.size + sizeof(uint32_t));
|
||||
if (header)
|
||||
{
|
||||
reader->Read(((uint8_t *)header) + sizeof(uint32_t), chunk.size, &bytes_read);
|
||||
if (bytes_read != chunk.size)
|
||||
return 0;
|
||||
total_bytes_read+=bytes_read;
|
||||
header->size_bytes = chunk.size;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
else if (chunk.id == 'frts')
|
||||
{
|
||||
nsavi::STRF *header = (nsavi::STRF *)malloc(chunk.size + sizeof(uint32_t));
|
||||
if (header)
|
||||
{
|
||||
reader->Read(((uint8_t *)header) + sizeof(uint32_t), chunk.size, &bytes_read);
|
||||
if (bytes_read != chunk.size)
|
||||
return 0;
|
||||
total_bytes_read+=bytes_read;
|
||||
header->size_bytes = chunk.size;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
else if (chunk.id == 'xdni')
|
||||
{
|
||||
nsavi::INDX *index = (nsavi::INDX *)malloc(chunk.size + sizeof(uint32_t));
|
||||
if (index)
|
||||
{
|
||||
reader->Read(&index->entry_size, chunk.size, &bytes_read);
|
||||
if (bytes_read != chunk.size)
|
||||
return 0;
|
||||
total_bytes_read+=bytes_read;
|
||||
index->size_bytes = chunk.size;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (skip_chunk(reader, &chunk, &bytes_read) != READ_OK)
|
||||
return 0;
|
||||
|
||||
total_bytes_read += bytes_read;
|
||||
}
|
||||
|
||||
}
|
||||
return total_bytes_read;
|
||||
}
|
||||
|
||||
uint32_t ParseGeneric(nsavi::avi_reader *reader, uint32_t chunk_size, int indent)
|
||||
{
|
||||
uint32_t total_bytes_read=0;
|
||||
uint32_t bytes_read=0;
|
||||
|
||||
riff_chunk chunk;
|
||||
while ((chunk_size - total_bytes_read) >= 8
|
||||
&& total_bytes_read < chunk_size // seems redundant, but since the above is unsigned math, we won't get negative values
|
||||
&& (read_riff_chunk(reader, &chunk, &bytes_read) == READ_OK))
|
||||
{
|
||||
total_bytes_read += bytes_read;
|
||||
printf_riff_chunk(&chunk, indent);
|
||||
if (chunk.id == 'TSIL')
|
||||
{
|
||||
bytes_read = ParseLIST(reader, &chunk, indent+1);
|
||||
if (!bytes_read)
|
||||
return 0;
|
||||
total_bytes_read += bytes_read;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (skip_chunk(reader, &chunk, &bytes_read) != READ_OK)
|
||||
return 0;
|
||||
|
||||
total_bytes_read += bytes_read;
|
||||
}
|
||||
|
||||
}
|
||||
return total_bytes_read;
|
||||
}
|
||||
|
||||
uint32_t ParseLIST(nsavi::avi_reader *reader, const riff_chunk *parse_chunk, int indent)
|
||||
{
|
||||
uint32_t total_bytes_read=0;
|
||||
uint32_t bytes_read=0;
|
||||
|
||||
if (parse_chunk->type == 'lrdh')
|
||||
ParseHDRL(reader, parse_chunk->size, indent);
|
||||
else if (parse_chunk->type == 'lrts')
|
||||
ParseSTRL(reader, parse_chunk->size, indent);
|
||||
else if (parse_chunk->type == 'lmdo')
|
||||
ParseSTRL(reader, parse_chunk->size, indent);
|
||||
else if (parse_chunk->type == 'OFNI')
|
||||
{
|
||||
Info *info = new Info;
|
||||
info->Read(reader, parse_chunk->size);
|
||||
info = info;
|
||||
}
|
||||
//else if (parse_chunk->type == 'ivom')
|
||||
//ParseGeneric(reader, parse_chunk->size, indent);
|
||||
//else if (parse_chunk->type == ' cer')
|
||||
//ParseGeneric(reader, parse_chunk->size, indent);
|
||||
else
|
||||
reader->Skip(parse_chunk->size);
|
||||
|
||||
if (parse_chunk->size & 1)
|
||||
reader->Skip(1);
|
||||
|
||||
|
||||
return parse_chunk->size;
|
||||
}
|
||||
|
||||
uint32_t ParseRIFF(nsavi::avi_reader *reader, uint32_t chunk_size, int indent)
|
||||
{
|
||||
uint32_t total_bytes_read=0;
|
||||
uint32_t bytes_read=0;
|
||||
|
||||
riff_chunk chunk;
|
||||
while ((chunk_size - total_bytes_read) >= 8
|
||||
&& total_bytes_read < chunk_size // seems redundant, but since the above is unsigned math, we won't get negative values
|
||||
&& (read_riff_chunk(reader, &chunk, &bytes_read) == READ_OK))
|
||||
{
|
||||
total_bytes_read += bytes_read;
|
||||
printf_riff_chunk(&chunk, indent);
|
||||
if (chunk.id == 'TSIL')
|
||||
{
|
||||
bytes_read = ParseLIST(reader, &chunk, indent+1);
|
||||
if (!bytes_read)
|
||||
return 0;
|
||||
total_bytes_read += bytes_read;
|
||||
}
|
||||
else if (chunk.id == '1xdi')
|
||||
{
|
||||
nsavi::IDX1 *index = (nsavi::IDX1 *)malloc(chunk.size + sizeof(uint32_t));
|
||||
if (index)
|
||||
{
|
||||
reader->Read(((uint8_t *)index) + sizeof(uint32_t), chunk.size, &bytes_read);
|
||||
if (bytes_read != chunk.size)
|
||||
return 0;
|
||||
total_bytes_read+=bytes_read;
|
||||
index->index_count = chunk.size / sizeof(IDX1_INDEX);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (skip_chunk(reader, &chunk, &bytes_read) != READ_OK)
|
||||
return 0;
|
||||
total_bytes_read += bytes_read;
|
||||
}
|
||||
|
||||
}
|
||||
if (chunk_size & 1)
|
||||
reader->Skip(1);
|
||||
|
||||
return total_bytes_read;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
AVIReaderFILE reader(L"//o2d2/ftp/usr/nullsoft/test media/20bit/Track 1.wav");
|
||||
riff_chunk chunk;
|
||||
while (read_riff_chunk(&reader, &chunk) == READ_OK)
|
||||
{
|
||||
printf_riff_chunk(&chunk, 0);
|
||||
if (chunk.id == 'FFIR')
|
||||
ParseRIFF(&reader, chunk.size, 1);
|
||||
else
|
||||
skip_chunk(&reader, &chunk);
|
||||
}
|
||||
|
||||
}
|
314
Src/nsavi/metadata.cpp
Normal file
314
Src/nsavi/metadata.cpp
Normal file
@ -0,0 +1,314 @@
|
||||
#include "Metadata.h"
|
||||
|
||||
nsavi::Metadata::Metadata(nsavi::avi_reader *_reader) : ParserBase(_reader)
|
||||
{
|
||||
info_found = NOT_READ;
|
||||
info = 0;
|
||||
}
|
||||
|
||||
|
||||
// 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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
int nsavi::Metadata::GetDuration(int *time_ms)
|
||||
{
|
||||
uint32_t riff_type;
|
||||
int ret = GetRIFFType(&riff_type);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (riff_type != ' IVA')
|
||||
return READ_INVALID_DATA;
|
||||
|
||||
nsavi::HeaderList header_list;
|
||||
ret = GetHeaderList(&header_list);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
int duration=-1;
|
||||
for (uint32_t i=0;i!=header_list.stream_list_size;i++)
|
||||
{
|
||||
const nsavi::STRL &stream = header_list.stream_list[i];
|
||||
if (stream.stream_header)
|
||||
{
|
||||
if (stream.stream_header->stream_type == nsavi::stream_type_audio)
|
||||
{
|
||||
if (stream.stream_header->length && !stream.stream_header->sample_size && stream.stream_header->rate)
|
||||
duration = (uint64_t)stream.stream_header->length * (uint64_t)stream.stream_header->scale * 1000ULL / (uint64_t)stream.stream_header->rate;
|
||||
}
|
||||
else if (stream.stream_header->stream_type == nsavi::stream_type_video && stream.stream_header->rate)
|
||||
{
|
||||
if (duration == -1)
|
||||
duration = (uint64_t)stream.stream_header->length * (uint64_t)stream.stream_header->scale * 1000ULL / (uint64_t)stream.stream_header->rate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*time_ms = duration;
|
||||
|
||||
return nsavi::READ_OK;
|
||||
}
|
||||
|
||||
int nsavi::Metadata::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;
|
||||
}
|
||||
|
||||
info_found = PARSED;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = SkipChunk(reader, &chunk, header_list_parsed, &bytes_read);
|
||||
if (ret)
|
||||
return ret;
|
||||
bytes_available -= bytes_read;
|
||||
}
|
||||
break;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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::Metadata::GetInfo(nsavi::Info **out_info)
|
||||
{
|
||||
if (riff_parsed != PARSED)
|
||||
return READ_INVALID_CALL;
|
||||
|
||||
if (riff_parsed == PARSE_RESYNC)
|
||||
reader->Seek(riff_start);
|
||||
|
||||
if (info_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 (bytes_available)
|
||||
{
|
||||
if (bytes_available < 8)
|
||||
{
|
||||
info_found = NOT_FOUND;
|
||||
return READ_NOT_FOUND;
|
||||
}
|
||||
uint32_t bytes_read;
|
||||
riff_chunk chunk;
|
||||
int ret = ReadChunk(reader, &chunk, info_found, &bytes_read);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
bytes_available -= bytes_read;
|
||||
if (bytes_available < chunk.size)
|
||||
{
|
||||
info_found = PARSE_ERROR;
|
||||
return READ_INVALID_DATA;
|
||||
}
|
||||
switch(chunk.id)
|
||||
{
|
||||
case 'TSIL': // list chunk
|
||||
switch(chunk.type)
|
||||
{
|
||||
case 'lrdh': // parse this if we havn't already
|
||||
if (header_list_parsed != PARSED)
|
||||
{
|
||||
ret = ParseHeaderList(chunk.size, &bytes_read);
|
||||
if (ret)
|
||||
return ret;
|
||||
bytes_available -= bytes_read;
|
||||
header_list_parsed = PARSED;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = SkipChunk(reader, &chunk, info_found, &bytes_read);
|
||||
if (ret)
|
||||
return ret;
|
||||
bytes_available -= bytes_read;
|
||||
}
|
||||
break;
|
||||
case 'OFNI': // INFO
|
||||
if (!info)
|
||||
{
|
||||
info = new nsavi::Info();
|
||||
if (!info)
|
||||
{
|
||||
info_found = PARSE_ERROR;
|
||||
return READ_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
ret = info->Read(reader, chunk.size);
|
||||
if (ret)
|
||||
{
|
||||
header_list_parsed = PARSE_ERROR;
|
||||
return ret;
|
||||
}
|
||||
|
||||
info_found = PARSED;
|
||||
*out_info = info;
|
||||
return nsavi::READ_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = SkipChunk(reader, &chunk, info_found, &bytes_read);
|
||||
if (ret)
|
||||
return ret;
|
||||
bytes_available -= bytes_read;
|
||||
}
|
||||
break;
|
||||
default: // skip anything we don't understand
|
||||
ret = SkipChunk(reader, &chunk, info_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, &chunk, info_found, &bytes_read);
|
||||
if (ret)
|
||||
return ret;
|
||||
bytes_available -= bytes_read;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (info_found == PARSED)
|
||||
{
|
||||
*out_info = info;
|
||||
return READ_OK;
|
||||
}
|
||||
|
||||
return READ_INVALID_CALL;
|
||||
}
|
24
Src/nsavi/metadata.h
Normal file
24
Src/nsavi/metadata.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
/* this parser is meant for retrieving metadata */
|
||||
#include "read.h"
|
||||
#include "avi_header.h"
|
||||
#include "avi_reader.h"
|
||||
#include "info.h"
|
||||
#include "ParserBase.h"
|
||||
|
||||
namespace nsavi
|
||||
{
|
||||
class Metadata : public ParserBase
|
||||
{
|
||||
public:
|
||||
Metadata(nsavi::avi_reader *_reader);
|
||||
int GetDuration(int *time_ms);
|
||||
int GetHeaderList(HeaderList *header_list);
|
||||
int GetInfo(Info **info);
|
||||
|
||||
private:
|
||||
/* INFO */
|
||||
Info *info;
|
||||
ParseState info_found;
|
||||
};
|
||||
};
|
10
Src/nsavi/nsavi.h
Normal file
10
Src/nsavi/nsavi.h
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include "read.h"
|
||||
#include "info.h"
|
||||
#include "avi_header.h"
|
||||
#include "avi_reader.h"
|
||||
#include "parserbase.h"
|
||||
#include "demuxer.h"
|
||||
#include "metadata.h"
|
||||
#include "duration.h"
|
||||
#include "seektable.h"
|
20
Src/nsavi/nsavi.sln
Normal file
20
Src/nsavi/nsavi.sln
Normal file
@ -0,0 +1,20 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 10.00
|
||||
# Visual Studio 2008
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nsavi", "nsavi.vcproj", "{975419D3-CCAD-4E57-8096-1A01AB2C147D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Win32 = Debug|Win32
|
||||
Release|Win32 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{975419D3-CCAD-4E57-8096-1A01AB2C147D}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{975419D3-CCAD-4E57-8096-1A01AB2C147D}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{975419D3-CCAD-4E57-8096-1A01AB2C147D}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{975419D3-CCAD-4E57-8096-1A01AB2C147D}.Release|Win32.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
212
Src/nsavi/nsavi.vcxproj
Normal file
212
Src/nsavi/nsavi.vcxproj
Normal file
@ -0,0 +1,212 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>17.0</VCProjectVersion>
|
||||
<ProjectGuid>{975419D3-CCAD-4E57-8096-1A01AB2C147D}</ProjectGuid>
|
||||
<RootNamespace>nsavi</RootNamespace>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<_ProjectFileVersion>17.0.32505.173</_ProjectFileVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
|
||||
<IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<IncludePath>$(IncludePath)</IncludePath>
|
||||
<LibraryPath>$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<IncludePath>$(IncludePath)</IncludePath>
|
||||
<LibraryPath>$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
|
||||
<IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<IncludePath>$(IncludePath)</IncludePath>
|
||||
<LibraryPath>$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<IncludePath>$(IncludePath)</IncludePath>
|
||||
<LibraryPath>$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<AdditionalIncludeDirectories>../Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
<PrecompiledHeader />
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<TargetMachine>MachineX86</TargetMachine>
|
||||
<ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
|
||||
<ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<AdditionalIncludeDirectories>../Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
|
||||
<ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<AdditionalIncludeDirectories>../Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<PrecompiledHeader />
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<DebugInformationFormat>None</DebugInformationFormat>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<TargetMachine>MachineX86</TargetMachine>
|
||||
<ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<AdditionalIncludeDirectories>../Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<DebugInformationFormat>None</DebugInformationFormat>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="avi_header.h" />
|
||||
<ClInclude Include="avi_reader.h" />
|
||||
<ClInclude Include="file_avi_reader.h" />
|
||||
<ClInclude Include="info.h" />
|
||||
<ClInclude Include="read.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="file_avi_reader.cpp" />
|
||||
<ClCompile Include="info.cpp" />
|
||||
<ClCompile Include="main.cpp" />
|
||||
<ClCompile Include="read.cpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
48
Src/nsavi/nsavi.vcxproj.filters
Normal file
48
Src/nsavi/nsavi.vcxproj.filters
Normal file
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="avi_reader.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="avi_header.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="file_avi_reader.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="info.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="read.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="file_avi_reader.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="info.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="main.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="read.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
53
Src/nsavi/read.cpp
Normal file
53
Src/nsavi/read.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
#include "read.h"
|
||||
|
||||
|
||||
/* helper macro to do a read, check error code and number of bytes read, and return from the function if necessary
|
||||
has to be a macro because of the return */
|
||||
|
||||
#define NSAVI_READ(reader, buffer, size, bytes_read) { int ret = reader->Read(buffer, size, &bytes_read); if (ret) return ret; if (bytes_read != size) return READ_EOF; }
|
||||
|
||||
int nsavi::read_riff_chunk(nsavi::avi_reader *reader, nsavi::riff_chunk *chunk, uint32_t *out_bytes_read)
|
||||
{
|
||||
uint32_t total_bytes_read=0;
|
||||
uint32_t bytes_read;
|
||||
NSAVI_READ(reader, chunk, 8, bytes_read); // read id and size
|
||||
total_bytes_read += bytes_read;
|
||||
|
||||
if (chunk->id == 'FFIR' || chunk->id == 'TSIL')
|
||||
{
|
||||
if (chunk->size < 4)
|
||||
return READ_INVALID_DATA;
|
||||
NSAVI_READ(reader, &chunk->type, 4, bytes_read);
|
||||
total_bytes_read += bytes_read;
|
||||
chunk->size -= 4;
|
||||
}
|
||||
else
|
||||
chunk->type = 0;
|
||||
|
||||
if (out_bytes_read)
|
||||
*out_bytes_read = total_bytes_read;
|
||||
return READ_OK;
|
||||
}
|
||||
|
||||
// we pass riff_chunk instead of size
|
||||
// to avoid any confusion about who is responsible for adding the padding byte
|
||||
// (this function is responsible)
|
||||
int nsavi::skip_chunk(nsavi::avi_reader *reader, const nsavi::riff_chunk *chunk, uint32_t *out_bytes_read)
|
||||
{
|
||||
uint32_t chunk_size = chunk->size;
|
||||
if (chunk_size & 1)
|
||||
{
|
||||
chunk_size ++; // odd chunk sizes must be padded by one
|
||||
if (chunk_size == 0) // check for overflow
|
||||
return READ_INVALID_DATA;
|
||||
}
|
||||
|
||||
int ret = reader->Skip(chunk_size);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (out_bytes_read)
|
||||
*out_bytes_read = chunk_size;
|
||||
return READ_OK;
|
||||
}
|
||||
|
38
Src/nsavi/read.h
Normal file
38
Src/nsavi/read.h
Normal file
@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
#include "bfc/platform/types.h"
|
||||
#include "avi_reader.h"
|
||||
|
||||
|
||||
namespace nsavi
|
||||
{
|
||||
class avi_reader;
|
||||
#pragma pack(push, 4)
|
||||
struct riff_chunk
|
||||
{
|
||||
uint32_t id;
|
||||
uint32_t size;
|
||||
uint32_t type; // if id is LIST or RIFF, this will be set
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
|
||||
enum ParseState
|
||||
{
|
||||
NOT_READ = 0,
|
||||
PARSED = 1,
|
||||
NOT_FOUND = 2,
|
||||
PARSE_ERROR = 3,
|
||||
FOUND = 4, // we know where it is, but we havn't read it
|
||||
PARSE_RESYNC = 5, // read was aborted (return code < 0). need to resync inside the avi_reader
|
||||
|
||||
};
|
||||
|
||||
#define nsaviFOURCC( ch0, ch1, ch2, ch3 ) ((uint32_t)(uint8_t)(ch0) | ((uint32_t)(uint8_t)(ch1) << 8 ) | ((uint32_t)(uint8_t)(ch2) << 16 ) | ( (uint32_t)(uint8_t)(ch3) << 24 ))
|
||||
|
||||
// negative return codes are 'pass-thru' from the the avi_reader object
|
||||
// interpret accordingly (e.g. in_avi might abort a long network i/o on stop or seek)
|
||||
int read_riff_chunk(nsavi::avi_reader *reader, riff_chunk *chunk, uint32_t *bytes_read=0);
|
||||
int skip_chunk(nsavi::avi_reader *reader, const nsavi::riff_chunk *chunk, uint32_t *out_bytes_read=0);
|
||||
|
||||
|
||||
}
|
177
Src/nsavi/seektable.cpp
Normal file
177
Src/nsavi/seektable.cpp
Normal file
@ -0,0 +1,177 @@
|
||||
#include "seektable.h"
|
||||
#include "demuxer.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::SeekTable::SeekTable(int stream_number, bool require_keyframes, const nsavi::HeaderList *header_list)
|
||||
: header_list(header_list), stream_number(stream_number), require_keyframes(require_keyframes), super_index(0), indices_processed(0), super_index_duration(0)
|
||||
{
|
||||
stream = header_list->stream_list[stream_number].stream_header;
|
||||
const nsavi::INDX *index = header_list->stream_list[stream_number].stream_index;
|
||||
if (index)
|
||||
{
|
||||
if (index->index_type == nsavi::indx_type_master)
|
||||
{
|
||||
super_index = (nsavi::AVISUPERINDEX *)index;
|
||||
}
|
||||
else if (index->index_type == nsavi::indx_type_chunk)
|
||||
{
|
||||
AddIndex(index, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nsavi::SeekTable::~SeekTable()
|
||||
{
|
||||
}
|
||||
|
||||
const nsavi::SeekEntry *nsavi::SeekTable::GetSeekPoint(int ×tamp_ms, int current_ms, int seek_direction)
|
||||
{
|
||||
int last_time = 0;
|
||||
const nsavi::SeekEntry *last_entry = 0;
|
||||
|
||||
// TODO: binary search
|
||||
for (SeekEntries::iterator itr=seek_entries.begin();itr!=seek_entries.end();itr++)
|
||||
{
|
||||
if (itr->first > timestamp_ms)
|
||||
{
|
||||
if (seek_direction == SEEK_FORWARD && current_ms >= last_time)
|
||||
{
|
||||
itr++;
|
||||
if (itr != seek_entries.end())
|
||||
{
|
||||
last_entry = &itr->second;
|
||||
last_time = itr->first;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
timestamp_ms = last_time;
|
||||
return last_entry;
|
||||
|
||||
}
|
||||
last_entry = &itr->second;
|
||||
last_time = itr->first;
|
||||
|
||||
}
|
||||
if (seek_direction == SEEK_FORWARD && current_ms >= last_time)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
timestamp_ms = last_time;
|
||||
return last_entry;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void nsavi::SeekTable::AddIndex(const IDX1 *index)
|
||||
{
|
||||
// TODO: calculate total bytes at the same time, so we can get a more accurate bitrate
|
||||
data_processed[index] = true;
|
||||
uint64_t total_time = 0;
|
||||
uint64_t stream_rate = (uint64_t)stream->rate;
|
||||
|
||||
//if (!require_keyframes)
|
||||
// seek_entries.reserve(seek_entries.size() + index->index_count);
|
||||
|
||||
for (uint32_t i=0;i!=index->index_count;i++)
|
||||
{
|
||||
const nsavi::IDX1_INDEX &this_index = index->indices[i];
|
||||
int this_stream_number = GetStreamNumber(this_index.chunk_id);
|
||||
if (this_stream_number == stream_number && (!require_keyframes || this_index.flags & nsavi::idx1_flags_keyframe))
|
||||
{
|
||||
int timestamp = (int)(total_time * 1000ULL / stream_rate);
|
||||
SeekEntry &entry = seek_entries[timestamp];
|
||||
#ifdef SEEK_TABLE_STORE_CHUNK_HEADER
|
||||
entry.chunk_id = this_index.chunk_id;
|
||||
entry.chunk_size = this_index.chunk_size;
|
||||
#endif
|
||||
entry.file_position = this_index.offset;
|
||||
entry.stream_time = total_time;
|
||||
entry.timestamp = timestamp;
|
||||
entry.absolute = false;
|
||||
}
|
||||
if (this_stream_number == stream_number && !(this_index.flags & nsavi::idx1_flags_no_duration))
|
||||
{
|
||||
if (stream->sample_size)
|
||||
{
|
||||
uint64_t samples = this_index.chunk_size / stream->sample_size;
|
||||
total_time += stream->scale * samples;
|
||||
}
|
||||
else
|
||||
total_time += stream->scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void nsavi::SeekTable::AddIndex(const INDX *index, uint64_t start_time)
|
||||
{
|
||||
if (index->index_type == nsavi::indx_type_chunk)
|
||||
{
|
||||
const nsavi::AVISTDINDEX *chunk_index = (nsavi::AVISTDINDEX *)index;
|
||||
|
||||
//if (!require_keyframes)
|
||||
// seek_entries.reserve(seek_entries.size() + chunk_index->indx.number_of_entries);
|
||||
|
||||
|
||||
start_time *= stream->scale;
|
||||
|
||||
uint64_t stream_rate = (uint64_t)stream->rate;
|
||||
for (uint32_t i=0;i!=chunk_index->indx.number_of_entries;i++)
|
||||
{
|
||||
const INDX_CHUNK_ENTRY &this_index = chunk_index->entries[i];
|
||||
if (!require_keyframes || !(this_index.size & 0x80000000))
|
||||
{
|
||||
int timestamp = (int)(start_time * 1000ULL / stream_rate);
|
||||
SeekEntry &entry = seek_entries[timestamp];
|
||||
#ifdef SEEK_TABLE_STORE_CHUNK_HEADER
|
||||
entry.chunk_id = chunk_index->indx.chunk_id;
|
||||
entry.chunk_size = this_index.size & ~0x80000000;
|
||||
#endif
|
||||
entry.file_position = chunk_index->base_offset + this_index.offset;
|
||||
entry.stream_time = start_time;
|
||||
entry.timestamp = timestamp;
|
||||
entry.absolute = true;
|
||||
}
|
||||
|
||||
if (stream->sample_size)
|
||||
{
|
||||
uint64_t samples = this_index.size / stream->sample_size;
|
||||
start_time += stream->scale * samples;
|
||||
}
|
||||
else
|
||||
start_time += stream->scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool nsavi::SeekTable::GetIndexLocation(int timestamp, uint64_t *position, uint64_t *start_time)
|
||||
{
|
||||
// TODO: use timestamp more effectively
|
||||
if (super_index)
|
||||
{
|
||||
for (uint32_t i=indices_processed;i!=super_index->indx.number_of_entries;i++)
|
||||
{
|
||||
indices_processed++;
|
||||
*start_time = super_index_duration;
|
||||
super_index_duration += super_index->entries[i].duration;
|
||||
*position = super_index->entries[i].offset;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
55
Src/nsavi/seektable.h
Normal file
55
Src/nsavi/seektable.h
Normal file
@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
#include <map>
|
||||
#include "../nsavi/avi_header.h"
|
||||
|
||||
//#define SEEK_TABLE_STORE_CHUNK_HEADER
|
||||
namespace nsavi
|
||||
{
|
||||
struct HeaderList;
|
||||
struct SeekEntry
|
||||
{
|
||||
SeekEntry(int dummy=0) : timestamp(0), absolute(false) {}
|
||||
int timestamp; // in miliseconds
|
||||
bool absolute;
|
||||
uint64_t file_position = 0;
|
||||
#ifdef SEEK_TABLE_STORE_CHUNK_HEADER
|
||||
uint32_t chunk_id; // chunk ID at file_position, used to verify seeking
|
||||
uint32_t chunk_size; // chunk size at file_position, used to verify seeking
|
||||
#endif
|
||||
uint64_t stream_time = 0; // actually allocated to header_list->stream_list_size
|
||||
};
|
||||
|
||||
class SeekTable
|
||||
{
|
||||
public:
|
||||
SeekTable(int stream_number, bool require_keyframes, const nsavi::HeaderList *header_list);
|
||||
~SeekTable();
|
||||
enum
|
||||
{
|
||||
SEEK_WHATEVER=0,
|
||||
SEEK_FORWARD=1,
|
||||
SEEK_BACKWARD=2,
|
||||
};
|
||||
const SeekEntry *GetSeekPoint(int ×tamp_ms, int current_ms=0, int seek_direction=SEEK_WHATEVER);
|
||||
void AddIndex(const IDX1 *index);
|
||||
void AddIndex(const INDX *index, uint64_t start_time);
|
||||
|
||||
// returns a recommend place to go hunting for an idx1, indx or ix## chunk
|
||||
// usually based on indx super chunks already found in header_list
|
||||
bool GetIndexLocation(int timestamp, uint64_t *position, uint64_t *start_time);
|
||||
|
||||
typedef std::map<int, SeekEntry> SeekEntries; // mapped to timestamp in milliseconds
|
||||
SeekEntries seek_entries;
|
||||
std::map<const void *, bool> data_processed; // TODO: make nu::Set
|
||||
const nsavi::HeaderList *header_list;
|
||||
int stream_number; // which stream number is this Seek Table associated with
|
||||
bool require_keyframes; // whether or not the master stream requires keyframes
|
||||
const nsavi::STRH *stream;
|
||||
const nsavi::AVISUPERINDEX *super_index;
|
||||
uint32_t indices_processed;
|
||||
uint64_t super_index_duration;
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
Reference in New Issue
Block a user