/** libsmacker - A C library for decoding .smk Smacker Video files Copyright (C) 2012-2017 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" /* safe malloc and free */ #include "smk_malloc.h" /* data structures */ #include "smk_bitstream.h" #include "smk_hufftree.h" /* GLOBALS */ /* tree processing order */ #define SMK_TREE_MMAP 0 #define SMK_TREE_MCLR 1 #define SMK_TREE_FULL 2 #define SMK_TREE_TYPE 3 /* SMACKER DATA STRUCTURES */ 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; }; /* 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\tReason: %s\n", (unsigned long)size, (unsigned long)bytesRead, strerror(errno)); 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) { smk s = NULL; /* 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 = NULL; /* safe malloc the structure */ smk_malloc(s, sizeof(struct smk_t)); /* 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 - we don't use but calling application might. */ for (temp_l = 0; temp_l < 4; temp_l++) { /* smk_read_ul(s->video.tree_size[temp_u]); */ smk_read_ul(temp_u); } /* 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 */ bs = smk_bs_init(hufftree_chunk, tree_size); /* create some tables */ for (temp_u = 0; temp_u < 4; temp_u++) { smk_huff16_build(bs, s->video.tree[temp_u]); } /* clean up */ smk_free(bs); 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(bs); 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; smk_assert(buffer); /* 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); } /* fall through, return s or null */ error: 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; smk_assert(file); /* 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; smk_assert(filename); if (!(fp = fopen(filename, "rb"))) { // Jeff commented out error messages // fprintf(stderr,"libsmacker::smk_open_file(%s,%u) - ERROR: could not open file (errno: %d)\n",filename,mode,errno); // 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; smk_assert(s); /* free video sub-components */ { for (u = 0; u < 4; u++) { if (s->video.tree[u]) smk_huff16_free(s->video.tree[u]); } smk_free(s->video.frame); } /* free audio sub-components */ for (u = 0; u < 7; u++) { 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); error:; } /* tell some info about the file */ char smk_info_all(const smk object, unsigned long* frame, unsigned long* frame_count, double* usf) { /* sanity check */ smk_assert(object); 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) { /* sanity check */ smk_assert(object); 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; error: return -1; } 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; /* sanity check */ smk_assert(object); 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; error: return -1; } /* Enable-disable switches */ char smk_enable_all(smk object, const unsigned char mask) { unsigned char i; /* sanity check */ smk_assert(object); /* 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; error: return -1; } char smk_enable_video(smk object, const unsigned char enable) { /* sanity check */ smk_assert(object); object->video.enable = enable; return 0; error: return -1; } char smk_enable_audio(smk object, const unsigned char track, const unsigned char enable) { /* sanity check */ smk_assert(object); object->audio[track].enable = enable; return 0; error: return -1; } const unsigned char* smk_get_palette(const smk object) { smk_assert(object); return (unsigned char*)object->video.palette; error: return NULL; } const unsigned char* smk_get_video(const smk object) { smk_assert(object); return object->video.frame; error: return NULL; } const unsigned char* smk_get_audio(const smk object, const unsigned char t) { smk_assert(object); return object->audio[t].buffer; error: return NULL; } unsigned long smk_get_audio_size(const smk object, const unsigned char t) { smk_assert(object); return object->audio[t].buffer_size; error: return 0; } /* 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 }; /* sanity check */ smk_assert(s); smk_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 || (src < i && src + count > i)) { 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 = NULL; /* results from a tree lookup */ long 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 }; /* sanity check */ smk_assert(s); smk_assert(p); row = 0; col = 0; /* Set up a bitstream for video unpacking */ /* We could check the return code but it will only fail if p is null and we already verified that. */ bs = smk_bs_init(p, size); /* Reset the cache on all bigtrees */ smk_huff16_reset(s->tree[0]); smk_huff16_reset(s->tree[1]); smk_huff16_reset(s->tree[2]); smk_huff16_reset(s->tree[3]); while (row < s->h) { smk_huff16_lookup(bs, s->tree[SMK_TREE_TYPE], unpack); type = ((unpack & 0x0003)); blocklen = ((unpack & 0x00FC) >> 2); typedata = ((unpack & 0xFF00) >> 8); /* support for v4 full-blocks */ if (type == 1 && s->v == '4') { smk_bs_read_1(bs, bit); if (bit) { type = 4; } else { smk_bs_read_1(bs, bit); if (bit) { type = 5; } } } for (j = 0; (j < sizetable[blocklen]) && (row < s->h); j++) { skip = (row * s->w) + col; switch (type) { case 0: smk_huff16_lookup(bs, s->tree[SMK_TREE_MCLR], unpack); s1 = (unpack & 0xFF00) >> 8; s2 = (unpack & 0x00FF); smk_huff16_lookup(bs, s->tree[SMK_TREE_MMAP], unpack); 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++) { smk_huff16_lookup(bs, s->tree[SMK_TREE_FULL], unpack); t[skip + 3] = ((unpack & 0xFF00) >> 8); t[skip + 2] = (unpack & 0x00FF); smk_huff16_lookup(bs, s->tree[SMK_TREE_FULL], unpack); 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++) { smk_huff16_lookup(bs, s->tree[SMK_TREE_FULL], unpack); 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++) { smk_huff16_lookup(bs, s->tree[SMK_TREE_FULL], unpack); 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); smk_huff16_lookup(bs, s->tree[SMK_TREE_FULL], unpack); 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; } } } smk_free(bs); return 0; error: smk_free(bs); return -1; } /* 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 = NULL; char bit; short unpack, unpack2; /* used for audio decoding */ struct smk_huff8_t* aud_tree[4] = { NULL, NULL, NULL, NULL }; /* sanity check */ smk_assert(s); smk_assert(p); if (!s->compress) { /* Raw PCM data, update buffer size and malloc */ 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 */ bs = smk_bs_init(p, size); smk_bs_read_1(bs, bit); if (!bit) { fputs("libsmacker::smk_render_audio - ERROR: initial get_bit returned 0\n", stderr); goto error; } smk_bs_read_1(bs, bit); if (s->channels != (bit == 1 ? 2 : 1)) { fputs("libsmacker::smk_render - ERROR: mono/stereo mismatch\n", stderr); } smk_bs_read_1(bs, bit); if (s->bitdepth != (bit == 1 ? 16 : 8)) { fputs("libsmacker::smk_render - ERROR: 8-/16-bit mismatch\n", stderr); } /* build the trees */ smk_huff8_build(bs, aud_tree[0]); j = 1; k = 1; if (s->bitdepth == 16) { smk_huff8_build(bs, aud_tree[1]); k = 2; } if (s->channels == 2) { smk_huff8_build(bs, aud_tree[2]); j = 2; k = 2; if (s->bitdepth == 16) { smk_huff8_build(bs, aud_tree[3]); k = 4; } } /* read initial sound level */ if (s->channels == 2) { smk_bs_read_8(bs, unpack); if (s->bitdepth == 16) { smk_bs_read_8(bs, ((short*)t)[1])((short*)t)[1] |= (unpack << 8); } else { ((unsigned char*)t)[1] = (unsigned char)unpack; } } smk_bs_read_8(bs, unpack); if (s->bitdepth == 16) { smk_bs_read_8(bs, ((short*)t)[0])((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) { smk_huff8_lookup(bs, aud_tree[0], unpack); ((unsigned char*)t)[j] = (char)unpack + ((unsigned char*)t)[j - s->channels]; j++; k++; } else { smk_huff8_lookup(bs, aud_tree[0], unpack); smk_huff8_lookup(bs, aud_tree[1], unpack2); ((short*)t)[j] = (short)(unpack | (unpack2 << 8)) + ((short*)t)[j - s->channels]; j++; k += 2; } if (s->channels == 2) { if (s->bitdepth == 8) { smk_huff8_lookup(bs, aud_tree[2], unpack); ((unsigned char*)t)[j] = (char)unpack + ((unsigned char*)t)[j - 2]; j++; k++; } else { smk_huff8_lookup(bs, aud_tree[2], unpack); smk_huff8_lookup(bs, aud_tree[3], unpack2); ((short*)t)[j] = (short)(unpack | (unpack2 << 8)) + ((short*)t)[j - 2]; j++; k += 2; } } } /* All done with the trees, free them. */ for (j = 0; j < 4; j++) { if (aud_tree[j]) { smk_huff8_free(aud_tree[j]); } } /* free bitstream */ smk_free(bs); } return 0; error: /* All done with the trees, free them. */ for (j = 0; j < 4; j++) { if (aud_tree[j]) { smk_huff8_free(aud_tree[j]); } } smk_free(bs); 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; /* sanity check */ smk_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 */ smk_malloc(buffer, i); /* 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) { smk_render_video(&(s->video), p, i); } 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) { smk_assert(s); 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); goto error; } if (s->f == 1) return SMK_LAST; return SMK_MORE; error: return -1; } /* advance to next frame */ char smk_next(smk s) { smk_assert(s); 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); goto error; } 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); goto error; } if (s->cur_frame + 1 == (s->f + s->ring_frame)) { return SMK_LAST; } return SMK_MORE; } return SMK_DONE; error: return -1; } /* seek to a keyframe in an smk */ char smk_seek_keyframe(smk s, unsigned long f) { smk_assert(s); /* 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); goto error; } return 0; error: return -1; }