diff options
Diffstat (limited to 'Src/nsavi')
-rw-r--r-- | Src/nsavi/ParserBase.cpp | 493 | ||||
-rw-r--r-- | Src/nsavi/ParserBase.h | 45 | ||||
-rw-r--r-- | Src/nsavi/avi_header.h | 248 | ||||
-rw-r--r-- | Src/nsavi/avi_reader.h | 45 | ||||
-rw-r--r-- | Src/nsavi/demuxer.cpp | 565 | ||||
-rw-r--r-- | Src/nsavi/demuxer.h | 36 | ||||
-rw-r--r-- | Src/nsavi/duration.cpp | 172 | ||||
-rw-r--r-- | Src/nsavi/duration.h | 18 | ||||
-rw-r--r-- | Src/nsavi/file_avi_reader.cpp | 43 | ||||
-rw-r--r-- | Src/nsavi/file_avi_reader.h | 24 | ||||
-rw-r--r-- | Src/nsavi/info.cpp | 68 | ||||
-rw-r--r-- | Src/nsavi/info.h | 18 | ||||
-rw-r--r-- | Src/nsavi/main.cpp | 269 | ||||
-rw-r--r-- | Src/nsavi/metadata.cpp | 314 | ||||
-rw-r--r-- | Src/nsavi/metadata.h | 24 | ||||
-rw-r--r-- | Src/nsavi/nsavi.h | 10 | ||||
-rw-r--r-- | Src/nsavi/nsavi.sln | 20 | ||||
-rw-r--r-- | Src/nsavi/nsavi.vcxproj | 212 | ||||
-rw-r--r-- | Src/nsavi/nsavi.vcxproj.filters | 48 | ||||
-rw-r--r-- | Src/nsavi/read.cpp | 53 | ||||
-rw-r--r-- | Src/nsavi/read.h | 38 | ||||
-rw-r--r-- | Src/nsavi/seektable.cpp | 177 | ||||
-rw-r--r-- | Src/nsavi/seektable.h | 55 |
23 files changed, 2995 insertions, 0 deletions
diff --git a/Src/nsavi/ParserBase.cpp b/Src/nsavi/ParserBase.cpp new file mode 100644 index 00000000..af1fb8b1 --- /dev/null +++ b/Src/nsavi/ParserBase.cpp @@ -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 +} diff --git a/Src/nsavi/ParserBase.h b/Src/nsavi/ParserBase.h new file mode 100644 index 00000000..3e01338f --- /dev/null +++ b/Src/nsavi/ParserBase.h @@ -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; + }; +}
\ No newline at end of file diff --git a/Src/nsavi/avi_header.h b/Src/nsavi/avi_header.h new file mode 100644 index 00000000..b6628b91 --- /dev/null +++ b/Src/nsavi/avi_header.h @@ -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) +}
\ No newline at end of file diff --git a/Src/nsavi/avi_reader.h b/Src/nsavi/avi_reader.h new file mode 100644 index 00000000..e44ec55d --- /dev/null +++ b/Src/nsavi/avi_reader.h @@ -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; +}; + +}
\ No newline at end of file diff --git a/Src/nsavi/demuxer.cpp b/Src/nsavi/demuxer.cpp new file mode 100644 index 00000000..b518c326 --- /dev/null +++ b/Src/nsavi/demuxer.cpp @@ -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; +} diff --git a/Src/nsavi/demuxer.h b/Src/nsavi/demuxer.h new file mode 100644 index 00000000..ceac5218 --- /dev/null +++ b/Src/nsavi/demuxer.h @@ -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; + }; +}
\ No newline at end of file diff --git a/Src/nsavi/duration.cpp b/Src/nsavi/duration.cpp new file mode 100644 index 00000000..e4105813 --- /dev/null +++ b/Src/nsavi/duration.cpp @@ -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; +} diff --git a/Src/nsavi/duration.h b/Src/nsavi/duration.h new file mode 100644 index 00000000..a92fefab --- /dev/null +++ b/Src/nsavi/duration.h @@ -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); + + }; +}; diff --git a/Src/nsavi/file_avi_reader.cpp b/Src/nsavi/file_avi_reader.cpp new file mode 100644 index 00000000..f6543a42 --- /dev/null +++ b/Src/nsavi/file_avi_reader.cpp @@ -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); + }
\ No newline at end of file diff --git a/Src/nsavi/file_avi_reader.h b/Src/nsavi/file_avi_reader.h new file mode 100644 index 00000000..cac2c56d --- /dev/null +++ b/Src/nsavi/file_avi_reader.h @@ -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; +};
\ No newline at end of file diff --git a/Src/nsavi/info.cpp b/Src/nsavi/info.cpp new file mode 100644 index 00000000..17c1c4a2 --- /dev/null +++ b/Src/nsavi/info.cpp @@ -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; +}
\ No newline at end of file diff --git a/Src/nsavi/info.h b/Src/nsavi/info.h new file mode 100644 index 00000000..98450fd2 --- /dev/null +++ b/Src/nsavi/info.h @@ -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); + }; +};
\ No newline at end of file diff --git a/Src/nsavi/main.cpp b/Src/nsavi/main.cpp new file mode 100644 index 00000000..b17c47f8 --- /dev/null +++ b/Src/nsavi/main.cpp @@ -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); + } + +}
\ No newline at end of file diff --git a/Src/nsavi/metadata.cpp b/Src/nsavi/metadata.cpp new file mode 100644 index 00000000..8ffedb56 --- /dev/null +++ b/Src/nsavi/metadata.cpp @@ -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; +}
\ No newline at end of file diff --git a/Src/nsavi/metadata.h b/Src/nsavi/metadata.h new file mode 100644 index 00000000..d9979c7a --- /dev/null +++ b/Src/nsavi/metadata.h @@ -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; + }; +}; diff --git a/Src/nsavi/nsavi.h b/Src/nsavi/nsavi.h new file mode 100644 index 00000000..9c3c106a --- /dev/null +++ b/Src/nsavi/nsavi.h @@ -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" diff --git a/Src/nsavi/nsavi.sln b/Src/nsavi/nsavi.sln new file mode 100644 index 00000000..8684a7fe --- /dev/null +++ b/Src/nsavi/nsavi.sln @@ -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 diff --git a/Src/nsavi/nsavi.vcxproj b/Src/nsavi/nsavi.vcxproj new file mode 100644 index 00000000..8a68e1c1 --- /dev/null +++ b/Src/nsavi/nsavi.vcxproj @@ -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>
\ No newline at end of file diff --git a/Src/nsavi/nsavi.vcxproj.filters b/Src/nsavi/nsavi.vcxproj.filters new file mode 100644 index 00000000..97c56903 --- /dev/null +++ b/Src/nsavi/nsavi.vcxproj.filters @@ -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>
\ No newline at end of file diff --git a/Src/nsavi/read.cpp b/Src/nsavi/read.cpp new file mode 100644 index 00000000..8790d5e0 --- /dev/null +++ b/Src/nsavi/read.cpp @@ -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; +} + diff --git a/Src/nsavi/read.h b/Src/nsavi/read.h new file mode 100644 index 00000000..25044f8b --- /dev/null +++ b/Src/nsavi/read.h @@ -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); + + +}
\ No newline at end of file diff --git a/Src/nsavi/seektable.cpp b/Src/nsavi/seektable.cpp new file mode 100644 index 00000000..494674e9 --- /dev/null +++ b/Src/nsavi/seektable.cpp @@ -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; +}
\ No newline at end of file diff --git a/Src/nsavi/seektable.h b/Src/nsavi/seektable.h new file mode 100644 index 00000000..44007fd7 --- /dev/null +++ b/Src/nsavi/seektable.h @@ -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; +}; + + + +}
\ No newline at end of file |