| // Copyright 2020 Joe Drago. All rights reserved. |
| // SPDX-License-Identifier: BSD-2-Clause |
| |
| // #define WIN32_MEMORY_LEAK_DETECTION |
| #ifdef WIN32_MEMORY_LEAK_DETECTION |
| #define _CRTDBG_MAP_ALLOC |
| #include <crtdbg.h> |
| #endif |
| |
| #include "avif/avif.h" |
| |
| #include <inttypes.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #if defined(_WIN32) |
| |
| #include <windows.h> |
| |
| typedef struct NextFilenameData |
| { |
| int didFirstFile; |
| HANDLE handle; |
| WIN32_FIND_DATA wfd; |
| } NextFilenameData; |
| |
| static const char * nextFilename(const char * parentDir, const char * extension, NextFilenameData * nfd) |
| { |
| for (;;) { |
| if (nfd->didFirstFile) { |
| if (FindNextFile(nfd->handle, &nfd->wfd) == 0) { |
| // No more files |
| break; |
| } |
| } else { |
| char filenameBuffer[2048]; |
| snprintf(filenameBuffer, sizeof(filenameBuffer), "%s\\*", parentDir); |
| filenameBuffer[sizeof(filenameBuffer) - 1] = 0; |
| nfd->handle = FindFirstFile(filenameBuffer, &nfd->wfd); |
| if (nfd->handle == INVALID_HANDLE_VALUE) { |
| return NULL; |
| } |
| nfd->didFirstFile = 1; |
| } |
| |
| // If we get here, we should have a valid wfd |
| const char * dot = strrchr(nfd->wfd.cFileName, '.'); |
| if (dot) { |
| ++dot; |
| if (!strcmp(dot, extension)) { |
| return nfd->wfd.cFileName; |
| } |
| } |
| } |
| |
| FindClose(nfd->handle); |
| nfd->handle = INVALID_HANDLE_VALUE; |
| nfd->didFirstFile = 0; |
| return NULL; |
| } |
| |
| #else |
| #include <dirent.h> |
| typedef struct NextFilenameData |
| { |
| DIR * dir; |
| } NextFilenameData; |
| |
| static const char * nextFilename(const char * parentDir, const char * extension, NextFilenameData * nfd) |
| { |
| if (!nfd->dir) { |
| nfd->dir = opendir(parentDir); |
| if (!nfd->dir) { |
| return NULL; |
| } |
| } |
| |
| struct dirent * entry; |
| while ((entry = readdir(nfd->dir)) != NULL) { |
| const char * dot = strrchr(entry->d_name, '.'); |
| if (dot) { |
| ++dot; |
| if (!strcmp(dot, extension)) { |
| return entry->d_name; |
| } |
| } |
| } |
| |
| closedir(nfd->dir); |
| nfd->dir = NULL; |
| return NULL; |
| } |
| #endif |
| |
| typedef struct avifIOTestReader |
| { |
| avifIO io; |
| avifROData rodata; |
| size_t availableBytes; |
| } avifIOTestReader; |
| |
| static avifResult avifIOTestReaderRead(struct avifIO * io, uint32_t readFlags, uint64_t offset, size_t size, avifROData * out) |
| { |
| // printf("avifIOTestReaderRead offset %" PRIu64 " size %zu\n", offset, size); |
| |
| if (readFlags != 0) { |
| // Unsupported readFlags |
| return AVIF_RESULT_IO_ERROR; |
| } |
| |
| avifIOTestReader * reader = (avifIOTestReader *)io; |
| |
| // Sanitize/clamp incoming request |
| if (offset > reader->rodata.size) { |
| // The offset is past the end of the buffer. |
| return AVIF_RESULT_IO_ERROR; |
| } |
| if (offset == reader->rodata.size) { |
| // The parser is *exactly* at EOF: return a 0-size pointer to any valid buffer |
| offset = 0; |
| size = 0; |
| } |
| uint64_t availableSize = reader->rodata.size - offset; |
| if (size > availableSize) { |
| size = (size_t)availableSize; |
| } |
| |
| if (offset > reader->availableBytes) { |
| return AVIF_RESULT_WAITING_ON_IO; |
| } |
| if (size > (reader->availableBytes - offset)) { |
| return AVIF_RESULT_WAITING_ON_IO; |
| } |
| |
| out->data = reader->rodata.data + offset; |
| out->size = size; |
| return AVIF_RESULT_OK; |
| } |
| |
| static void avifIOTestReaderDestroy(struct avifIO * io) |
| { |
| avifFree(io); |
| } |
| |
| static avifIOTestReader * avifIOCreateTestReader(const uint8_t * data, size_t size) |
| { |
| avifIOTestReader * reader = avifAlloc(sizeof(avifIOTestReader)); |
| memset(reader, 0, sizeof(avifIOTestReader)); |
| reader->io.destroy = avifIOTestReaderDestroy; |
| reader->io.read = avifIOTestReaderRead; |
| reader->io.sizeHint = size; |
| reader->io.persistent = AVIF_TRUE; |
| reader->rodata.data = data; |
| reader->rodata.size = size; |
| return reader; |
| } |
| |
| #define FILENAME_MAX_LENGTH 2047 |
| |
| static int runIOTests(const char * dataDir) |
| { |
| printf("AVIF Test Suite: Running IO Tests...\n"); |
| |
| static const char * ioSuffix = "/io/"; |
| |
| char ioDir[FILENAME_MAX_LENGTH + 1]; |
| size_t dataDirLen = strlen(dataDir); |
| size_t ioSuffixLen = strlen(ioSuffix); |
| |
| if ((dataDirLen + ioSuffixLen) > FILENAME_MAX_LENGTH) { |
| printf("Path too long: %s\n", dataDir); |
| return 1; |
| } |
| strcpy(ioDir, dataDir); |
| strcat(ioDir, ioSuffix); |
| size_t ioDirLen = strlen(ioDir); |
| |
| int retCode = 0; |
| |
| NextFilenameData nfd; |
| memset(&nfd, 0, sizeof(nfd)); |
| avifRWData fileBuffer = AVIF_DATA_EMPTY; |
| const char * filename = nextFilename(ioDir, "avif", &nfd); |
| for (; filename != NULL; filename = nextFilename(ioDir, "avif", &nfd)) { |
| char fullFilename[FILENAME_MAX_LENGTH + 1]; |
| size_t filenameLen = strlen(filename); |
| if ((ioDirLen + filenameLen) > FILENAME_MAX_LENGTH) { |
| printf("Path too long: %s\n", filename); |
| retCode = 1; |
| break; |
| } |
| strcpy(fullFilename, ioDir); |
| strcat(fullFilename, filename); |
| |
| FILE * f = fopen(fullFilename, "rb"); |
| if (!f) { |
| printf("Can't open for read: %s\n", filename); |
| retCode = 1; |
| break; |
| } |
| fseek(f, 0, SEEK_END); |
| size_t fileSize = ftell(f); |
| fseek(f, 0, SEEK_SET); |
| if (avifRWDataRealloc(&fileBuffer, fileSize) != AVIF_RESULT_OK) { |
| printf("Out of memory when allocating buffer to read file: %s\n", filename); |
| fclose(f); |
| retCode = 1; |
| break; |
| } |
| if (fread(fileBuffer.data, 1, fileSize, f) != fileSize) { |
| printf("Can't read entire file: %s\n", filename); |
| fclose(f); |
| retCode = 1; |
| break; |
| } |
| fclose(f); |
| |
| avifDecoder * decoder = avifDecoderCreate(); |
| if (decoder == NULL) { |
| printf("Memory allocation failure\n"); |
| retCode = 1; |
| break; |
| } |
| avifIOTestReader * io = avifIOCreateTestReader(fileBuffer.data, fileBuffer.size); |
| avifDecoderSetIO(decoder, (avifIO *)io); |
| |
| for (int pass = 0; pass < 4; ++pass) { |
| io->io.persistent = ((pass % 2) == 0); |
| decoder->ignoreExif = decoder->ignoreXMP = (pass < 2); |
| |
| // Slowly pretend to have streamed-in / downloaded more and more bytes |
| avifResult parseResult = AVIF_RESULT_UNKNOWN_ERROR; |
| for (io->availableBytes = 0; io->availableBytes <= io->io.sizeHint; ++io->availableBytes) { |
| parseResult = avifDecoderParse(decoder); |
| if (parseResult == AVIF_RESULT_WAITING_ON_IO) { |
| continue; |
| } |
| if (parseResult != AVIF_RESULT_OK) { |
| retCode = 1; |
| } |
| |
| printf("File: [%s @ %zu / %" PRIu64 " bytes, %s, %s] parse returned: %s\n", |
| filename, |
| io->availableBytes, |
| io->io.sizeHint, |
| io->io.persistent ? "Persistent" : "NonPersistent", |
| decoder->ignoreExif ? "IgnoreMetadata" : "Metadata", |
| avifResultToString(parseResult)); |
| break; |
| } |
| |
| if (parseResult == AVIF_RESULT_OK) { |
| for (; io->availableBytes <= io->io.sizeHint; ++io->availableBytes) { |
| avifExtent extent; |
| avifResult extentResult = avifDecoderNthImageMaxExtent(decoder, 0, &extent); |
| if (extentResult != AVIF_RESULT_OK) { |
| retCode = 1; |
| |
| printf("File: [%s @ %zu / %" PRIu64 " bytes, %s, %s] maxExtent returned: %s\n", |
| filename, |
| io->availableBytes, |
| io->io.sizeHint, |
| io->io.persistent ? "Persistent" : "NonPersistent", |
| decoder->ignoreExif ? "IgnoreMetadata" : "Metadata", |
| avifResultToString(extentResult)); |
| } else { |
| avifResult nextImageResult = avifDecoderNextImage(decoder); |
| if (nextImageResult == AVIF_RESULT_WAITING_ON_IO) { |
| continue; |
| } |
| if (nextImageResult != AVIF_RESULT_OK) { |
| retCode = 1; |
| } |
| |
| printf("File: [%s @ %zu / %" PRIu64 " bytes, %s, %s] nextImage [MaxExtent off %" PRIu64 ", size %zu] returned: %s\n", |
| filename, |
| io->availableBytes, |
| io->io.sizeHint, |
| io->io.persistent ? "Persistent" : "NonPersistent", |
| decoder->ignoreExif ? "IgnoreMetadata" : "Metadata", |
| extent.offset, |
| extent.size, |
| avifResultToString(nextImageResult)); |
| } |
| break; |
| } |
| } |
| } |
| |
| avifDecoderDestroy(decoder); |
| } |
| |
| avifRWDataFree(&fileBuffer); |
| return retCode; |
| } |
| |
| static void syntax(void) |
| { |
| fprintf(stderr, "Syntax: aviftest dataDir\n"); |
| } |
| |
| int main(int argc, char * argv[]) |
| { |
| const char * dataDir = NULL; |
| |
| #ifdef WIN32_MEMORY_LEAK_DETECTION |
| _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); |
| // _CrtSetBreakAlloc(2906); |
| #endif |
| |
| // Parse cmdline |
| for (int i = 1; i < argc; ++i) { |
| char * arg = argv[i]; |
| if (!strcmp(arg, "--io-only")) { |
| fprintf(stderr, "WARNING: --io-only is deprecated; ignoring.\n"); |
| } else if (dataDir == NULL) { |
| dataDir = arg; |
| } else { |
| fprintf(stderr, "Too many positional arguments: %s\n", arg); |
| syntax(); |
| return 1; |
| } |
| } |
| |
| // Verify all required args were set |
| if (dataDir == NULL) { |
| fprintf(stderr, "dataDir is required, bailing out.\n"); |
| syntax(); |
| return 1; |
| } |
| |
| setbuf(stdout, NULL); |
| |
| char codecVersions[256]; |
| avifCodecVersions(codecVersions); |
| printf("Codec Versions: %s\n", codecVersions); |
| printf("Test Data Dir : %s\n", dataDir); |
| |
| int retCode = runIOTests(dataDir); |
| if (retCode == 0) { |
| printf("AVIF Test Suite: Complete.\n"); |
| } else { |
| printf("AVIF Test Suite: Failed.\n"); |
| } |
| return retCode; |
| } |