/* * import_vag.c -- module for importing PlayStation VAG audio data * Written by Andrew Church * * This file is part of transcode, a video stream processing tool. * transcode is free software, distributable under the terms of the GNU * General Public License (version 2 or later). See the file COPYING * for details. */ #define MOD_NAME "import_vag.so" #define MOD_VERSION "v1.0.0 (2006-04-18)" #define MOD_CAP "Imports PlayStation VAG-format audio" #define MOD_AUTHOR "Andrew Church" /*%* *%* DESCRIPTION *%* This module decodes VAG-format audio (from PlayStation). *%* *%* #BUILD-DEPENDS *%* *%* #DEPENDS *%* *%* PROCESSING *%* import/demuxer *%* *%* MEDIA *%* audio *%* *%* #INPUT *%* *%* OUTPUT *%* PCM *%* *%* OPTION *%* blocksize (integer) *%* stereo blocking size. *%*/ #define MOD_FEATURES \ TC_MODULE_FEATURE_DECODE|TC_MODULE_FEATURE_AUDIO #define MOD_FLAGS \ TC_MODULE_FLAG_RECONFIGURABLE #include "transcode.h" #include "libtc/libtc.h" #include "libtc/optstr.h" #include "libtc/tcmodule-plugin.h" /* For UNLIKELY() (FIXME: this should be defined somewhere useful) */ #include "aclib/ac_internal.h" /* Maximum stereo block size we allow */ #define MAX_STEREO_BLOCK 0x1000 /* Default stereo block size */ #define DEF_STEREO_BLOCK 0x1000 /* Private data used by this module. */ typedef struct { int blocksize; /* Stereo block size */ uint8_t databuf[MAX_STEREO_BLOCK]; /* For accumulating data */ int datalen; int datapos; int nclip; /* Number of sampled clipped */ int prevsamp[2][2]; /* prevsamp[ch][0] is the immediately previous sample; * prevsamp[ch][1] is the sample before that */ int totalread; /* For statistics */ } PrivateData; /* Local routine declarations. */ static int vag_decode(TCModuleInstance *self, aframe_list_t *inframe, aframe_list_t *outframe); static void do_decode(const uint8_t *inbuf, int16_t *outbuf, int chan, PrivateData *pd); /*************************************************************************/ /*************************************************************************/ /* Module interface routines and data. */ /*************************************************************************/ /** * vag_init: Initialize this instance of the module. See tcmodule-data.h * for function details. */ static int vag_init(TCModuleInstance *self, uint32_t features) { PrivateData *pd = NULL; TC_MODULE_SELF_CHECK(self, "init"); TC_MODULE_INIT_CHECK(self, MOD_FEATURES, features); pd = tc_zalloc(sizeof(PrivateData)); if (!pd) { tc_log_error(MOD_NAME, "init: out of memory!"); return TC_ERROR; } pd->blocksize = DEF_STEREO_BLOCK; self->userdata = pd; if (verbose) { tc_log_info(MOD_NAME, "%s %s", MOD_VERSION, MOD_CAP); } return TC_OK; } /*************************************************************************/ /** * vag_fini: Clean up after this instance of the module. See * tcmodule-data.h for function details. */ static int vag_fini(TCModuleInstance *self) { TC_MODULE_SELF_CHECK(self, "fini"); tc_free(self->userdata); self->userdata = NULL; return TC_OK; } /*************************************************************************/ /** * vag_configure: Configure this instance of the module. See * tcmodule-data.h for function details. */ static int vag_configure(TCModuleInstance *self, const char *options, vob_t *vob) { TC_MODULE_SELF_CHECK(self, "configure"); return TC_OK; } /*************************************************************************/ /** * vag_stop: Reset this instance of the module. See tcmodule-data.h for * function details. */ static int vag_stop(TCModuleInstance *self) { PrivateData *pd = NULL; TC_MODULE_SELF_CHECK(self, "stop"); pd = self->userdata; if (verbose & TC_DEBUG) tc_log_info(MOD_NAME, "%d bytes processed", pd->totalread); if (pd->nclip > 0) tc_log_info(MOD_NAME, "%d samples clipped", pd->nclip); pd->datalen = 0; pd->datapos = 0; pd->nclip = 0; pd->prevsamp[0][0] = 0; pd->prevsamp[0][1] = 0; pd->prevsamp[1][0] = 0; pd->prevsamp[1][1] = 0; pd->totalread = 0; return TC_OK; } /*************************************************************************/ /** * vag_inspect: Return the value of an option in this instance of the * module. See tcmodule-data.h for function details. */ static int vag_inspect(TCModuleInstance *self, const char *param, const char **value) { PrivateData *pd = NULL; static char buf[TC_BUF_MAX]; TC_MODULE_SELF_CHECK(self, "inspect"); TC_MODULE_SELF_CHECK(param, "inspect"); TC_MODULE_SELF_CHECK(value, "inspect"); pd = self->userdata; if (optstr_lookup(param, "help")) { tc_snprintf(buf, sizeof(buf), "Overview:\n" " Decodes PlayStation VAG format (ADPCM-style) audio.\n" "Options available:\n" " blocksize=N Set stereo blocking size (16-%d, default %d)\n", MAX_STEREO_BLOCK, DEF_STEREO_BLOCK); *value = buf; return TC_IMPORT_OK; } if (optstr_lookup(param, "blocksize")) { tc_snprintf(buf, sizeof(buf), "%d", pd->blocksize); *value = buf; return TC_IMPORT_OK; } return TC_IMPORT_OK; } /*************************************************************************/ static const TCCodecID vag_codecs_in[] = { TC_CODEC_VAG, TC_CODEC_ERROR }; static const TCCodecID vag_codecs_out[] = { TC_CODEC_PCM, TC_CODEC_ERROR }; static const TCFormatID vag_formats_in[] = { TC_FORMAT_ERROR }; static const TCFormatID vag_formats_out[] = { TC_FORMAT_ERROR }; static const TCModuleInfo vag_info = { .features = MOD_FEATURES, .flags = MOD_FLAGS, .name = MOD_NAME, .version = MOD_VERSION, .description = MOD_CAP, .codecs_in = vag_codecs_in, .codecs_out = vag_codecs_out, .formats_in = vag_formats_in, .formats_out = vag_formats_out }; static const TCModuleClass vag_class = { TC_MODULE_CLASS_HEAD(vag), .init = vag_init, .fini = vag_fini, .configure = vag_configure, .stop = vag_stop, .inspect = vag_inspect, .decode_audio = vag_decode, }; TC_MODULE_ENTRY_POINT(vag) /*************************************************************************/ /*************************************************************************/ /** * vag_decode: Decode a frame of data. See tcmodule-data.h for function * details. */ static int vag_decode(TCModuleInstance *self, aframe_list_t *inframe, aframe_list_t *outframe) { PrivateData *pd; uint8_t *inptr; int insize; int16_t *outptr; TC_MODULE_SELF_CHECK(self, "decode"); TC_MODULE_SELF_CHECK(inframe, "decode"); TC_MODULE_SELF_CHECK(outframe, "decode"); pd = self->userdata; inptr = inframe->audio_buf; insize = inframe->audio_size; outptr = (int16_t *)outframe->audio_buf; outframe->audio_size = 0; /* Fill out any accumulated data block first */ if (pd->datalen > 0) { int needed = 16 - pd->datalen; if (insize < needed) { /* Not enough for a 16-byte block--copy and exit */ memcpy(pd->databuf + pd->datalen, inframe->audio_buf, insize); pd->datalen += insize; return TC_OK; } else { /* Finish off the partial block */ memcpy(pd->databuf + pd->datalen, inframe->audio_buf, needed); insize -= needed; do_decode(pd->databuf, outptr, 0, pd); outptr += 28; pd->datalen = 0; } } /* Loop through all complete data blocks in the input */ while (insize >= 16) { do_decode(inptr, outptr, 0, pd); inptr += 16; insize -= 16; outptr += 28; } /* Save any remaining data in the accumulation buffer */ if (insize > 0) { memcpy(pd->databuf, inptr, insize); pd->datalen = insize; } return TC_OK; } /*************************************************************************/ /** * do_decode: Decode a single block of 16 bytes into 28 samples. * * Parameters: * inbuf: Pointer to 16 input bytes. * outbuf: Pointer to buffer of 56 bytes (28 samples). * chan: Channel selector (0 or 1); controls which previous-sample * data is used. * pd: Pointer to module instance's PrivateData structure. * Return value: * None. * Preconditions: * inbuf != NULL * outbuf != NULL * chan == 0 || chan == 1 * pd != NULL */ static void do_decode(const uint8_t *inbuf, int16_t *outbuf, int chan, PrivateData *pd) { static const int predict[16][2] = { { 0, 0}, { 60, 0}, {115, 52}, { 98, 55}, {122, 60}, { 0, 0}, { 0, 60}, }; int type = inbuf[0] >> 4; int scale = 16 - (inbuf[0] & 0x0F); int prev0 = pd->prevsamp[chan][0]; /* Pull into variables for speed */ int prev1 = pd->prevsamp[chan][1]; int i; for (i = 0; i < 28; i++) { int val; if (i%2 == 0) val = inbuf[2+i/2] & 0x0F; else val = inbuf[2+i/2] >> 4; if (val >= 8) val -= 16; val <<= scale; val = (prev0*predict[type][0] - prev1*predict[type][1] + (val<<2)) >>6; if (UNLIKELY(val > 0x7FFF)) { if (verbose & TC_DEBUG) { tc_log_warn(MOD_NAME, "clipping to +max: prev1=%c%04X prev0=" "%c%04X val=+%04X (type/scale/in=%X/%X/%X)", prev1>=0 ? '+' : '-', prev1 & 0xFFFF, prev0>=0 ? '+' : '-', prev0 & 0xFFFF, val & 0xFFFF, type, 16-scale, i%2==0 ? inbuf[2+i/2]&0x0F : inbuf[2+i/2]>>4); } val = 0x7FFF; } if (UNLIKELY(val < -0x8000)) { if (verbose & TC_DEBUG) { tc_log_warn(MOD_NAME, "clipping to -min: prev1=%c%04X prev0=" "%c%04X val=-%04X (type/scale/in=%X/%X/%X)", prev1>=0 ? '+' : '-', prev1 & 0xFFFF, prev0>=0 ? '+' : '-', prev0 & 0xFFFF, val & 0xFFFF, type, 16-scale, i%2==0 ? inbuf[2+i/2]&0x0F : inbuf[2+i/2]>>4); } val = -0x8000; } outbuf[i] = val; prev1 = prev0; prev0 = val; } /* Update private data */ pd->prevsamp[chan][0] = prev0; pd->prevsamp[chan][1] = prev1; pd->totalread += 16; } /*************************************************************************/ /*************************************************************************/ /* Old-fashioned module stuff */ static PrivateData static_pd; static FILE *file; static int16_t saved_samples[56]; static int saved_samples_count; static int mpeg_mode; // extracting from program stream? static int mpeg_packet_left; // for xread() static int mpeg_check_for_header; static int mpeg_stop; static int verbose_flag; static int capability_flag = TC_CAP_PCM; #define MOD_PRE vagOLD #define MOD_CODEC "(audio) PS-VAG" #include "import_def.h" #include "magic.h" /*************************************************************************/ MOD_open { uint8_t buf[16]; if (param->flag != TC_AUDIO) return TC_ERROR; if (vob->a_chan != 1 && vob->a_chan != 2) { tc_log_error(MOD_NAME, "%d channels not supported (must be 1 or 2)", vob->a_chan); return TC_ERROR; } if (vob->a_bits != 16) { tc_log_error(MOD_NAME, "%d bits not supported (must be 16)", vob->a_bits); return TC_ERROR; } memset(&static_pd, 0, sizeof(static_pd)); if (vob->im_a_string && sscanf(vob->im_a_string, "blocksize=%d", &static_pd.blocksize) == 1 ) { if (static_pd.blocksize<16 || static_pd.blocksize>MAX_STEREO_BLOCK) { tc_log_error(MOD_NAME, "Block size %d out of range (16...%d)", static_pd.blocksize, MAX_STEREO_BLOCK); return TC_ERROR; } else if (static_pd.blocksize & 15) { tc_log_error(MOD_NAME, "Block size %d not a multiple of 16", static_pd.blocksize); return TC_ERROR; } } else { static_pd.blocksize = DEF_STEREO_BLOCK; } saved_samples_count = 0; param->fd = NULL; /* we handle the reading ourselves */ file = fopen(vob->audio_in_file, "r"); if (!file) { tc_log_error(MOD_NAME, "Unable to open %s: %s", vob->audio_in_file, strerror(errno)); return TC_ERROR; } /* Check whether this is an MPEG stream and enable the hacks if needed */ if (fread(buf, 5, 1, file) != 1) { tc_log_error(MOD_NAME, "File %s is empty!", vob->audio_in_file); fclose(file); file = NULL; return TC_ERROR; } if ((buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3]) == TC_MAGIC_VOB) { mpeg_mode = 1; mpeg_packet_left = 0; mpeg_check_for_header = 1; mpeg_stop = 0; if ((buf[4] & 0xC0) == 0x40) { /* mpeg2 */ if (fread(buf, 9, 1, file) != 1) { tc_log_error(MOD_NAME, "%s: short file!", vob->audio_in_file); goto close_and_abort; } if ((buf[8] & 7) && fread(buf, buf[8] & 7, 1, file) != 1) { tc_log_error(MOD_NAME, "%s: short file!", vob->audio_in_file); goto close_and_abort; } } else if ((buf[4] & 0xF0) == 0x20) { /* mpeg1 */ if (fread(buf, 7, 1, file) != 1) { tc_log_error(MOD_NAME, "%s: short file!", vob->audio_in_file); goto close_and_abort; } } else { tc_log_error(MOD_NAME, "%s: bizarre MPEG stream!", vob->audio_in_file); goto close_and_abort; } } else { mpeg_mode = 0; if (vob->a_chan == 2) { memcpy(static_pd.databuf, buf, 5); if (fread(static_pd.databuf+5,static_pd.blocksize-5,1,file) != 1) { tc_log_error(MOD_NAME, "%s: short file!", vob->audio_in_file); goto close_and_abort; } static_pd.datalen = static_pd.blocksize; } else { /* mono */ if (fread(buf+5, 11, 1, file) != 1) { tc_log_error(MOD_NAME, "%s: short file!", vob->audio_in_file); goto close_and_abort; } do_decode(buf, saved_samples, 0, &static_pd); saved_samples_count = 28; } } /* if an MPEG stream */ return TC_OK; close_and_abort: fclose(file); file = NULL; return TC_ERROR; } /*************************************************************************/ MOD_close { if (verbose & TC_DEBUG) tc_log_info(MOD_NAME, "%d bytes processed", static_pd.totalread); if (static_pd.nclip > 0) tc_log_info(MOD_NAME, "%d samples clipped", static_pd.nclip); if (file) { fclose(file); file = NULL; } return TC_OK; } /*************************************************************************/ /** * xread: Read data like fread(), but if `mpeg_mode' is nonzero, extract * the data from the MPEG stream. Parameters and return value are as for * fread() (except that the buffer is of type uint8_t * for simplicity). * This is a really ugly hack; I hope the new module system gets moving * soon... */ static size_t xread(uint8_t *buf, size_t elsize, size_t els, FILE *f) { int nread; /* Total bytes read */ uint8_t readbuf[2048]; if (!mpeg_mode) return fread(buf, elsize, els, f); if (mpeg_stop) return TC_OK; nread = 0; if (mpeg_packet_left > 0) { if (mpeg_packet_left >= elsize*els) { nread = fread(buf, 1, elsize*els, f); mpeg_packet_left -= nread; return nread / elsize; } else { nread = fread(buf, 1, mpeg_packet_left, f); if (nread < mpeg_packet_left) /* EOF */ return TC_OK; mpeg_packet_left = 0; } } while (nread < elsize*els) { if (fread(readbuf, 4, 1, f) != 1) break; if (memcmp(readbuf, "\0\0\1", 3) != 0) { tc_log_warn(MOD_NAME, "No start code found at %ld", (long)ftell(f)-4); break; } if (verbose & TC_DEBUG) { tc_log_msg(MOD_NAME, "Start code 0x%02X at %ld", readbuf[3], (long)ftell(f)-4); } if (readbuf[3] == 0xB9) { /* program end */ if (verbose & TC_DEBUG) tc_log_msg(MOD_NAME, "Program end code found"); mpeg_stop = 1; break; } else if (readbuf[3] == 0xBA) { if (fread(readbuf, 8, 1, f) != 1) break; if ((readbuf[0] & 0xC0) == 0x40) { /* mpeg2 */ if (fread(readbuf, 2, 1, f) != 1) break; if ((readbuf[1]&7) && fread(readbuf, readbuf[1]&7, 1, f) != 1) break; } } else { int packetlen; if (fread(readbuf+4, 2, 1, f) != 1) break; packetlen = readbuf[4]<<8 | readbuf[5]; if (readbuf[3] != 0xBD) { while (packetlen > 0) { int toread = sizeof(readbuf); if (toread > packetlen) toread = packetlen; if (fread(readbuf, toread, 1, f) != 1) break; packetlen -= toread; } if (packetlen > 0) /* i.e. read failed in the while loop */ break; } else { /* 0xBD==private stream 1: get stream ID (VAG audio is 0xFF) */ if (fread(readbuf, 1, 1, f) != 1) break; packetlen -= 1; if ((readbuf[0] & 0xC0) == 0x80) { /* mpeg2 */ if (fread(readbuf, 2, 1, f) != 1) break; packetlen -= 2+readbuf[1]; if (fread(readbuf, readbuf[1], 1, f) != 1) break; } else { /* mpeg1 */ int skipbytes; while (readbuf[0] == 0xFF) { if (fread(readbuf, 1, 1, f) != 1) break; packetlen -= 1; } if (readbuf[0] == 0xFF) /* i.e. read failed */ break; if ((readbuf[0] & 0xC0) == 0x40) { if (fread(readbuf, 2, 1, f) != 1) break; packetlen -= 2; readbuf[0] = readbuf[1]; } switch (readbuf[0] >> 4) { case 0: skipbytes = 1; break; case 2: skipbytes = 5; break; case 3: skipbytes = 10; break; default: skipbytes = 0; break; } if (skipbytes) { if (fread(readbuf, skipbytes, 1, f) != 1) break; packetlen -= skipbytes; } } if (fread(readbuf, 1, 1, f) != 1) break; packetlen -= 1; if (packetlen > 1) { if (fread(readbuf+1, 1, 1, f) != 1) break; packetlen -= 1; } else { readbuf[1] = 0; // anything but 0xA1 } if (verbose & TC_DEBUG) { tc_log_msg(MOD_NAME, "... stream code %02X %02X", readbuf[0], readbuf[1]); } if (readbuf[0] != 0xFF || readbuf[1] != 0xA1) { while (packetlen > 0) { int toread = sizeof(readbuf); if (toread > packetlen) toread = packetlen; if (fread(readbuf, toread, 1, f) != 1) break; packetlen -= toread; } if (packetlen > 0) /* i.e. read failed */ break; } else if (packetlen < 2) { /* okay, enough is enough */ tc_log_error(MOD_NAME, "private stream 1 packet too small!!"); return TC_OK; } else { /* A desired data packet, at last */ int toread; if (fread(readbuf, 2, 1, f) != 1) break; packetlen -= 2; /* FIXME: this won't work if we read 1 byte at a time */ if (mpeg_check_for_header && packetlen >= 4 && nread+4 <= els*elsize ) { mpeg_check_for_header = 0; if (fread(readbuf, 4, 1, f) != 1) break; packetlen -= 4; if (memcmp(readbuf,"SShd",4) == 0 && packetlen >= 36) { int bits, rate, chans, block, size; if (fread(readbuf+4, 36, 1, f) != 1) break; packetlen -= 36; bits = readbuf[ 8] | readbuf[ 9]<<8 | readbuf[10]<<16 | readbuf[11]<<24; rate = readbuf[12] | readbuf[13]<<8 | readbuf[14]<<16 | readbuf[15]<<24; chans = readbuf[16] | readbuf[17]<<8 | readbuf[18]<<16 | readbuf[19]<<24; block = readbuf[20] | readbuf[21]<<8 | readbuf[22]<<16 | readbuf[23]<<24; size = readbuf[36] | readbuf[37]<<8 | readbuf[38]<<16 | readbuf[39]<<24; tc_log_info(MOD_NAME, "MPEG-embedded audio: %d/%d/%d," " stereo blocksize %d, %d data bytes", rate, bits, chans, block, size); } else { memcpy(buf+nread, readbuf, 4); nread += 4; } } /* Whew, now we can start reading */ toread = els*elsize - nread; if (toread > packetlen) toread = packetlen; toread = fread(buf+nread, 1, toread, f); nread += toread; mpeg_packet_left = packetlen - toread; if (mpeg_packet_left > 0) break; /* stopped reading in the middle */ } } /* if 0xBD */ } /* if not 0xB9/0xBA */ } /* while bytes left to read */ return nread / elsize; } /*************************************************************************/ MOD_decode { uint8_t inbuf[16]; int outlimit = param->size / 2; int outcount = 0; /* total samples for the frame */ while (outcount < outlimit) { /* First save any samples in the output buffer */ if (saved_samples_count > 0) { if (outcount + saved_samples_count > outlimit) { int nleft = outlimit - outcount; memcpy(param->buffer + outcount*2, saved_samples, nleft*2); outcount += nleft; saved_samples_count -= nleft; memmove(saved_samples, saved_samples + nleft, saved_samples_count*2); break; } else { memcpy(param->buffer + outcount*2, saved_samples, saved_samples_count*2); outcount += saved_samples_count; saved_samples_count = 0; } } /* Now read the next block of data and decode it to the output buffer*/ if (vob->a_chan == 2 && static_pd.datapos >= static_pd.datalen) { /* Finished the previous stereo block, read a new one */ if (xread(static_pd.databuf, static_pd.blocksize, 1, file) != 1) { if (verbose & TC_DEBUG) tc_log_msg(MOD_NAME, "EOF reached"); break; } static_pd.datalen = static_pd.blocksize; static_pd.datapos = 0; } if (xread(inbuf, 16, 1, file) != 1) { if (verbose & TC_DEBUG) tc_log_msg(MOD_NAME, "EOF reached"); break; } if (vob->a_chan == 1) { do_decode(inbuf, saved_samples, 0, &static_pd); saved_samples_count = 28; } else { uint16_t outbuf0[28], outbuf1[28]; int i; do_decode(inbuf, outbuf0, 0, &static_pd); do_decode(inbuf, outbuf1, 1, &static_pd); for (i = 0; i < 28; i++) { saved_samples[i*2 ] = outbuf0[i]; saved_samples[i*2+1] = outbuf1[i]; } saved_samples_count = 56; static_pd.datapos += 16; } } /* All done, set final size and return */ param->size = outcount*2; return outcount