/** libsmacker - A C library for decoding .smk Smacker Video files Copyright (C) 2012-2021 Greg Kennedy See smacker.h for more information. smacker.c Main implementation file of libsmacker. Open, close, query, render, advance and seek an smk */ #include "smacker.h" #include "smk_malloc.h" #include #include #include #include /* ************************************************************************* */ /* BITSTREAM Structure */ /* ************************************************************************* */ /* Wraps a block of memory and adds functions to read 1 or 8 bits at a time */ struct smk_bit_t { const unsigned char * buffer, * end; unsigned int bit_num; }; /* ************************************************************************* */ /* BITSTREAM Functions */ /* ************************************************************************* */ /** Initialize a bitstream wrapper */ static void smk_bs_init(struct smk_bit_t * const bs, const unsigned char * const b, const size_t size) { /* null check */ assert(bs); assert(b); /* set up the pointer to bitstream start and end, and set the bit pointer to 0 */ bs->buffer = b; bs->end = b + size; bs->bit_num = 0; } /* Reads a bit Returns -1 if error encountered */ static int smk_bs_read_1(struct smk_bit_t * const bs) { int ret; /* null check */ assert(bs); /* don't die when running out of bits, but signal */ if (bs->buffer >= bs->end) { fputs("libsmacker::smk_bs_read_1(): ERROR: bitstream exhausted.\n", stderr); return -1; } /* get next bit and store for return */ ret = (*bs->buffer >> bs->bit_num) & 1; /* advance to next bit */ if (bs->bit_num >= 7) { /* Out of bits in this byte: next! */ bs->buffer ++; bs->bit_num = 0; } else bs->bit_num ++; return ret; } /* Reads a byte Returns -1 if error. */ static int smk_bs_read_8(struct smk_bit_t * const bs) { /* null check */ assert(bs); /* don't die when running out of bits, but signal */ if (bs->buffer + (bs->bit_num > 0) >= bs->end) { fputs("libsmacker::smk_bs_read_8(): ERROR: bitstream exhausted.\n", stderr); return -1; } if (bs->bit_num) { /* unaligned read */ int ret = *bs->buffer >> bs->bit_num; bs->buffer ++; return ret | (*bs->buffer << (8 - bs->bit_num) & 0xFF); } /* aligned read */ return *bs->buffer++; } /* ************************************************************************* */ /* HUFF8 Structure */ /* ************************************************************************* */ #define SMK_HUFF8_BRANCH 0x8000 #define SMK_HUFF8_LEAF_MASK 0x7FFF struct smk_huff8_t { /* Unfortunately, smk files do not store the alloc size of a small tree. 511 entries is the pessimistic case (N codes and N-1 branches, with N=256 for 8 bits) */ size_t size; unsigned short tree[511]; }; /* ************************************************************************* */ /* HUFF8 Functions */ /* ************************************************************************* */ /* Recursive sub-func for building a tree into an array. */ static int _smk_huff8_build_rec(struct smk_huff8_t * const t, struct smk_bit_t * const bs) { int bit, value; assert(t); assert(bs); /* Make sure we aren't running out of bounds */ if (t->size >= 511) { fputs("libsmacker::_smk_huff8_build_rec() - ERROR: size exceeded\n", stderr); return 0; } /* Read the next bit */ if ((bit = smk_bs_read_1(bs)) < 0) { fputs("libsmacker::_smk_huff8_build_rec() - ERROR: get_bit returned -1\n", stderr); return 0; } if (bit) { /* Bit set: this forms a Branch node. what we have to do is build the left-hand branch, assign the "jump" address, then build the right hand branch from there. */ /* track the current index */ value = t->size ++; /* go build the left branch */ if (! _smk_huff8_build_rec(t, bs)) { fputs("libsmacker::_smk_huff8_build_rec() - ERROR: failed to build left sub-tree\n", stderr); return 0; } /* now go back to our current location, and mark our location as a "jump" */ t->tree[value] = SMK_HUFF8_BRANCH | t->size; /* continue building the right side */ if (! _smk_huff8_build_rec(t, bs)) { fputs("libsmacker::_smk_huff8_build_rec() - ERROR: failed to build right sub-tree\n", stderr); return 0; } } else { /* Bit unset signifies a Leaf node. */ /* Attempt to read value */ if ((value = smk_bs_read_8(bs)) < 0) { fputs("libsmacker::_smk_huff8_build_rec() - ERROR: get_byte returned -1\n", stderr); return 0; } /* store to tree */ t->tree[t->size ++] = value; } return 1; } /** Build an 8-bit Hufftree out of a Bitstream. */ static int smk_huff8_build(struct smk_huff8_t * const t, struct smk_bit_t * const bs) { int bit; /* null check */ assert(t); assert(bs); /* Smacker huff trees begin with a set-bit. */ if ((bit = smk_bs_read_1(bs)) < 0) { fputs("libsmacker::smk_huff8_build() - ERROR: initial get_bit returned -1\n", stderr); return 0; } /* OK to fill out the struct now */ t->size = 0; /* First bit indicates whether a tree is present or not. */ /* Very small or audio-only files may have no tree. */ if (bit) { if (! _smk_huff8_build_rec(t, bs)) { fputs("libsmacker::smk_huff8_build() - ERROR: tree build failed\n", stderr); return 0; } } else t->tree[0] = 0; /* huff trees end with an unset-bit */ if ((bit = smk_bs_read_1(bs)) < 0) { fputs("libsmacker::smk_huff8_build() - ERROR: final get_bit returned -1\n", stderr); return 0; } /* a 0 is expected here, a 1 generally indicates a problem! */ if (bit) { fputs("libsmacker::smk_huff8_build() - ERROR: final get_bit returned 1\n", stderr); return 0; } return 1; } /* Look up an 8-bit value from a basic huff tree. Return -1 on error. */ static int smk_huff8_lookup(const struct smk_huff8_t * const t, struct smk_bit_t * const bs) { int bit, index = 0; /* null check */ assert(t); assert(bs); while (t->tree[index] & SMK_HUFF8_BRANCH) { if ((bit = smk_bs_read_1(bs)) < 0) { fputs("libsmacker::smk_huff8_lookup() - ERROR: get_bit returned -1\n", stderr); return -1; } if (bit) { /* take the right branch */ index = t->tree[index] & SMK_HUFF8_LEAF_MASK; } else { /* take the left branch */ index ++; } } /* at leaf node. return the value at this point. */ return t->tree[index]; } /* ************************************************************************* */ /* HUFF16 Structure */ /* ************************************************************************* */ #define SMK_HUFF16_BRANCH 0x80000000 #define SMK_HUFF16_CACHE 0x40000000 #define SMK_HUFF16_LEAF_MASK 0x3FFFFFFF struct smk_huff16_t { unsigned int * tree; size_t size; /* recently-used values cache */ unsigned short cache[3]; }; /* ************************************************************************* */ /* HUFF16 Functions */ /* ************************************************************************* */ /* Recursive sub-func for building a tree into an array. */ static int _smk_huff16_build_rec(struct smk_huff16_t * const t, struct smk_bit_t * const bs, const struct smk_huff8_t * const low8, const struct smk_huff8_t * const hi8, const size_t limit) { int bit, value; assert(t); assert(bs); assert(low8); assert(hi8); /* Make sure we aren't running out of bounds */ if (t->size >= limit) { fputs("libsmacker::_smk_huff16_build_rec() - ERROR: size exceeded\n", stderr); return 0; } /* Read the first bit */ if ((bit = smk_bs_read_1(bs)) < 0) { fputs("libsmacker::_smk_huff16_build_rec() - ERROR: get_bit returned -1\n", stderr); return 0; } if (bit) { /* See tree-in-array explanation for HUFF8 above */ /* track the current index */ value = t->size ++; /* go build the left branch */ if (! _smk_huff16_build_rec(t, bs, low8, hi8, limit)) { fputs("libsmacker::_smk_huff16_build_rec() - ERROR: failed to build left sub-tree\n", stderr); return 0; } /* now go back to our current location, and mark our location as a "jump" */ t->tree[value] = SMK_HUFF16_BRANCH | t->size; /* continue building the right side */ if (! _smk_huff16_build_rec(t, bs, low8, hi8, limit)) { fputs("libsmacker::_smk_huff16_build_rec() - ERROR: failed to build right sub-tree\n", stderr); return 0; } } else { /* Bit unset signifies a Leaf node. */ /* Attempt to read LOW value */ if ((value = smk_huff8_lookup(low8, bs)) < 0) { fputs("libsmacker::_smk_huff16_build_rec() - ERROR: get LOW value returned -1\n", stderr); return 0; } t->tree[t->size] = value; /* now read HIGH value */ if ((value = smk_huff8_lookup(hi8, bs)) < 0) { fputs("libsmacker::_smk_huff16_build_rec() - ERROR: get HIGH value returned -1\n", stderr); return 0; } /* Looks OK: we got low and hi values. Return a new LEAF */ t->tree[t->size] |= (value << 8); /* Last: when building the tree, some Values may correspond to cache positions. Identify these values and set the Escape code byte accordingly. */ if (t->tree[t->size] == t->cache[0]) t->tree[t->size] = SMK_HUFF16_CACHE; else if (t->tree[t->size] == t->cache[1]) t->tree[t->size] = SMK_HUFF16_CACHE | 1; else if (t->tree[t->size] == t->cache[2]) t->tree[t->size] = SMK_HUFF16_CACHE | 2; t->size ++; } return 1; } /* Entry point for building a big 16-bit tree. */ static int smk_huff16_build(struct smk_huff16_t * const t, struct smk_bit_t * const bs, const unsigned int alloc_size) { struct smk_huff8_t low8, hi8; size_t limit; int value, i, bit; /* null check */ assert(t); assert(bs); /* Smacker huff trees begin with a set-bit. */ if ((bit = smk_bs_read_1(bs)) < 0) { fputs("libsmacker::smk_huff16_build() - ERROR: initial get_bit returned -1\n", stderr); return 0; } t->size = 0; /* First bit indicates whether a tree is present or not. */ /* Very small or audio-only files may have no tree. */ if (bit) { /* build low-8-bits tree */ if (! smk_huff8_build(&low8, bs)) { fputs("libsmacker::smk_huff16_build() - ERROR: failed to build LOW tree\n", stderr); return 0; } /* build hi-8-bits tree */ if (! smk_huff8_build(&hi8, bs)) { fputs("libsmacker::smk_huff16_build() - ERROR: failed to build HIGH tree\n", stderr); return 0; } /* Init the escape code cache. */ for (i = 0; i < 3; i ++) { if ((value = smk_bs_read_8(bs)) < 0) { fprintf(stderr, "libsmacker::smk_huff16_build() - ERROR: get LOW value for cache %d returned -1\n", i); return 0; } t->cache[i] = value; /* now read HIGH value */ if ((value = smk_bs_read_8(bs)) < 0) { fprintf(stderr, "libsmacker::smk_huff16_build() - ERROR: get HIGH value for cache %d returned -1\n", i); return 0; } t->cache[i] |= (value << 8); } /* Everything looks OK so far. Time to malloc structure. */ if (alloc_size < 12 || alloc_size % 4) { fprintf(stderr, "libsmacker::smk_huff16_build() - ERROR: illegal value %u for alloc_size\n", alloc_size); return 0; } limit = (alloc_size - 12) / 4; if ((t->tree = malloc(limit * sizeof(unsigned int))) == NULL) { perror("libsmacker::smk_huff16_build() - ERROR: failed to malloc() huff16 tree"); return 0; } /* Finally, call recursive function to retrieve the Bigtree. */ if (! _smk_huff16_build_rec(t, bs, &low8, &hi8, limit)) { fputs("libsmacker::smk_huff16_build() - ERROR: failed to build huff16 tree\n", stderr); free(t->tree); t->tree = NULL; return 0; } /* check that we completely filled the tree */ if (limit != t->size) { fputs("libsmacker::smk_huff16_build() - ERROR: failed to completely decode huff16 tree\n", stderr); free(t->tree); t->tree = NULL; return 0; } } else { if ((t->tree = malloc(sizeof(unsigned int))) == NULL) { perror("libsmacker::smk_huff16_build() - ERROR: failed to malloc() huff16 tree"); return 0; } t->tree[0] = 0; //t->cache[0] = t->cache[1] = t->cache[2] = 0; } /* Check final end tag. */ if ((bit = smk_bs_read_1(bs)) < 0) { fputs("libsmacker::smk_huff16_build() - ERROR: final get_bit returned -1\n", stderr); free(t->tree); t->tree = NULL; return 0; } /* a 0 is expected here, a 1 generally indicates a problem! */ if (bit) { fputs("libsmacker::smk_huff16_build() - ERROR: final get_bit returned 1\n", stderr); free(t->tree); t->tree = NULL; return 0; } return 1; } /* Look up a 16-bit value from a large huff tree. Return -1 on error. Note that this also updates the recently-used-values cache. */ static int smk_huff16_lookup(struct smk_huff16_t * const t, struct smk_bit_t * const bs) { int bit, value, index = 0; /* null check */ assert(t); assert(bs); while (t->tree[index] & SMK_HUFF16_BRANCH) { if ((bit = smk_bs_read_1(bs)) < 0) { fputs("libsmacker::smk_huff16_lookup() - ERROR: get_bit returned -1\n", stderr); return -1; } if (bit) { /* take the right branch */ index = t->tree[index] & SMK_HUFF16_LEAF_MASK; } else { /* take the left branch */ index ++; } } /* Get the value at this point */ value = t->tree[index]; if (value & SMK_HUFF16_CACHE) { /* uses cached value instead of actual value */ value = t->cache[value & SMK_HUFF16_LEAF_MASK]; } if (t->cache[0] != value) { /* Update the cache, by moving val to the front of the queue, if it isn't already there. */ t->cache[2] = t->cache[1]; t->cache[1] = t->cache[0]; t->cache[0] = value; } return value; } /* ************************************************************************* */ /* SMACKER Structure */ /* ************************************************************************* */ /* tree processing order */ #define SMK_TREE_MMAP 0 #define SMK_TREE_MCLR 1 #define SMK_TREE_FULL 2 #define SMK_TREE_TYPE 3 struct smk_t { /* meta-info */ /* file mode: see flags, smacker.h */ unsigned char mode; /* microsec per frame - stored as a double to handle scaling (large positive millisec / frame values may overflow a ul) */ double usf; /* total frames */ unsigned long f; /* does file have a ring frame? (in other words, does file loop?) */ unsigned char ring_frame; /* Index of current frame */ unsigned long cur_frame; /* SOURCE union. Where the data is going to be read from (or be stored), depending on the file mode. */ union { struct { /* on-disk mode */ FILE * fp; unsigned long * chunk_offset; } file; /* in-memory mode: unprocessed chunks */ unsigned char ** chunk_data; } source; /* shared array of "chunk sizes"*/ unsigned long * chunk_size; /* Holds per-frame flags (i.e. 'keyframe') */ unsigned char * keyframe; /* Holds per-frame type mask (e.g. 'audio track 3, 2, and palette swap') */ unsigned char * frame_type; /* video and audio structures */ /* Video data type: enable/disable decode switch, video info and flags, pointer to last-decoded-palette */ struct smk_video_t { /* enable/disable decode switch */ unsigned char enable; /* video info */ unsigned long w; unsigned long h; /* Y scale mode (constants defined in smacker.h) 0: unscaled 1: doubled 2: interlaced */ unsigned char y_scale_mode; /* version ('2' or '4') */ unsigned char v; /* Huffman trees */ unsigned long tree_size[4]; struct smk_huff16_t tree[4]; /* Palette data type: pointer to last-decoded-palette */ unsigned char palette[256][3]; /* Last-unpacked frame */ unsigned char * frame; } video; /* audio structure */ struct smk_audio_t { /* set if track exists in file */ unsigned char exists; /* enable/disable switch (per track) */ unsigned char enable; /* Info */ unsigned char channels; unsigned char bitdepth; unsigned long rate; long max_buffer; /* compression type 0: raw PCM 1: SMK DPCM 2: Bink (Perceptual), unsupported */ unsigned char compress; /* pointer to last-decoded-audio-buffer */ void * buffer; unsigned long buffer_size; } audio[7]; }; union smk_read_t { FILE * file; unsigned char * ram; }; /* ************************************************************************* */ /* SMACKER Functions */ /* ************************************************************************* */ /* An fread wrapper: consumes N bytes, or returns -1 on failure (when size doesn't match expected) */ static char smk_read_file(void * buf, const size_t size, FILE * fp) { /* don't bother checking buf or fp, fread does it for us */ size_t bytesRead = fread(buf, 1, size, fp); if (bytesRead != size) { fprintf(stderr, "libsmacker::smk_read_file(buf,%lu,fp) - ERROR: Short read, %lu bytes returned\n", (unsigned long)size, (unsigned long)bytesRead); perror("\tReason"); return -1; } return 0; } /* A memcpy wrapper: consumes N bytes, or returns -1 on failure (when size too low) */ static char smk_read_memory(void * buf, const unsigned long size, unsigned char ** p, unsigned long * p_size) { if (size > *p_size) { fprintf(stderr, "libsmacker::smk_read_memory(buf,%lu,p,%lu) - ERROR: Short read\n", (unsigned long)size, (unsigned long)*p_size); return -1; } memcpy(buf, *p, size); *p += size; *p_size -= size; return 0; } /* Helper functions to do the reading, plus byteswap from LE to host order */ /* read n bytes from (source) into ret */ #define smk_read(ret,n) \ { \ if (m) \ { \ r = (smk_read_file(ret,n,fp.file)); \ } \ else \ { \ r = (smk_read_memory(ret,n,&fp.ram,&size)); \ } \ if (r < 0) \ { \ fprintf(stderr,"libsmacker::smk_read(...) - Errors encountered on read, bailing out (file: %s, line: %lu)\n", __FILE__, (unsigned long)__LINE__); \ goto error; \ } \ } /* Calls smk_read, but returns a ul */ #define smk_read_ul(p) \ { \ smk_read(buf,4); \ p = ((unsigned long) buf[3] << 24) | \ ((unsigned long) buf[2] << 16) | \ ((unsigned long) buf[1] << 8) | \ ((unsigned long) buf[0]); \ } /* PUBLIC FUNCTIONS */ /* open an smk (from a generic Source) */ static smk smk_open_generic(const unsigned char m, union smk_read_t fp, unsigned long size, const unsigned char process_mode) { /* Smacker structure we intend to work on / return */ smk s; /* Temporary variables */ long temp_l; unsigned long temp_u; /* r is used by macros above for return code */ char r; unsigned char buf[4] = {'\0'}; /* video hufftrees are stored as a large chunk (bitstream) these vars are used to load, then decode them */ unsigned char * hufftree_chunk = NULL; unsigned long tree_size; /* a bitstream struct */ struct smk_bit_t bs; /** **/ /* safe malloc the structure */ if ((s = calloc(1, sizeof(struct smk_t))) == NULL) { perror("libsmacker::smk_open_generic() - ERROR: failed to malloc() smk structure"); return NULL; } /* Check for a valid signature */ smk_read(buf, 3); if (buf[0] != 'S' || buf[1] != 'M' || buf[2] != 'K') { fprintf(stderr, "libsmacker::smk_open_generic - ERROR: invalid SMKn signature (got: %s)\n", buf); goto error; } /* Read .smk file version */ smk_read(&s->video.v, 1); if (s->video.v != '2' && s->video.v != '4') { fprintf(stderr, "libsmacker::smk_open_generic - Warning: invalid SMK version %c (expected: 2 or 4)\n", s->video.v); /* take a guess */ if (s->video.v < '4') s->video.v = '2'; else s->video.v = '4'; fprintf(stderr, "\tProcessing will continue as type %c\n", s->video.v); } /* width, height, total num frames */ smk_read_ul(s->video.w); smk_read_ul(s->video.h); smk_read_ul(s->f); /* frames per second calculation */ smk_read_ul(temp_u); temp_l = (int)temp_u; if (temp_l > 0) { /* millisec per frame */ s->usf = temp_l * 1000; } else if (temp_l < 0) { /* 10 microsec per frame */ s->usf = temp_l * -10; } else { /* defaults to 10 usf (= 100000 microseconds) */ s->usf = 100000; } /* Video flags follow. Ring frame is important to libsmacker. Y scale / Y interlace go in the Video flags. The user should scale appropriately. */ smk_read_ul(temp_u); if (temp_u & 0x01) s->ring_frame = 1; if (temp_u & 0x02) s->video.y_scale_mode = SMK_FLAG_Y_DOUBLE; if (temp_u & 0x04) { if (s->video.y_scale_mode == SMK_FLAG_Y_DOUBLE) fputs("libsmacker::smk_open_generic - Warning: SMK file specifies both Y-Double AND Y-Interlace.\n", stderr); s->video.y_scale_mode = SMK_FLAG_Y_INTERLACE; } /* Max buffer size for each audio track - used to pre-allocate buffers */ for (temp_l = 0; temp_l < 7; temp_l ++) smk_read_ul(s->audio[temp_l].max_buffer); /* Read size of "hufftree chunk" - save for later. */ smk_read_ul(tree_size); /* "unpacked" sizes of each huff tree */ for (temp_l = 0; temp_l < 4; temp_l ++) smk_read_ul(s->video.tree_size[temp_l]); /* read audio rate data */ for (temp_l = 0; temp_l < 7; temp_l ++) { smk_read_ul(temp_u); if (temp_u & 0x40000000) { /* Audio track specifies "exists" flag, malloc structure and copy components. */ s->audio[temp_l].exists = 1; /* and for all audio tracks */ smk_malloc(s->audio[temp_l].buffer, s->audio[temp_l].max_buffer); if (temp_u & 0x80000000) s->audio[temp_l].compress = 1; s->audio[temp_l].bitdepth = ((temp_u & 0x20000000) ? 16 : 8); s->audio[temp_l].channels = ((temp_u & 0x10000000) ? 2 : 1); if (temp_u & 0x0c000000) { fprintf(stderr, "libsmacker::smk_open_generic - Warning: audio track %ld is compressed with Bink (perceptual) Audio Codec: this is currently unsupported by libsmacker\n", temp_l); s->audio[temp_l].compress = 2; } /* Bits 25 & 24 are unused. */ s->audio[temp_l].rate = (temp_u & 0x00FFFFFF); } } /* Skip over Dummy field */ smk_read_ul(temp_u); /* FrameSizes and Keyframe marker are stored together. */ smk_malloc(s->keyframe, (s->f + s->ring_frame)); smk_malloc(s->chunk_size, (s->f + s->ring_frame) * sizeof(unsigned long)); for (temp_u = 0; temp_u < (s->f + s->ring_frame); temp_u ++) { smk_read_ul(s->chunk_size[temp_u]); /* Set Keyframe */ if (s->chunk_size[temp_u] & 0x01) s->keyframe[temp_u] = 1; /* Bits 1 is used, but the purpose is unknown. */ s->chunk_size[temp_u] &= 0xFFFFFFFC; } /* That was easy... Now read FrameTypes! */ smk_malloc(s->frame_type, (s->f + s->ring_frame)); for (temp_u = 0; temp_u < (s->f + s->ring_frame); temp_u ++) smk_read(&s->frame_type[temp_u], 1); /* HuffmanTrees We know the sizes already: read and assemble into something actually parse-able at run-time */ smk_malloc(hufftree_chunk, tree_size); smk_read(hufftree_chunk, tree_size); /* set up a Bitstream */ smk_bs_init(&bs, hufftree_chunk, tree_size); /* create some tables */ for (temp_u = 0; temp_u < 4; temp_u ++) { if (! smk_huff16_build(&s->video.tree[temp_u], &bs, s->video.tree_size[temp_u])) { fprintf(stderr, "libsmacker::smk_open_generic - ERROR: failed to create huff16 tree %lu\n", temp_u); goto error; } } /* clean up */ smk_free(hufftree_chunk); /* Go ahead and malloc storage for the video frame */ smk_malloc(s->video.frame, s->video.w * s->video.h); /* final processing: depending on ProcessMode, handle what to do with rest of file data */ s->mode = process_mode; /* Handle the rest of the data. For MODE_MEMORY, read the chunks and store */ if (s->mode == SMK_MODE_MEMORY) { smk_malloc(s->source.chunk_data, (s->f + s->ring_frame) * sizeof(unsigned char *)); for (temp_u = 0; temp_u < (s->f + s->ring_frame); temp_u ++) { smk_malloc(s->source.chunk_data[temp_u], s->chunk_size[temp_u]); smk_read(s->source.chunk_data[temp_u], s->chunk_size[temp_u]); } } else { /* MODE_STREAM: don't read anything now, just precompute offsets. use fseek to verify that the file is "complete" */ smk_malloc(s->source.file.chunk_offset, (s->f + s->ring_frame) * sizeof(unsigned long)); for (temp_u = 0; temp_u < (s->f + s->ring_frame); temp_u ++) { s->source.file.chunk_offset[temp_u] = ftell(fp.file); if (fseek(fp.file, s->chunk_size[temp_u], SEEK_CUR)) { fprintf(stderr, "libsmacker::smk_open - ERROR: fseek to frame %lu not OK.\n", temp_u); perror("\tError reported was"); goto error; } } } return s; error: smk_free(hufftree_chunk); smk_close(s); return NULL; } /* open an smk (from a memory buffer) */ smk smk_open_memory(const unsigned char * buffer, const unsigned long size) { smk s = NULL; union smk_read_t fp; if (buffer == NULL) { fputs("libsmacker::smk_open_memory() - ERROR: buffer pointer is NULL\n", stderr); return NULL; } /* set up the read union for Memory mode */ fp.ram = (unsigned char *)buffer; if (!(s = smk_open_generic(0, fp, size, SMK_MODE_MEMORY))) fprintf(stderr, "libsmacker::smk_open_memory(buffer,%lu) - ERROR: Fatal error in smk_open_generic, returning NULL.\n", size); return s; } /* open an smk (from a file) */ smk smk_open_filepointer(FILE * file, const unsigned char mode) { smk s = NULL; union smk_read_t fp; if (file == NULL) { fputs("libsmacker::smk_open_filepointer() - ERROR: file pointer is NULL\n", stderr); return NULL; } /* Copy file ptr to internal union */ fp.file = file; if (!(s = smk_open_generic(1, fp, 0, mode))) { fprintf(stderr, "libsmacker::smk_open_filepointer(file,%u) - ERROR: Fatal error in smk_open_generic, returning NULL.\n", mode); fclose(fp.file); goto error; } if (mode == SMK_MODE_MEMORY) fclose(fp.file); else s->source.file.fp = fp.file; /* fall through, return s or null */ error: return s; } /* open an smk (from a file) */ smk smk_open_file(const char * filename, const unsigned char mode) { FILE * fp; if (filename == NULL) { fputs("libsmacker::smk_open_file() - ERROR: filename is NULL\n", stderr); return NULL; } if (!(fp = fopen(filename, "rb"))) { fprintf(stderr, "libsmacker::smk_open_file(%s,%u) - ERROR: could not open file\n", filename, mode); perror("\tError reported was"); goto error; } /* kick processing to smk_open_filepointer */ return smk_open_filepointer(fp, mode); /* fall through, return s or null */ error: return NULL; } /* close out an smk file and clean up memory */ void smk_close(smk s) { unsigned long u; if (s == NULL) { fputs("libsmacker::smk_close() - ERROR: smk is NULL\n", stderr); return; } /* free video sub-components */ for (u = 0; u < 4; u ++) { if (s->video.tree[u].tree) free(s->video.tree[u].tree); } smk_free(s->video.frame); /* free audio sub-components */ for (u = 0; u < 7; u++) { if (s->audio[u].buffer) smk_free(s->audio[u].buffer); } smk_free(s->keyframe); smk_free(s->frame_type); if (s->mode == SMK_MODE_DISK) { /* disk-mode */ if (s->source.file.fp) fclose(s->source.file.fp); smk_free(s->source.file.chunk_offset); } else { /* mem-mode */ if (s->source.chunk_data != NULL) { for (u = 0; u < (s->f + s->ring_frame); u++) smk_free(s->source.chunk_data[u]); smk_free(s->source.chunk_data); } } smk_free(s->chunk_size); smk_free(s); } /* tell some info about the file */ char smk_info_all(const smk object, unsigned long * frame, unsigned long * frame_count, double * usf) { /* null check */ if (object == NULL) { fputs("libsmacker::smk_info_all() - ERROR: smk is NULL\n", stderr); return -1; } if (!frame && !frame_count && !usf) { fputs("libsmacker::smk_info_all(object,frame,frame_count,usf) - ERROR: Request for info with all-NULL return references\n", stderr); goto error; } if (frame) *frame = (object->cur_frame % object->f); if (frame_count) *frame_count = object->f; if (usf) *usf = object->usf; return 0; error: return -1; } char smk_info_video(const smk object, unsigned long * w, unsigned long * h, unsigned char * y_scale_mode) { /* null check */ if (object == NULL) { fputs("libsmacker::smk_info_video() - ERROR: smk is NULL\n", stderr); return -1; } if (!w && !h && !y_scale_mode) { fputs("libsmacker::smk_info_all(object,w,h,y_scale_mode) - ERROR: Request for info with all-NULL return references\n", stderr); return -1; } if (w) *w = object->video.w; if (h) *h = object->video.h; if (y_scale_mode) *y_scale_mode = object->video.y_scale_mode; return 0; } char smk_info_audio(const smk object, unsigned char * track_mask, unsigned char channels[7], unsigned char bitdepth[7], unsigned long audio_rate[7]) { unsigned char i; /* null check */ if (object == NULL) { fputs("libsmacker::smk_info_audio() - ERROR: smk is NULL\n", stderr); return -1; } if (!track_mask && !channels && !bitdepth && !audio_rate) { fputs("libsmacker::smk_info_audio(object,track_mask,channels,bitdepth,audio_rate) - ERROR: Request for info with all-NULL return references\n", stderr); return -1; } if (track_mask) { *track_mask = ((object->audio[0].exists) | ((object->audio[1].exists) << 1) | ((object->audio[2].exists) << 2) | ((object->audio[3].exists) << 3) | ((object->audio[4].exists) << 4) | ((object->audio[5].exists) << 5) | ((object->audio[6].exists) << 6)); } if (channels) { for (i = 0; i < 7; i ++) channels[i] = object->audio[i].channels; } if (bitdepth) { for (i = 0; i < 7; i ++) bitdepth[i] = object->audio[i].bitdepth; } if (audio_rate) { for (i = 0; i < 7; i ++) audio_rate[i] = object->audio[i].rate; } return 0; } /* Enable-disable switches */ char smk_enable_all(smk object, const unsigned char mask) { unsigned char i; /* null check */ if (object == NULL) { fputs("libsmacker::smk_enable_all() - ERROR: smk is NULL\n", stderr); return -1; } /* set video-enable */ object->video.enable = (mask & 0x80); for (i = 0; i < 7; i ++) { if (object->audio[i].exists) object->audio[i].enable = (mask & (1 << i)); } return 0; } char smk_enable_video(smk object, const unsigned char enable) { /* null check */ if (object == NULL) { fputs("libsmacker::smk_enable_video() - ERROR: smk is NULL\n", stderr); return -1; } object->video.enable = enable; return 0; } char smk_enable_audio(smk object, const unsigned char track, const unsigned char enable) { /* null check */ if (object == NULL) { fputs("libsmacker::smk_enable_audio() - ERROR: smk is NULL\n", stderr); return -1; } object->audio[track].enable = enable; return 0; } const unsigned char * smk_get_palette(const smk object) { /* null check */ if (object == NULL) { fputs("libsmacker::smk_get_palette() - ERROR: smk is NULL\n", stderr); return NULL; } return (unsigned char *)object->video.palette; } const unsigned char * smk_get_video(const smk object) { /* null check */ if (object == NULL) { fputs("libsmacker::smk_get_video() - ERROR: smk is NULL\n", stderr); return NULL; } return object->video.frame; } const unsigned char * smk_get_audio(const smk object, const unsigned char t) { /* null check */ if (object == NULL) { fputs("libsmacker::smk_get_audio() - ERROR: smk is NULL\n", stderr); return NULL; } return object->audio[t].buffer; } unsigned long smk_get_audio_size(const smk object, const unsigned char t) { /* null check */ if (object == NULL) { fputs("libsmacker::smk_get_audio_size() - ERROR: smk is NULL\n", stderr); return 0; } return object->audio[t].buffer_size; } /* Decompresses a palette-frame. */ static char smk_render_palette(struct smk_video_t * s, unsigned char * p, unsigned long size) { /* Index into palette */ unsigned short i = 0; /* Helper variables */ unsigned short count, src; static unsigned char oldPalette[256][3]; /* Smacker palette map: smk colors are 6-bit, this table expands them to 8. */ const unsigned char palmap[64] = { 0x00, 0x04, 0x08, 0x0C, 0x10, 0x14, 0x18, 0x1C, 0x20, 0x24, 0x28, 0x2C, 0x30, 0x34, 0x38, 0x3C, 0x41, 0x45, 0x49, 0x4D, 0x51, 0x55, 0x59, 0x5D, 0x61, 0x65, 0x69, 0x6D, 0x71, 0x75, 0x79, 0x7D, 0x82, 0x86, 0x8A, 0x8E, 0x92, 0x96, 0x9A, 0x9E, 0xA2, 0xA6, 0xAA, 0xAE, 0xB2, 0xB6, 0xBA, 0xBE, 0xC3, 0xC7, 0xCB, 0xCF, 0xD3, 0xD7, 0xDB, 0xDF, 0xE3, 0xE7, 0xEB, 0xEF, 0xF3, 0xF7, 0xFB, 0xFF }; /* null check */ assert(s); assert(p); /* Copy palette to old palette */ memcpy(oldPalette, s->palette, 256 * 3); /* Loop until palette is complete, or we are out of bytes to process */ while ((i < 256) && (size > 0)) { if ((*p) & 0x80) { /* 0x80: Skip block (preserve C+1 palette entries from previous palette) */ count = ((*p) & 0x7F) + 1; p ++; size --; /* check for overflow condition */ if (i + count > 256) { fprintf(stderr, "libsmacker::palette_render(s,p,size) - ERROR: overflow, 0x80 attempt to skip %d entries from %d\n", count, i); goto error; } /* finally: advance the index. */ i += count; } else if ((*p) & 0x40) { /* 0x40: Color-shift block Copy (c + 1) color entries of the previous palette, starting from entry (s), to the next entries of the new palette. */ if (size < 2) { fputs("libsmacker::palette_render(s,p,size) - ERROR: 0x40 ran out of bytes for copy\n", stderr); goto error; } /* pick "count" items to copy */ count = ((*p) & 0x3F) + 1; p ++; size --; /* start offset of old palette */ src = *p; p ++; size --; /* overflow: see if we write/read beyond 256colors, or overwrite own palette */ if (i + count > 256 || src + count > 256) { fprintf(stderr, "libsmacker::palette_render(s,p,size) - ERROR: overflow, 0x40 attempt to copy %d entries from %d to %d\n", count, src, i); goto error; } /* OK! Copy the color-palette entries. */ memmove(&s->palette[i][0], &oldPalette[src][0], count * 3); i += count; } else { /* 0x00: Set Color block Direct-set the next 3 bytes for palette index */ if (size < 3) { fprintf(stderr, "libsmacker::palette_render - ERROR: 0x3F ran out of bytes for copy, size=%lu\n", size); goto error; } for (count = 0; count < 3; count ++) { if (*p > 0x3F) { fprintf(stderr, "libsmacker::palette_render - ERROR: palette index exceeds 0x3F (entry [%u][%u])\n", i, count); goto error; } s->palette[i][count] = palmap[*p]; p++; size --; } i ++; } } if (i < 256) { fprintf(stderr, "libsmacker::palette_render - ERROR: did not completely fill palette (idx=%u)\n", i); goto error; } return 0; error: /* Error, return -1 The new palette probably has errors but is preferrable to a black screen */ return -1; } static char smk_render_video(struct smk_video_t * s, unsigned char * p, unsigned int size) { unsigned char * t = s->frame; unsigned char s1, s2; unsigned short temp; unsigned long i, j, k, row, col, skip; /* used for video decoding */ struct smk_bit_t bs; /* results from a tree lookup */ int unpack; /* unpack, broken into pieces */ unsigned char type; unsigned char blocklen; unsigned char typedata; char bit; const unsigned short sizetable[64] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 128, 256, 512, 1024, 2048 }; /* null check */ assert(s); assert(p); row = 0; col = 0; /* Set up a bitstream for video unpacking */ smk_bs_init(&bs, p, size); /* Reset the cache on all bigtrees */ for (i = 0; i < 4; i++) memset(&s->tree[i].cache, 0, 3 * sizeof(unsigned short)); while (row < s->h) { if ((unpack = smk_huff16_lookup(&s->tree[SMK_TREE_TYPE], &bs)) < 0) { fputs("libsmacker::smk_render_video() - ERROR: failed to lookup from TYPE tree.\n", stderr); return -1; } type = ((unpack & 0x0003)); blocklen = ((unpack & 0x00FC) >> 2); typedata = ((unpack & 0xFF00) >> 8); /* support for v4 full-blocks */ if (type == 1 && s->v == '4') { bit = smk_bs_read_1(&bs); if (bit) type = 4; else { bit = smk_bs_read_1(&bs); if (bit) type = 5; } } for (j = 0; (j < sizetable[blocklen]) && (row < s->h); j ++) { skip = (row * s->w) + col; switch (type) { case 0: if ((unpack = smk_huff16_lookup(&s->tree[SMK_TREE_MCLR], &bs)) < 0) { fputs("libsmacker::smk_render_video() - ERROR: failed to lookup from MCLR tree.\n", stderr); return -1; } s1 = (unpack & 0xFF00) >> 8; s2 = (unpack & 0x00FF); if ((unpack = smk_huff16_lookup(&s->tree[SMK_TREE_MMAP], &bs)) < 0) { fputs("libsmacker::smk_render_video() - ERROR: failed to lookup from MMAP tree.\n", stderr); return -1; } temp = 0x01; for (k = 0; k < 4; k ++) { for (i = 0; i < 4; i ++) { if (unpack & temp) t[skip + i] = s1; else t[skip + i] = s2; temp = temp << 1; } skip += s->w; } break; case 1: /* FULL BLOCK */ for (k = 0; k < 4; k ++) { if ((unpack = smk_huff16_lookup(&s->tree[SMK_TREE_FULL], &bs)) < 0) { fputs("libsmacker::smk_render_video() - ERROR: failed to lookup from FULL tree.\n", stderr); return -1; } t[skip + 3] = ((unpack & 0xFF00) >> 8); t[skip + 2] = (unpack & 0x00FF); if ((unpack = smk_huff16_lookup(&s->tree[SMK_TREE_FULL], &bs)) < 0) { fputs("libsmacker::smk_render_video() - ERROR: failed to lookup from FULL tree.\n", stderr); return -1; } t[skip + 1] = ((unpack & 0xFF00) >> 8); t[skip] = (unpack & 0x00FF); skip += s->w; } break; case 2: /* VOID BLOCK */ /* break; if (s->frame) { memcpy(&t[skip], &s->frame[skip], 4); skip += s->w; memcpy(&t[skip], &s->frame[skip], 4); skip += s->w; memcpy(&t[skip], &s->frame[skip], 4); skip += s->w; memcpy(&t[skip], &s->frame[skip], 4); } */ break; case 3: /* SOLID BLOCK */ memset(&t[skip], typedata, 4); skip += s->w; memset(&t[skip], typedata, 4); skip += s->w; memset(&t[skip], typedata, 4); skip += s->w; memset(&t[skip], typedata, 4); break; case 4: /* V4 DOUBLE BLOCK */ for (k = 0; k < 2; k ++) { if ((unpack = smk_huff16_lookup(&s->tree[SMK_TREE_FULL], &bs)) < 0) { fputs("libsmacker::smk_render_video() - ERROR: failed to lookup from FULL tree.\n", stderr); return -1; } for (i = 0; i < 2; i ++) { memset(&t[skip + 2], (unpack & 0xFF00) >> 8, 2); memset(&t[skip], (unpack & 0x00FF), 2); skip += s->w; } } break; case 5: /* V4 HALF BLOCK */ for (k = 0; k < 2; k ++) { if ((unpack = smk_huff16_lookup(&s->tree[SMK_TREE_FULL], &bs)) < 0) { fputs("libsmacker::smk_render_video() - ERROR: failed to lookup from FULL tree.\n", stderr); return -1; } t[skip + 3] = ((unpack & 0xFF00) >> 8); t[skip + 2] = (unpack & 0x00FF); t[skip + s->w + 3] = ((unpack & 0xFF00) >> 8); t[skip + s->w + 2] = (unpack & 0x00FF); if ((unpack = smk_huff16_lookup(&s->tree[SMK_TREE_FULL], &bs)) < 0) { fputs("libsmacker::smk_render_video() - ERROR: failed to lookup from FULL tree.\n", stderr); return -1; } t[skip + 1] = ((unpack & 0xFF00) >> 8); t[skip] = (unpack & 0x00FF); t[skip + s->w + 1] = ((unpack & 0xFF00) >> 8); t[skip + s->w] = (unpack & 0x00FF); skip += (s->w << 1); } break; } col += 4; if (col >= s->w) { col = 0; row += 4; } } } return 0; } /* Decompress audio track i. */ static char smk_render_audio(struct smk_audio_t * s, unsigned char * p, unsigned long size) { unsigned int j, k; unsigned char * t = s->buffer; struct smk_bit_t bs; char bit; short unpack, unpack2; /* used for audio decoding */ struct smk_huff8_t aud_tree[4]; /* null check */ assert(s); assert(p); if (!s->compress) { /* Raw PCM data, update buffer size and perform copy */ s->buffer_size = size; memcpy(t, p, size); } else if (s->compress == 1) { /* SMACKER DPCM compression */ /* need at least 4 bytes to process */ if (size < 4) { fputs("libsmacker::smk_render_audio() - ERROR: need 4 bytes to get unpacked output buffer size.\n", stderr); goto error; } /* chunk is compressed (huff-compressed dpcm), retrieve unpacked buffer size */ s->buffer_size = ((unsigned int) p[3] << 24) | ((unsigned int) p[2] << 16) | ((unsigned int) p[1] << 8) | ((unsigned int) p[0]); p += 4; size -= 4; /* Compressed audio: must unpack here */ /* Set up a bitstream */ smk_bs_init(&bs, p, size); bit = smk_bs_read_1(&bs); if (!bit) { fputs("libsmacker::smk_render_audio - ERROR: initial get_bit returned 0\n", stderr); goto error; } bit = smk_bs_read_1(&bs); if (s->channels != (bit == 1 ? 2 : 1)) fputs("libsmacker::smk_render - ERROR: mono/stereo mismatch\n", stderr); bit = smk_bs_read_1(&bs); if (s->bitdepth != (bit == 1 ? 16 : 8)) fputs("libsmacker::smk_render - ERROR: 8-/16-bit mismatch\n", stderr); /* build the trees */ smk_huff8_build(&aud_tree[0], &bs); j = 1; k = 1; if (s->bitdepth == 16) { smk_huff8_build(&aud_tree[1], &bs); k = 2; } if (s->channels == 2) { smk_huff8_build(&aud_tree[2], &bs); j = 2; k = 2; if (s->bitdepth == 16) { smk_huff8_build(&aud_tree[3], &bs); k = 4; } } /* read initial sound level */ if (s->channels == 2) { unpack = smk_bs_read_8(&bs); if (s->bitdepth == 16) { ((short *)t)[1] = smk_bs_read_8(&bs); ((short *)t)[1] |= (unpack << 8); } else ((unsigned char *)t)[1] = (unsigned char)unpack; } unpack = smk_bs_read_8(&bs); if (s->bitdepth == 16) { ((short *)t)[0] = smk_bs_read_8(&bs); ((short *)t)[0] |= (unpack << 8); } else ((unsigned char *)t)[0] = (unsigned char)unpack; /* All set: let's read some DATA! */ while (k < s->buffer_size) { if (s->bitdepth == 8) { unpack = smk_huff8_lookup(&aud_tree[0], &bs); ((unsigned char *)t)[j] = (char)unpack + ((unsigned char *)t)[j - s->channels]; j ++; k++; } else { unpack = smk_huff8_lookup(&aud_tree[0], &bs); unpack2 = smk_huff8_lookup(&aud_tree[1], &bs); ((short *)t)[j] = (short)(unpack | (unpack2 << 8)) + ((short *)t)[j - s->channels]; j ++; k += 2; } if (s->channels == 2) { if (s->bitdepth == 8) { unpack = smk_huff8_lookup(&aud_tree[2], &bs); ((unsigned char *)t)[j] = (char)unpack + ((unsigned char *)t)[j - 2]; j ++; k++; } else { unpack = smk_huff8_lookup(&aud_tree[2], &bs); unpack2 = smk_huff8_lookup(&aud_tree[3], &bs); ((short *)t)[j] = (short)(unpack | (unpack2 << 8)) + ((short *)t)[j - 2]; j ++; k += 2; } } } } return 0; error: return -1; } /* "Renders" (unpacks) the frame at cur_frame Preps all the image and audio pointers */ static char smk_render(smk s) { unsigned long i, size; unsigned char * buffer = NULL, * p, track; /* null check */ assert(s); /* Retrieve current chunk_size for this frame. */ if (!(i = s->chunk_size[s->cur_frame])) { fprintf(stderr, "libsmacker::smk_render(s) - Warning: frame %lu: chunk_size is 0.\n", s->cur_frame); goto error; } if (s->mode == SMK_MODE_DISK) { /* Skip to frame in file */ if (fseek(s->source.file.fp, s->source.file.chunk_offset[s->cur_frame], SEEK_SET)) { fprintf(stderr, "libsmacker::smk_render(s) - ERROR: fseek to frame %lu (offset %lu) failed.\n", s->cur_frame, s->source.file.chunk_offset[s->cur_frame]); perror("\tError reported was"); goto error; } /* In disk-streaming mode: make way for our incoming chunk buffer */ if ((buffer = malloc(i)) == NULL) { perror("libsmacker::smk_render() - ERROR: failed to malloc() buffer"); return -1; } /* Read into buffer */ if (smk_read_file(buffer, s->chunk_size[s->cur_frame], s->source.file.fp) < 0) { fprintf(stderr, "libsmacker::smk_render(s) - ERROR: frame %lu (offset %lu): smk_read had errors.\n", s->cur_frame, s->source.file.chunk_offset[s->cur_frame]); goto error; } } else { /* Just point buffer at the right place */ if (!s->source.chunk_data[s->cur_frame]) { fprintf(stderr, "libsmacker::smk_render(s) - ERROR: frame %lu: memory chunk is a NULL pointer.\n", s->cur_frame); goto error; } buffer = s->source.chunk_data[s->cur_frame]; } p = buffer; /* Palette record first */ if (s->frame_type[s->cur_frame] & 0x01) { /* need at least 1 byte to process */ if (!i) { fprintf(stderr, "libsmacker::smk_render(s) - ERROR: frame %lu: insufficient data for a palette rec.\n", s->cur_frame); goto error; } /* Byte 1 in block, times 4, tells how many subsequent bytes are present */ size = 4 * (*p); /* If video rendering enabled, kick this off for decode. */ if (s->video.enable) smk_render_palette(&(s->video), p + 1, size - 1); p += size; i -= size; } /* Unpack audio chunks */ for (track = 0; track < 7; track ++) { if (s->frame_type[s->cur_frame] & (0x02 << track)) { /* need at least 4 byte to process */ if (i < 4) { fprintf(stderr, "libsmacker::smk_render(s) - ERROR: frame %lu: insufficient data for audio[%u] rec.\n", s->cur_frame, track); goto error; } /* First 4 bytes in block tell how many subsequent bytes are present */ size = (((unsigned int) p[3] << 24) | ((unsigned int) p[2] << 16) | ((unsigned int) p[1] << 8) | ((unsigned int) p[0])); /* If audio rendering enabled, kick this off for decode. */ if (s->audio[track].enable) smk_render_audio(&s->audio[track], p + 4, size - 4); p += size; i -= size; } else s->audio[track].buffer_size = 0; } /* Unpack video chunk */ if (s->video.enable) { if (smk_render_video(&(s->video), p, i) < 0) { fprintf(stderr, "libsmacker::smk_render(s) - ERROR: frame %lu: failed to render video.\n", s->cur_frame); goto error; } } if (s->mode == SMK_MODE_DISK) { /* Remember that buffer we allocated? Trash it */ smk_free(buffer); } return 0; error: if (s->mode == SMK_MODE_DISK) { /* Remember that buffer we allocated? Trash it */ smk_free(buffer); } return -1; } /* rewind to first frame and unpack */ char smk_first(smk s) { /* null check */ if (s == NULL) { fputs("libsmacker::smk_first() - ERROR: smk is NULL\n", stderr); return -1; } s->cur_frame = 0; if (smk_render(s) < 0) { fprintf(stderr, "libsmacker::smk_first(s) - Warning: frame %lu: smk_render returned errors.\n", s->cur_frame); return -1; } if (s->f == 1) return SMK_LAST; return SMK_MORE; } /* advance to next frame */ char smk_next(smk s) { /* null check */ if (s == NULL) { fputs("libsmacker::smk_next() - ERROR: smk is NULL\n", stderr); return -1; } if (s->cur_frame + 1 < (s->f + s->ring_frame)) { s->cur_frame ++; if (smk_render(s) < 0) { fprintf(stderr, "libsmacker::smk_next(s) - Warning: frame %lu: smk_render returned errors.\n", s->cur_frame); return -1; } if (s->cur_frame + 1 == (s->f + s->ring_frame)) return SMK_LAST; return SMK_MORE; } else if (s->ring_frame) { s->cur_frame = 1; if (smk_render(s) < 0) { fprintf(stderr, "libsmacker::smk_next(s) - Warning: frame %lu: smk_render returned errors.\n", s->cur_frame); return -1; } if (s->cur_frame + 1 == (s->f + s->ring_frame)) return SMK_LAST; return SMK_MORE; } return SMK_DONE; } /* seek to a keyframe in an smk */ char smk_seek_keyframe(smk s, unsigned long f) { /* null check */ if (s == NULL) { fputs("libsmacker::smk_seek_keyframe() - ERROR: smk is NULL\n", stderr); return -1; } /* rewind (or fast forward!) exactly to f */ s->cur_frame = f; /* roll back to previous keyframe in stream, or 0 if no keyframes exist */ while (s->cur_frame > 0 && !(s->keyframe[s->cur_frame])) s->cur_frame --; /* render the frame: we're ready */ if (smk_render(s) < 0) { fprintf(stderr, "libsmacker::smk_seek_keyframe(s,%lu) - Warning: frame %lu: smk_render returned errors.\n", f, s->cur_frame); return -1; } return 0; } unsigned char smk_palette_updated(smk s) { return s->frame_type[s->cur_frame] & 0x01; }