543 lines
18 KiB
C
543 lines
18 KiB
C
////////////////////////////////////////////////////////////////////////////
|
|
// **** WAVPACK **** //
|
|
// Hybrid Lossless Wavefile Compressor //
|
|
// Copyright (c) 1998 - 2019 David Bryant. //
|
|
// All Rights Reserved. //
|
|
// Distributed under the BSD Software License (see license.txt) //
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
// wvparser.c
|
|
|
|
// This is a completely self-contained utility to parse complete Wavpack files
|
|
// into their component blocks and display some informative details about them
|
|
// (including all the "metadata" sub-blocks contained in each block). This can
|
|
// be very handy for visualizing how WavPack files are structured or debugging.
|
|
// Some sanity checking is performed, but no thorough verification is done
|
|
// (and, of course, no decoding takes place). APEv2 tags are ignored as is any
|
|
// other garbage between blocks.
|
|
|
|
// Acts as a filter: WavPack file to stdin, output to stdout.
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#ifdef _WIN32
|
|
#include <io.h>
|
|
#endif
|
|
|
|
#if defined(_MSC_VER) && _MSC_VER < 1600
|
|
typedef unsigned __int64 uint64_t;
|
|
typedef unsigned __int32 uint32_t;
|
|
typedef unsigned __int16 uint16_t;
|
|
typedef unsigned __int8 uint8_t;
|
|
typedef __int64 int64_t;
|
|
typedef __int32 int32_t;
|
|
typedef __int16 int16_t;
|
|
typedef __int8 int8_t;
|
|
#else
|
|
#include <stdint.h>
|
|
#endif
|
|
|
|
typedef int32_t (*read_stream)(void *, int32_t);
|
|
|
|
////////////////////////////// WavPack Header /////////////////////////////////
|
|
|
|
// Note that this is the ONLY structure that is written to (or read from)
|
|
// WavPack 4.0 files, and is the preamble to every block in both the .wv
|
|
// and .wvc files.
|
|
|
|
typedef struct {
|
|
char ckID [4];
|
|
uint32_t ckSize;
|
|
int16_t version;
|
|
unsigned char track_no, index_no;
|
|
uint32_t total_samples, block_index, block_samples, flags, crc;
|
|
} WavpackHeader;
|
|
|
|
#define WavpackHeaderFormat "4LS2LLLLL"
|
|
|
|
// or-values for "flags"
|
|
|
|
#define BYTES_STORED 3 // 1-4 bytes/sample
|
|
#define MONO_FLAG 4 // not stereo
|
|
#define HYBRID_FLAG 8 // hybrid mode
|
|
#define JOINT_STEREO 0x10 // joint stereo
|
|
#define CROSS_DECORR 0x20 // no-delay cross decorrelation
|
|
#define HYBRID_SHAPE 0x40 // noise shape (hybrid mode only)
|
|
#define FLOAT_DATA 0x80 // ieee 32-bit floating point data
|
|
|
|
#define INT32_DATA 0x100 // special extended int handling
|
|
#define HYBRID_BITRATE 0x200 // bitrate noise (hybrid mode only)
|
|
#define HYBRID_BALANCE 0x400 // balance noise (hybrid stereo mode only)
|
|
|
|
#define INITIAL_BLOCK 0x800 // initial block of multichannel segment
|
|
#define FINAL_BLOCK 0x1000 // final block of multichannel segment
|
|
|
|
#define SHIFT_LSB 13
|
|
#define SHIFT_MASK (0x1fL << SHIFT_LSB)
|
|
|
|
#define MAG_LSB 18
|
|
#define MAG_MASK (0x1fL << MAG_LSB)
|
|
|
|
#define SRATE_LSB 23
|
|
#define SRATE_MASK (0xfL << SRATE_LSB)
|
|
|
|
#define FALSE_STEREO 0x40000000 // block is stereo, but data is mono
|
|
#define NEW_SHAPING 0x20000000 // use IIR filter for negative shaping
|
|
|
|
#define MONO_DATA (MONO_FLAG | FALSE_STEREO)
|
|
|
|
// Introduced in WavPack 5.0:
|
|
#define HAS_CHECKSUM 0x10000000 // block contains a trailing checksum
|
|
#define DSD_FLAG 0x80000000 // block is encoded DSD (1-bit PCM)
|
|
|
|
#define IGNORED_FLAGS 0x08000000 // reserved, but ignore if encountered
|
|
#define UNKNOWN_FLAGS 0x00000000 // we no longer have any of these spares
|
|
|
|
#define MIN_STREAM_VERS 0x402 // lowest stream version we'll decode
|
|
#define MAX_STREAM_VERS 0x410 // highest stream version we'll decode or encode
|
|
|
|
static const uint32_t sample_rates [] = { 6000, 8000, 9600, 11025, 12000, 16000, 22050,
|
|
24000, 32000, 44100, 48000, 64000, 88200, 96000, 192000 };
|
|
|
|
//////////////////////////// WavPack Metadata /////////////////////////////////
|
|
|
|
// This is an internal representation of metadata.
|
|
|
|
typedef struct {
|
|
int32_t byte_length;
|
|
void *data;
|
|
unsigned char id;
|
|
} WavpackMetadata;
|
|
|
|
#define ID_UNIQUE 0x3f
|
|
#define ID_OPTIONAL_DATA 0x20
|
|
#define ID_ODD_SIZE 0x40
|
|
#define ID_LARGE 0x80
|
|
|
|
static const char *metadata_names [] = {
|
|
"DUMMY", "ENCODER_INFO", "DECORR_TERMS", "DECORR_WEIGHTS", "DECORR_SAMPLES", "ENTROPY_VARS", "HYBRID_PROFILE", "SHAPING_WEIGHTS",
|
|
"FLOAT_INFO", "INT32_INFO", "WV_BITSTREAM", "WVC_BITSTREAM", "WVX_BITSTREAM", "CHANNEL_INFO", "DSD_BLOCK", "UNASSIGNED",
|
|
"UNASSIGNED", "RIFF_HEADER", "RIFF_TRAILER", "ALT_HEADER", "ALT_TRAILER", "CONFIG_BLOCK", "MD5_CHECKSUM", "SAMPLE_RATE",
|
|
"ALT_EXTENSION", "ALT_MD5_CHECKSUM", "NEW_CONFIG", "CHANNEL_IDENTITIES", "UNASSIGNED", "UNASSIGNED", "UNASSIGNED", "BLOCK_CHECKSUM"
|
|
};
|
|
|
|
static int32_t read_bytes (void *buff, int32_t bcount);
|
|
static uint32_t read_next_header (read_stream infile, WavpackHeader *wphdr);
|
|
static void little_endian_to_native (void *data, char *format);
|
|
static void native_to_little_endian (void *data, char *format);
|
|
static void parse_wavpack_block (unsigned char *block_data);
|
|
static int verify_wavpack_block (unsigned char *buffer);
|
|
|
|
static const char *sign_on = "\n"
|
|
" WVPARSER WavPack Audio File Parser Test Filter Version 1.10\n"
|
|
" Copyright (c) 1998 - 2020 David Bryant. All Rights Reserved.\n\n";
|
|
|
|
int main (int argc, char **argv)
|
|
{
|
|
uint32_t bcount, total_bytes, sample_rate, first_sample, last_sample = -1L;
|
|
int channel_count, block_count;
|
|
char flags_list [256];
|
|
FILE *outfile = NULL;
|
|
WavpackHeader wphdr;
|
|
|
|
if (argc > 1) {
|
|
outfile = fopen (argv [1], "wb");
|
|
|
|
if (!outfile) {
|
|
fprintf (stderr, "can't open file '%s'!\n", argv [1]);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
setmode (_fileno (stdin), O_BINARY);
|
|
#endif
|
|
fprintf (stderr, "%s", sign_on);
|
|
|
|
while (1) {
|
|
|
|
// read next WavPack header
|
|
|
|
bcount = read_next_header (read_bytes, &wphdr);
|
|
|
|
if (bcount == (uint32_t) -1) {
|
|
printf ("\nend of file\n\n");
|
|
break;
|
|
}
|
|
|
|
if (bcount)
|
|
printf ("\nunknown data skipped, %u bytes\n", bcount);
|
|
|
|
if (((wphdr.flags & SRATE_MASK) >> SRATE_LSB) == 15) {
|
|
if (sample_rate != 44100)
|
|
printf ("\nwarning: unknown sample rate...using 44100 default\n");
|
|
sample_rate = 44100;
|
|
}
|
|
else {
|
|
sample_rate = sample_rates [(wphdr.flags & SRATE_MASK) >> SRATE_LSB];
|
|
|
|
if (wphdr.flags & DSD_FLAG)
|
|
sample_rate *= 4; // most common multiplier (DSD64), but could be wrong
|
|
}
|
|
|
|
// basic summary of the block
|
|
|
|
if ((wphdr.flags & INITIAL_BLOCK) || !wphdr.block_samples)
|
|
printf ("\n");
|
|
|
|
if (wphdr.block_samples) {
|
|
printf ("%s audio block, version 0x%03x, %u samples in %u bytes, time = %.2f-%.2f\n",
|
|
(wphdr.flags & MONO_FLAG) ? "mono" : "stereo", wphdr.version, wphdr.block_samples, wphdr.ckSize + 8,
|
|
(double) wphdr.block_index / sample_rate, (double) (wphdr.block_index + wphdr.block_samples - 1) / sample_rate);
|
|
|
|
// now show information from the "flags" field of the header
|
|
|
|
printf ("samples are %d bits in %d bytes, shifted %d bits, sample rate = %u Hz\n",
|
|
(int)((wphdr.flags & MAG_MASK) >> MAG_LSB) + 1,
|
|
(wphdr.flags & BYTES_STORED) + 1,
|
|
(int)(wphdr.flags & SHIFT_MASK) >> SHIFT_LSB,
|
|
sample_rate);
|
|
|
|
flags_list [0] = 0;
|
|
|
|
if (wphdr.flags) {
|
|
if (wphdr.flags & INITIAL_BLOCK) strcat (flags_list, "INITIAL ");
|
|
if (wphdr.flags & MONO_FLAG) strcat (flags_list, "MONO ");
|
|
if (wphdr.flags & DSD_FLAG) strcat (flags_list, "DSD ");
|
|
if (wphdr.flags & HYBRID_FLAG) strcat (flags_list, "HYBRID ");
|
|
if (wphdr.flags & JOINT_STEREO) strcat (flags_list, "JOINT-STEREO ");
|
|
if (wphdr.flags & CROSS_DECORR) strcat (flags_list, "CROSS-DECORR ");
|
|
if (wphdr.flags & HYBRID_SHAPE) strcat (flags_list, "NOISE-SHAPING ");
|
|
if (wphdr.flags & FLOAT_DATA) strcat (flags_list, "FLOAT ");
|
|
if (wphdr.flags & INT32_DATA) strcat (flags_list, "INT32 ");
|
|
if (wphdr.flags & HYBRID_BITRATE) strcat (flags_list, "HYBRID-BITRATE ");
|
|
if (wphdr.flags & FALSE_STEREO) strcat (flags_list, "FALSE-STEREO ");
|
|
if (wphdr.flags & NEW_SHAPING) strcat (flags_list, "NEW-SHAPING ");
|
|
if (wphdr.flags & HAS_CHECKSUM) strcat (flags_list, "CHECKSUM ");
|
|
if (wphdr.flags & (IGNORED_FLAGS | UNKNOWN_FLAGS)) strcat (flags_list, "UNKNOWN-FLAGS ");
|
|
if (wphdr.flags & FINAL_BLOCK) strcat (flags_list, "FINAL");
|
|
}
|
|
else
|
|
strcat (flags_list, "none");
|
|
|
|
printf ("flags: %s\n", flags_list);
|
|
}
|
|
else
|
|
printf ("non-audio block of %u bytes, version 0x%03x\n", wphdr.ckSize + 8, wphdr.version);
|
|
|
|
// read and parse the actual block data (which is entirely composed of "meta" blocks)
|
|
|
|
if (wphdr.ckSize > sizeof (WavpackHeader) - 8) {
|
|
uint32_t block_size = wphdr.ckSize + 8;
|
|
char *block_buff = malloc (block_size);
|
|
|
|
memcpy (block_buff, &wphdr, sizeof (WavpackHeader));
|
|
read_bytes (block_buff + sizeof (WavpackHeader), block_size - sizeof (WavpackHeader));
|
|
|
|
if (outfile) // add condition here if desired to select only certain blocks be written
|
|
fwrite (block_buff, block_size, 1, outfile);
|
|
|
|
parse_wavpack_block (block_buff);
|
|
free (block_buff);
|
|
}
|
|
|
|
// if there's audio samples in there do some other sanity checks (especially for multichannel)
|
|
|
|
if (wphdr.block_samples) {
|
|
if ((wphdr.flags & INITIAL_BLOCK) && wphdr.block_index != last_sample + 1)
|
|
printf ("error: discontinuity detected!\n");
|
|
|
|
if (!(wphdr.flags & INITIAL_BLOCK))
|
|
if (first_sample != wphdr.block_index ||
|
|
last_sample != wphdr.block_index + wphdr.block_samples - 1)
|
|
printf ("error: multichannel block mismatch detected!\n");
|
|
|
|
last_sample = (first_sample = wphdr.block_index) + wphdr.block_samples - 1;
|
|
|
|
if (wphdr.flags & INITIAL_BLOCK) {
|
|
channel_count = (wphdr.flags & MONO_FLAG) ? 1 : 2;
|
|
total_bytes = wphdr.ckSize + 8;
|
|
block_count = 1;
|
|
}
|
|
else {
|
|
channel_count += (wphdr.flags & MONO_FLAG) ? 1 : 2;
|
|
total_bytes += wphdr.ckSize + 8;
|
|
block_count++;
|
|
|
|
if (wphdr.flags & FINAL_BLOCK)
|
|
printf ("multichannel: %d channels in %d blocks, %u bytes total\n",
|
|
channel_count, block_count, total_bytes);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (outfile)
|
|
fclose (outfile);
|
|
|
|
return 0;
|
|
}
|
|
|
|
// read the next metadata block, or return 0 if there aren't any more (or an error occurs)
|
|
|
|
static int read_metadata_buff (WavpackMetadata *wpmd, unsigned char *blockbuff, unsigned char **buffptr)
|
|
{
|
|
WavpackHeader *wphdr = (WavpackHeader *) blockbuff;
|
|
unsigned char *buffend = blockbuff + wphdr->ckSize + 8;
|
|
|
|
if (buffend - *buffptr < 2)
|
|
return 0;
|
|
|
|
wpmd->id = *(*buffptr)++;
|
|
wpmd->byte_length = *(*buffptr)++ << 1;
|
|
|
|
if (wpmd->id & ID_LARGE) {
|
|
wpmd->id &= ~ID_LARGE;
|
|
|
|
if (buffend - *buffptr < 2)
|
|
return 0;
|
|
|
|
wpmd->byte_length += *(*buffptr)++ << 9;
|
|
wpmd->byte_length += *(*buffptr)++ << 17;
|
|
}
|
|
|
|
if (wpmd->id & ID_ODD_SIZE) {
|
|
if (!wpmd->byte_length) // odd size and zero length makes no sense
|
|
return 0;
|
|
wpmd->id &= ~ID_ODD_SIZE;
|
|
wpmd->byte_length--;
|
|
}
|
|
|
|
if (wpmd->byte_length) {
|
|
if (buffend - *buffptr < wpmd->byte_length + (wpmd->byte_length & 1)) {
|
|
wpmd->data = NULL;
|
|
return 0;
|
|
}
|
|
|
|
wpmd->data = *buffptr;
|
|
(*buffptr) += wpmd->byte_length + (wpmd->byte_length & 1);
|
|
}
|
|
else
|
|
wpmd->data = NULL;
|
|
|
|
return 1;
|
|
}
|
|
|
|
// given a pointer to a WavPack block, parse all the "meta" blocks and display something about them
|
|
|
|
static void parse_wavpack_block (unsigned char *block_data)
|
|
{
|
|
unsigned char *blockptr = block_data + sizeof (WavpackHeader);
|
|
WavpackHeader *wphdr = (WavpackHeader *) block_data;
|
|
int metadata_count = 0;
|
|
WavpackMetadata wpmd;
|
|
|
|
while (read_metadata_buff (&wpmd, block_data, &blockptr)) {
|
|
metadata_count++;
|
|
if (wpmd.id & 0x10)
|
|
printf (" metadata: ID = 0x%02x (UNASSIGNED), size = %d bytes\n", wpmd.id, wpmd.byte_length);
|
|
else if (wpmd.id & 0x20)
|
|
printf (" metadata: ID = 0x%02x (%s), size = %d bytes\n", wpmd.id, metadata_names [wpmd.id - 0x10], wpmd.byte_length);
|
|
else
|
|
printf (" metadata: ID = 0x%02x (%s), size = %d bytes\n", wpmd.id, metadata_names [wpmd.id], wpmd.byte_length);
|
|
}
|
|
|
|
if (blockptr != block_data + wphdr->ckSize + 8)
|
|
printf ("error: garbage at end of WavPack block\n");
|
|
|
|
if (!verify_wavpack_block (block_data))
|
|
printf ("error: checksum failure on WavPack block\n");
|
|
}
|
|
|
|
// Quickly verify the referenced block. It is assumed that the WavPack header has been converted
|
|
// to native endian format. If a block checksum is performed, that is done in little-endian
|
|
// (file) format. It is also assumed that the caller has made sure that the block length
|
|
// indicated in the header is correct (we won't overflow the buffer). If a checksum is present,
|
|
// then it is checked, otherwise we just check that all the metadata blocks are formatted
|
|
// correctly (without looking at their contents). Returns FALSE for bad block.
|
|
|
|
#define ID_BLOCK_CHECKSUM (ID_OPTIONAL_DATA | 0xf)
|
|
|
|
static int verify_wavpack_block (unsigned char *buffer)
|
|
{
|
|
WavpackHeader *wphdr = (WavpackHeader *) buffer;
|
|
uint32_t checksum_passed = 0, bcount, meta_bc;
|
|
unsigned char *dp, meta_id, c1, c2;
|
|
|
|
if (strncmp (wphdr->ckID, "wvpk", 4) || wphdr->ckSize + 8 < sizeof (WavpackHeader))
|
|
return 0;
|
|
|
|
bcount = wphdr->ckSize - sizeof (WavpackHeader) + 8;
|
|
dp = (unsigned char *)(wphdr + 1);
|
|
|
|
while (bcount >= 2) {
|
|
meta_id = *dp++;
|
|
c1 = *dp++;
|
|
|
|
meta_bc = c1 << 1;
|
|
bcount -= 2;
|
|
|
|
if (meta_id & ID_LARGE) {
|
|
if (bcount < 2)
|
|
return 0;
|
|
|
|
c1 = *dp++;
|
|
c2 = *dp++;
|
|
meta_bc += ((uint32_t) c1 << 9) + ((uint32_t) c2 << 17);
|
|
bcount -= 2;
|
|
}
|
|
|
|
if (bcount < meta_bc)
|
|
return 0;
|
|
|
|
if ((meta_id & ID_UNIQUE) == ID_BLOCK_CHECKSUM) {
|
|
unsigned char *csptr = buffer;
|
|
|
|
int wcount = (int)(dp - 2 - buffer) >> 1;
|
|
uint32_t csum = (uint32_t) -1;
|
|
|
|
if ((meta_id & ID_ODD_SIZE) || meta_bc < 2 || meta_bc > 4)
|
|
return 0;
|
|
|
|
native_to_little_endian ((WavpackHeader *) buffer, WavpackHeaderFormat);
|
|
|
|
while (wcount--) {
|
|
csum = (csum * 3) + csptr [0] + (csptr [1] << 8);
|
|
csptr += 2;
|
|
}
|
|
|
|
little_endian_to_native ((WavpackHeader *) buffer, WavpackHeaderFormat);
|
|
|
|
if (meta_bc == 4) {
|
|
if (*dp++ != (csum & 0xff) || *dp++ != ((csum >> 8) & 0xff) || *dp++ != ((csum >> 16) & 0xff) || *dp++ != ((csum >> 24) & 0xff))
|
|
return 0;
|
|
}
|
|
else {
|
|
csum ^= csum >> 16;
|
|
|
|
if (*dp++ != (csum & 0xff) || *dp++ != ((csum >> 8) & 0xff))
|
|
return 0;
|
|
}
|
|
|
|
checksum_passed++;
|
|
}
|
|
|
|
bcount -= meta_bc;
|
|
dp += meta_bc;
|
|
}
|
|
|
|
return (bcount == 0) && (!(wphdr->flags & HAS_CHECKSUM) || checksum_passed);
|
|
}
|
|
|
|
static int32_t read_bytes (void *buff, int32_t bcount)
|
|
{
|
|
return fread (buff, 1, bcount, stdin);
|
|
}
|
|
|
|
// Read from current file position until a valid 32-byte WavPack 4.0 header is
|
|
// found and read into the specified pointer. The number of bytes skipped is
|
|
// returned. If no WavPack header is found within 1 meg, then a -1 is returned
|
|
// to indicate the error. No additional bytes are read past the header and it
|
|
// is returned in the processor's native endian mode. Seeking is not required.
|
|
|
|
static uint32_t read_next_header (read_stream infile, WavpackHeader *wphdr)
|
|
{
|
|
unsigned char buffer [sizeof (*wphdr)], *sp = buffer + sizeof (*wphdr), *ep = sp;
|
|
uint32_t bytes_skipped = 0;
|
|
int bleft;
|
|
|
|
while (1) {
|
|
if (sp < ep) {
|
|
bleft = ep - sp;
|
|
memmove (buffer, sp, bleft);
|
|
}
|
|
else
|
|
bleft = 0;
|
|
|
|
if (infile (buffer + bleft, sizeof (*wphdr) - bleft) != (int32_t) sizeof (*wphdr) - bleft)
|
|
return -1;
|
|
|
|
sp = buffer;
|
|
|
|
if (*sp++ == 'w' && *sp == 'v' && *++sp == 'p' && *++sp == 'k' &&
|
|
!(*++sp & 1) && sp [2] < 16 && !sp [3] && (sp [2] || sp [1] || *sp >= 24) && sp [5] == 4 &&
|
|
sp [4] >= (MIN_STREAM_VERS & 0xff) && sp [4] <= (MAX_STREAM_VERS & 0xff) && sp [18] < 3 && !sp [19]) {
|
|
memcpy (wphdr, buffer, sizeof (*wphdr));
|
|
little_endian_to_native (wphdr, WavpackHeaderFormat);
|
|
return bytes_skipped;
|
|
}
|
|
|
|
// printf ("read_next_header() did not see valid block right away: %c %c %c %c\n", buffer [0], buffer [1], buffer [2], buffer [3]);
|
|
|
|
while (sp < ep && *sp != 'w')
|
|
sp++;
|
|
|
|
if ((bytes_skipped += sp - buffer) > 1024 * 1024)
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// using the specified format string, convert the referenced structure from little-endian to native
|
|
|
|
static void little_endian_to_native (void *data, char *format)
|
|
{
|
|
unsigned char *cp = (unsigned char *) data;
|
|
int32_t temp;
|
|
|
|
while (*format) {
|
|
switch (*format) {
|
|
case 'L':
|
|
temp = cp [0] + ((int32_t) cp [1] << 8) + ((int32_t) cp [2] << 16) + ((int32_t) cp [3] << 24);
|
|
* (int32_t *) cp = temp;
|
|
cp += 4;
|
|
break;
|
|
|
|
case 'S':
|
|
temp = cp [0] + (cp [1] << 8);
|
|
* (int16_t *) cp = (int16_t) temp;
|
|
cp += 2;
|
|
break;
|
|
|
|
default:
|
|
if (isdigit (*format))
|
|
cp += *format - '0';
|
|
|
|
break;
|
|
}
|
|
|
|
format++;
|
|
}
|
|
}
|
|
|
|
static void native_to_little_endian (void *data, char *format)
|
|
{
|
|
unsigned char *cp = (unsigned char *) data;
|
|
int32_t temp;
|
|
|
|
while (*format) {
|
|
switch (*format) {
|
|
case 'L':
|
|
temp = * (int32_t *) cp;
|
|
*cp++ = (unsigned char) temp;
|
|
*cp++ = (unsigned char) (temp >> 8);
|
|
*cp++ = (unsigned char) (temp >> 16);
|
|
*cp++ = (unsigned char) (temp >> 24);
|
|
break;
|
|
|
|
case 'S':
|
|
temp = * (int16_t *) cp;
|
|
*cp++ = (unsigned char) temp;
|
|
*cp++ = (unsigned char) (temp >> 8);
|
|
break;
|
|
|
|
default:
|
|
if (isdigit (*format))
|
|
cp += *format - '0';
|
|
|
|
break;
|
|
}
|
|
|
|
format++;
|
|
}
|
|
}
|