From aad1340bf4b7d46d0c1fb5fb042ecbada9343de9 Mon Sep 17 00:00:00 2001 From: Adrian Siekierka Date: Mon, 15 May 2023 16:17:16 +0200 Subject: [PATCH] first commit --- LICENSE | 18 ++ libpsxav/adpcm.c | 327 ++++++++++++++++++++++ libpsxav/cdrom.c | 69 +++++ libpsxav/libpsxav.h | 89 ++++++ meson.build | 24 ++ psxavenc/cdrom.c | 53 ++++ psxavenc/common.h | 124 +++++++++ psxavenc/decoding.c | 350 ++++++++++++++++++++++++ psxavenc/filefmt.c | 136 ++++++++++ psxavenc/mdec.c | 644 ++++++++++++++++++++++++++++++++++++++++++++ psxavenc/psxavenc.c | 187 +++++++++++++ 11 files changed, 2021 insertions(+) create mode 100644 LICENSE create mode 100644 libpsxav/adpcm.c create mode 100644 libpsxav/cdrom.c create mode 100644 libpsxav/libpsxav.h create mode 100644 meson.build create mode 100644 psxavenc/cdrom.c create mode 100644 psxavenc/common.h create mode 100644 psxavenc/decoding.c create mode 100644 psxavenc/filefmt.c create mode 100644 psxavenc/mdec.c create mode 100644 psxavenc/psxavenc.c diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9bc54ba --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ +Copyright (c) 2019 Ben "GreaseMonkey" Russell +Copyright (c) 2019, 2020, 2023 Adrian "asie" Siekierka + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. diff --git a/libpsxav/adpcm.c b/libpsxav/adpcm.c new file mode 100644 index 0000000..931f876 --- /dev/null +++ b/libpsxav/adpcm.c @@ -0,0 +1,327 @@ +/* +libpsxav: MDEC video + SPU/XA-ADPCM audio library + +Copyright (c) 2019, 2020 Adrian "asie" Siekierka +Copyright (c) 2019 Ben "GreaseMonkey" Russell + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#include +#include +#include "libpsxav.h" + +#define ADPCM_FILTER_COUNT 5 +#define XA_ADPCM_FILTER_COUNT 4 +#define SPU_ADPCM_FILTER_COUNT 5 + +static const int16_t filter_k1[ADPCM_FILTER_COUNT] = {0, 60, 115, 98, 122}; +static const int16_t filter_k2[ADPCM_FILTER_COUNT] = {0, 0, -52, -55, -60}; + +static int find_min_shift(const psx_audio_encoder_channel_state_t *state, int16_t *samples, int pitch, int filter) { + // Assumption made: + // + // There is value in shifting right one step further to allow the nibbles to clip. + // However, given a possible shift value, there is no value in shifting one step less. + // + // Having said that, this is not a completely accurate model of the encoder, + // so maybe we will need to shift one step less. + // + int prev1 = state->prev1; + int prev2 = state->prev2; + int k1 = filter_k1[filter]; + int k2 = filter_k2[filter]; + + int right_shift = 0; + + int32_t s_min = 0; + int32_t s_max = 0; + for (int i = 0; i < 28; i++) { + int32_t raw_sample = samples[i * pitch]; + int32_t previous_values = (k1*prev1 + k2*prev2 + (1<<5))>>6; + int32_t sample = raw_sample - previous_values; + if (sample < s_min) { s_min = sample; } + if (sample > s_max) { s_max = sample; } + prev2 = prev1; + prev1 = raw_sample; + } + while(right_shift < 12 && (s_max>>right_shift) > +0x7) { right_shift += 1; }; + while(right_shift < 12 && (s_min>>right_shift) < -0x8) { right_shift += 1; }; + + int min_shift = 12 - right_shift; + assert(0 <= min_shift && min_shift <= 12); + return min_shift; +} + +static uint8_t attempt_to_encode_nibbles(psx_audio_encoder_channel_state_t *outstate, const psx_audio_encoder_channel_state_t *instate, int16_t *samples, int sample_limit, int pitch, uint8_t *data, int data_shift, int data_pitch, int filter, int sample_shift) { + uint8_t nondata_mask = ~(0x0F << data_shift); + int min_shift = sample_shift; + int k1 = filter_k1[filter]; + int k2 = filter_k2[filter]; + + uint8_t hdr = (min_shift & 0x0F) | (filter << 4); + + if (outstate != instate) { + memcpy(outstate, instate, sizeof(psx_audio_encoder_channel_state_t)); + } + + outstate->mse = 0; + + for (int i = 0; i < 28; i++) { + int32_t sample = ((i * pitch) >= sample_limit ? 0 : samples[i * pitch]) + outstate->qerr; + int32_t previous_values = (k1*outstate->prev1 + k2*outstate->prev2 + (1<<5))>>6; + int32_t sample_enc = sample - previous_values; + sample_enc <<= min_shift; + sample_enc += (1<<(12-1)); + sample_enc >>= 12; + if(sample_enc < -8) { sample_enc = -8; } + if(sample_enc > +7) { sample_enc = +7; } + sample_enc &= 0xF; + + int32_t sample_dec = (int16_t) ((sample_enc&0xF) << 12); + sample_dec >>= min_shift; + sample_dec += previous_values; + if (sample_dec > +0x7FFF) { sample_dec = +0x7FFF; } + if (sample_dec < -0x8000) { sample_dec = -0x8000; } + int64_t sample_error = sample_dec - sample; + + assert(sample_error < (1<<30)); + assert(sample_error > -(1<<30)); + + data[i * data_pitch] = (data[i * data_pitch] & nondata_mask) | (sample_enc << data_shift); + // FIXME: dithering is hard to predict + //outstate->qerr += sample_error; + outstate->mse += ((uint64_t)sample_error) * (uint64_t)sample_error; + + outstate->prev2 = outstate->prev1; + outstate->prev1 = sample_dec; + } + + return hdr; +} + +static uint8_t encode_nibbles(psx_audio_encoder_channel_state_t *state, int16_t *samples, int sample_limit, int pitch, uint8_t *data, int data_shift, int data_pitch, int filter_count) { + psx_audio_encoder_channel_state_t proposed; + int64_t best_mse = ((int64_t)1<<(int64_t)50); + int best_filter = 0; + int best_sample_shift = 0; + + for (int filter = 0; filter < filter_count; filter++) { + int true_min_shift = find_min_shift(state, samples, pitch, filter); + + // Testing has shown that the optimal shift can be off the true minimum shift + // by 1 in *either* direction. + // This is NOT the case when dither is used. + int min_shift = true_min_shift - 1; + int max_shift = true_min_shift + 1; + if (min_shift < 0) { min_shift = 0; } + if (max_shift > 12) { max_shift = 12; } + + for (int sample_shift = min_shift; sample_shift <= max_shift; sample_shift++) { + // ignore header here + attempt_to_encode_nibbles( + &proposed, state, + samples, sample_limit, pitch, + data, data_shift, data_pitch, + filter, sample_shift); + + if (best_mse > proposed.mse) { + best_mse = proposed.mse; + best_filter = filter; + best_sample_shift = sample_shift; + } + } + } + + // now go with the encoder + return attempt_to_encode_nibbles( + state, state, + samples, sample_limit, pitch, + data, data_shift, data_pitch, + best_filter, best_sample_shift); +} + +static void encode_block_xa(int16_t *audio_samples, int audio_samples_limit, uint8_t *data, psx_audio_xa_settings_t settings, psx_audio_encoder_state_t *state) { + if (settings.bits_per_sample == 4) { + if (settings.stereo) { + data[0] = encode_nibbles(&(state->left), audio_samples, audio_samples_limit, 2, data + 0x10, 0, 4, XA_ADPCM_FILTER_COUNT); + data[1] = encode_nibbles(&(state->right), audio_samples + 1, audio_samples_limit - 1, 2, data + 0x10, 4, 4, XA_ADPCM_FILTER_COUNT); + data[2] = encode_nibbles(&(state->left), audio_samples + 56, audio_samples_limit - 56, 2, data + 0x11, 0, 4, XA_ADPCM_FILTER_COUNT); + data[3] = encode_nibbles(&(state->right), audio_samples + 56 + 1, audio_samples_limit - 56 - 1, 2, data + 0x11, 4, 4, XA_ADPCM_FILTER_COUNT); + data[8] = encode_nibbles(&(state->left), audio_samples + 56*2, audio_samples_limit - 56*2, 2, data + 0x12, 0, 4, XA_ADPCM_FILTER_COUNT); + data[9] = encode_nibbles(&(state->right), audio_samples + 56*2 + 1, audio_samples_limit - 56*2 - 1, 2, data + 0x12, 4, 4, XA_ADPCM_FILTER_COUNT); + data[10] = encode_nibbles(&(state->left), audio_samples + 56*3, audio_samples_limit - 56*3, 2, data + 0x13, 0, 4, XA_ADPCM_FILTER_COUNT); + data[11] = encode_nibbles(&(state->right), audio_samples + 56*3 + 1, audio_samples_limit - 56*3 - 1, 2, data + 0x13, 4, 4, XA_ADPCM_FILTER_COUNT); + } else { + data[0] = encode_nibbles(&(state->left), audio_samples, audio_samples_limit, 1, data + 0x10, 0, 4, XA_ADPCM_FILTER_COUNT); + data[1] = encode_nibbles(&(state->right), audio_samples + 28, audio_samples_limit - 28, 1, data + 0x10, 4, 4, XA_ADPCM_FILTER_COUNT); + data[2] = encode_nibbles(&(state->left), audio_samples + 28*2, audio_samples_limit - 28*2, 1, data + 0x11, 0, 4, XA_ADPCM_FILTER_COUNT); + data[3] = encode_nibbles(&(state->right), audio_samples + 28*3, audio_samples_limit - 28*3, 1, data + 0x11, 4, 4, XA_ADPCM_FILTER_COUNT); + data[8] = encode_nibbles(&(state->left), audio_samples + 28*4, audio_samples_limit - 28*4, 1, data + 0x12, 0, 4, XA_ADPCM_FILTER_COUNT); + data[9] = encode_nibbles(&(state->right), audio_samples + 28*5, audio_samples_limit - 28*5, 1, data + 0x12, 4, 4, XA_ADPCM_FILTER_COUNT); + data[10] = encode_nibbles(&(state->left), audio_samples + 28*6, audio_samples_limit - 28*6, 1, data + 0x13, 0, 4, XA_ADPCM_FILTER_COUNT); + data[11] = encode_nibbles(&(state->right), audio_samples + 28*7, audio_samples_limit - 28*7, 1, data + 0x13, 4, 4, XA_ADPCM_FILTER_COUNT); + } + } else { +/* if (settings->stereo) { + data[0] = encode_bytes(audio_samples, 2, data + 0x10); + data[1] = encode_bytes(audio_samples + 1, 2, data + 0x11); + data[2] = encode_bytes(audio_samples + 56, 2, data + 0x12); + data[3] = encode_bytes(audio_samples + 57, 2, data + 0x13); + } else { + data[0] = encode_bytes(audio_samples, 1, data + 0x10); + data[1] = encode_bytes(audio_samples + 28, 1, data + 0x11); + data[2] = encode_bytes(audio_samples + 56, 1, data + 0x12); + data[3] = encode_bytes(audio_samples + 84, 1, data + 0x13); + } */ + } +} + +uint32_t psx_audio_xa_get_buffer_size(psx_audio_xa_settings_t settings, int sample_count) { + int sample_pitch = psx_audio_xa_get_samples_per_sector(settings); + int xa_sectors = ((sample_count + sample_pitch - 1) / sample_pitch); + int xa_sector_size = psx_audio_xa_get_buffer_size_per_sector(settings); + return xa_sectors * xa_sector_size; +} + +uint32_t psx_audio_spu_get_buffer_size(int sample_count) { + return ((sample_count + 27) / 28) << 4; +} + +uint32_t psx_audio_xa_get_buffer_size_per_sector(psx_audio_xa_settings_t settings) { + return settings.format == PSX_AUDIO_XA_FORMAT_XA ? 2336 : 2352; +} + +uint32_t psx_audio_spu_get_buffer_size_per_block(void) { + return 16; +} + +uint32_t psx_audio_xa_get_samples_per_sector(psx_audio_xa_settings_t settings) { + return (((settings.bits_per_sample == 8) ? 112 : 224) >> (settings.stereo ? 1 : 0)) * 18; +} + +uint32_t psx_audio_spu_get_samples_per_block(void) { + return 28; +} + +static void psx_audio_xa_encode_init_sector(uint8_t *buffer, psx_audio_xa_settings_t settings) { + if (settings.format == PSX_AUDIO_XA_FORMAT_XACD) { + memset(buffer, 0, 2352); + memset(buffer+0x001, 0xFF, 10); + buffer[0x00F] = 0x02; + } else { + memset(buffer + 0x10, 0, 2336); + } + + buffer[0x010] = settings.file_number; + buffer[0x011] = settings.channel_number & 0x1F; + buffer[0x012] = 0x24 | 0x40; + buffer[0x013] = + (settings.stereo ? 1 : 0) + | (settings.frequency >= PSX_AUDIO_XA_FREQ_DOUBLE ? 0 : 4) + | (settings.bits_per_sample >= 8 ? 16 : 0); + memcpy(buffer + 0x014, buffer + 0x010, 4); +} + +int psx_audio_xa_encode(psx_audio_xa_settings_t settings, psx_audio_encoder_state_t *state, int16_t* samples, int sample_count, uint8_t *output) { + int sample_jump = (settings.bits_per_sample == 8) ? 112 : 224; + int i, j; + int xa_sector_size = settings.format == PSX_AUDIO_XA_FORMAT_XA ? 2336 : 2352; + int xa_offset = 2352 - xa_sector_size; + uint8_t init_sector = 1; + + if (settings.stereo) { sample_count <<= 1; } + + for (i = 0, j = 0; i < sample_count || ((j % 18) != 0); i += sample_jump, j++) { + uint8_t *sector_data = output + ((j/18) * xa_sector_size) - xa_offset; + uint8_t *block_data = sector_data + 0x18 + ((j%18) * 0x80); + + if (init_sector) { + psx_audio_xa_encode_init_sector(sector_data, settings); + init_sector = 0; + } + + encode_block_xa(samples + i, sample_count - i, block_data, settings, state); + + memcpy(block_data + 4, block_data, 4); + memcpy(block_data + 12, block_data + 8, 4); + + if ((j+1)%18 == 0) { + psx_cdrom_calculate_checksums(sector_data, PSX_CDROM_SECTOR_TYPE_MODE2_FORM2); + init_sector = 1; + } + } + + return (((j + 17) / 18) * xa_sector_size); +} + +int psx_audio_xa_encode_finalize(psx_audio_xa_settings_t settings, uint8_t *output, int output_length) { + if (output_length >= 2336) { + output[output_length - 2352 + 0x12] |= 0x80; + output[output_length - 2352 + 0x18] |= 0x80; + } +} + +int psx_audio_xa_encode_simple(psx_audio_xa_settings_t settings, int16_t* samples, int sample_count, uint8_t *output) { + psx_audio_encoder_state_t state; + memset(&state, 0, sizeof(psx_audio_encoder_state_t)); + int length = psx_audio_xa_encode(settings, &state, samples, sample_count, output); + psx_audio_xa_encode_finalize(settings, output, length); + return length; +} + +int psx_audio_spu_encode(psx_audio_encoder_state_t *state, int16_t* samples, int sample_count, uint8_t *output) { + uint8_t prebuf[28]; + uint8_t *buffer = output; + uint8_t *data; + + for (int i = 0; i < sample_count; i += 28, buffer += 16) { + buffer[0] = encode_nibbles(&(state->left), samples + i, sample_count - i, 1, prebuf, 0, 1, SPU_ADPCM_FILTER_COUNT); + buffer[1] = 0; + + for (int j = 0; j < 28; j+=2) { + buffer[2 + (j>>1)] = (prebuf[j] & 0x0F) | (prebuf[j+1] << 4); + } + } + + return buffer - output; +} + +int psx_audio_spu_encode_simple(int16_t* samples, int sample_count, uint8_t *output, int loop_start) { + psx_audio_encoder_state_t state; + memset(&state, 0, sizeof(psx_audio_encoder_state_t)); + int length = psx_audio_spu_encode(&state, samples, sample_count, output); + + if (length >= 32) { + if (loop_start < 0) { + output[1] = 4; + output[length - 16 + 1] = 1; + } else { + psx_audio_spu_set_flag_at_sample(output, loop_start, 4); + output[length - 16 + 1] = 3; + } + } else if (length >= 16) { + output[1] = loop_start >= 0 ? 7 : 5; + } + + return length; +} + +void psx_audio_spu_set_flag_at_sample(uint8_t* spu_data, int sample_pos, int flag) { + int buffer_pos = (sample_pos / 28) << 4; + spu_data[buffer_pos + 1] = flag; +} diff --git a/libpsxav/cdrom.c b/libpsxav/cdrom.c new file mode 100644 index 0000000..f6b0144 --- /dev/null +++ b/libpsxav/cdrom.c @@ -0,0 +1,69 @@ +/* +libpsxav: MDEC video + SPU/XA-ADPCM audio library + +Copyright (c) 2019, 2020 Adrian "asie" Siekierka +Copyright (c) 2019 Ben "GreaseMonkey" Russell + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#include +#include "libpsxav.h" + +static uint32_t psx_cdrom_calculate_edc(uint8_t *sector, uint32_t offset, uint32_t size) +{ + uint32_t edc = 0; + for (int i = offset; i < offset+size; i++) { + edc ^= 0xFF&(uint32_t)sector[i]; + for (int ibit = 0; ibit < 8; ibit++) { + edc = (edc>>1)^(0xD8018001*(edc&0x1)); + } + } + return edc; +} + +void psx_cdrom_calculate_checksums(uint8_t *sector, psx_cdrom_sector_type_t type) +{ + switch (type) { + case PSX_CDROM_SECTOR_TYPE_MODE1: { + uint32_t edc = psx_cdrom_calculate_edc(sector, 0x0, 0x810); + sector[0x810] = (uint8_t)(edc); + sector[0x811] = (uint8_t)(edc >> 8); + sector[0x812] = (uint8_t)(edc >> 16); + sector[0x813] = (uint8_t)(edc >> 24); + + memset(sector + 0x814, 0, 8); + // TODO: ECC + } break; + case PSX_CDROM_SECTOR_TYPE_MODE2_FORM1: { + uint32_t edc = psx_cdrom_calculate_edc(sector, 0x10, 0x808); + sector[0x818] = (uint8_t)(edc); + sector[0x819] = (uint8_t)(edc >> 8); + sector[0x81A] = (uint8_t)(edc >> 16); + sector[0x81B] = (uint8_t)(edc >> 24); + + // TODO: ECC + } break; + case PSX_CDROM_SECTOR_TYPE_MODE2_FORM2: { + uint32_t edc = psx_cdrom_calculate_edc(sector, 0x10, 0x91C); + sector[0x92C] = (uint8_t)(edc); + sector[0x92D] = (uint8_t)(edc >> 8); + sector[0x92E] = (uint8_t)(edc >> 16); + sector[0x92F] = (uint8_t)(edc >> 24); + } break; + } +} \ No newline at end of file diff --git a/libpsxav/libpsxav.h b/libpsxav/libpsxav.h new file mode 100644 index 0000000..828d8f8 --- /dev/null +++ b/libpsxav/libpsxav.h @@ -0,0 +1,89 @@ +/* +libpsxav: MDEC video + SPU/XA-ADPCM audio library + +Copyright (c) 2019, 2020 Adrian "asie" Siekierka +Copyright (c) 2019 Ben "GreaseMonkey" Russell + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#ifndef __LIBPSXAV_H__ +#define __LIBPSXAV_H__ + +#include +#include + +// audio.c + +#define PSX_AUDIO_XA_FREQ_SINGLE 18900 +#define PSX_AUDIO_XA_FREQ_DOUBLE 37800 + +typedef enum { + PSX_AUDIO_XA_FORMAT_XA, // .xa file + PSX_AUDIO_XA_FORMAT_XACD // 2352-byte sector +} psx_audio_xa_format_t; + +typedef struct { + psx_audio_xa_format_t format; + bool stereo; // false or true + int frequency; // 18900 or 37800 Hz + int bits_per_sample; // 4 or 8 + int file_number; // 00-FF + int channel_number; // 00-1F +} psx_audio_xa_settings_t; + +typedef struct { + int qerr; // quanitisation error + uint64_t mse; // mean square error + int prev1, prev2; +} psx_audio_encoder_channel_state_t; + +typedef struct { + psx_audio_encoder_channel_state_t left; + psx_audio_encoder_channel_state_t right; +} psx_audio_encoder_state_t; + +#define PSX_AUDIO_SPU_LOOP_END 1 +#define PSX_AUDIO_SPU_LOOP_REPEAT 3 +#define PSX_AUDIO_SPU_LOOP_START 4 + +uint32_t psx_audio_xa_get_buffer_size(psx_audio_xa_settings_t settings, int sample_count); +uint32_t psx_audio_spu_get_buffer_size(int sample_count); +uint32_t psx_audio_xa_get_buffer_size_per_sector(psx_audio_xa_settings_t settings); +uint32_t psx_audio_spu_get_buffer_size_per_block(void); +uint32_t psx_audio_xa_get_samples_per_sector(psx_audio_xa_settings_t settings); +uint32_t psx_audio_spu_get_samples_per_block(void); +int psx_audio_xa_encode(psx_audio_xa_settings_t settings, psx_audio_encoder_state_t *state, int16_t* samples, int sample_count, uint8_t *output); +int psx_audio_xa_encode_simple(psx_audio_xa_settings_t settings, int16_t* samples, int sample_count, uint8_t *output); +int psx_audio_spu_encode(psx_audio_encoder_state_t *state, int16_t* samples, int sample_count, uint8_t *output); +int psx_audio_spu_encode_simple(int16_t* samples, int sample_count, uint8_t *output, int loop_start); +int psx_audio_xa_encode_finalize(psx_audio_xa_settings_t settings, uint8_t *output, int output_length); +void psx_audio_spu_set_flag_at_sample(uint8_t* spu_data, int sample_pos, int flag); + +// cdrom.c + +#define PSX_CDROM_SECTOR_SIZE 2352 + +typedef enum { + PSX_CDROM_SECTOR_TYPE_MODE1, + PSX_CDROM_SECTOR_TYPE_MODE2_FORM1, + PSX_CDROM_SECTOR_TYPE_MODE2_FORM2 +} psx_cdrom_sector_type_t; + +void psx_cdrom_calculate_checksums(uint8_t *sector, psx_cdrom_sector_type_t type); + +#endif /* __LIBPSXAV_H__ */ \ No newline at end of file diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..59d113c --- /dev/null +++ b/meson.build @@ -0,0 +1,24 @@ +project('psxavenc', 'c', default_options: ['c_std=c11']) + +ffmpeg = [ + dependency('libavformat'), + dependency('libavcodec'), + dependency('libavutil'), + dependency('libswresample'), + dependency('libswscale') +] + +libpsxav = static_library('psxav', [ + 'libpsxav/adpcm.c', + 'libpsxav/cdrom.c', + 'libpsxav/libpsxav.h' +]) +libpsxav_dep = declare_dependency(include_directories: include_directories('libpsxav'), link_with: libpsxav) + +executable('psxavenc', [ + 'psxavenc/cdrom.c', + 'psxavenc/decoding.c', + 'psxavenc/filefmt.c', + 'psxavenc/mdec.c', + 'psxavenc/psxavenc.c' +], dependencies: [ffmpeg, libpsxav_dep], install: true) diff --git a/psxavenc/cdrom.c b/psxavenc/cdrom.c new file mode 100644 index 0000000..fe6a83f --- /dev/null +++ b/psxavenc/cdrom.c @@ -0,0 +1,53 @@ +/* +psxavenc: MDEC video + SPU/XA-ADPCM audio encoder frontend + +Copyright (c) 2019, 2020 Adrian "asie" Siekierka +Copyright (c) 2019 Ben "GreaseMonkey" Russell + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#include "common.h" + +void init_sector_buffer_video(uint8_t *buffer, settings_t *settings) { + memset(buffer,0,2352); + memset(buffer+0x001,0xFF,10); + + buffer[0x00F] = 0x02; + buffer[0x010] = settings->file_number; + buffer[0x011] = settings->channel_number & 0x1F; + buffer[0x012] = 0x08 | 0x40; + buffer[0x013] = 0x00; + memcpy(buffer + 0x014, buffer + 0x010, 4); +} + +void calculate_edc_data(uint8_t *buffer) +{ + uint32_t edc = 0; + for (int i = 0x010; i < 0x818; i++) { + edc ^= 0xFF&(uint32_t)buffer[i]; + for (int ibit = 0; ibit < 8; ibit++) { + edc = (edc>>1)^(0xD8018001*(edc&0x1)); + } + } + buffer[0x818] = (uint8_t)(edc); + buffer[0x819] = (uint8_t)(edc >> 8); + buffer[0x81A] = (uint8_t)(edc >> 16); + buffer[0x81B] = (uint8_t)(edc >> 24); + + // TODO: ECC +} diff --git a/psxavenc/common.h b/psxavenc/common.h new file mode 100644 index 0000000..e43dbd7 --- /dev/null +++ b/psxavenc/common.h @@ -0,0 +1,124 @@ +/* +psxavenc: MDEC video + SPU/XA-ADPCM audio encoder frontend + +Copyright (c) 2019, 2020 Adrian "asie" Siekierka +Copyright (c) 2019 Ben "GreaseMonkey" Russell + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define FORMAT_XA 0 +#define FORMAT_XACD 1 +#define FORMAT_SPU 2 +#define FORMAT_STR2 3 + +#define MAX_UNMUXED_BLOCKS 9 +typedef struct { + int frame_index; + int frame_block_index; + int frame_block_count; + int frame_block_base_overflow; + int frame_block_overflow_num; + int frame_block_overflow_den; + uint16_t bits_value; + int bits_left; + uint8_t unmuxed[2016*MAX_UNMUXED_BLOCKS]; + int bytes_used; + int blocks_used; + int uncomp_hwords_used; + int quant_scale; + int32_t *dct_block_lists[6]; +} vid_encoder_state_t; + +typedef struct { + int video_frame_src_size; + int video_frame_dst_size; + int audio_stream_index; + int video_stream_index; + AVFormatContext* format; + AVStream* audio_stream; + AVStream* video_stream; + AVCodecContext* audio_codec_context; + AVCodecContext* video_codec_context; + AVCodec* audio_codec; + AVCodec* video_codec; + struct SwrContext* resampler; + struct SwsContext* scaler; + AVFrame* frame; + + int sample_count_mul; + + double video_next_pts; +} av_decoder_state_t; + +typedef struct { + int format; // FORMAT_* + bool stereo; // false or true + int frequency; // 18900 or 37800 Hz + int bits_per_sample; // 4 or 8 + int file_number; // 00-FF + int channel_number; // 00-1F + + int video_width; + int video_height; + int video_fps_num; // FPS numerator + int video_fps_den; // FPS denominator + + int16_t *audio_samples; + int audio_sample_count; + uint8_t *video_frames; + int video_frame_count; + + av_decoder_state_t decoder_state_av; + + vid_encoder_state_t state_vid; +} settings_t; + +// cdrom.c +void init_sector_buffer_video(uint8_t *buffer, settings_t *settings); +void calculate_edc_data(uint8_t *buffer); + +// decoding.c +bool open_av_data(const char *filename, settings_t *settings); +bool poll_av_data(settings_t *settings); +bool ensure_av_data(settings_t *settings, int needed_audio_samples, int needed_video_frames); +void pull_all_av_data(settings_t *settings); +void retire_av_data(settings_t *settings, int retired_audio_samples, int retired_video_frames); +void close_av_data(settings_t *settings); + +// filefmt.c +void encode_file_spu(int16_t *audio_samples, int audio_sample_count, settings_t *settings, FILE *output); +void encode_file_xa(int16_t *audio_samples, int audio_sample_count, settings_t *settings, FILE *output); +void encode_file_str(settings_t *settings, FILE *output); + +// mdec.c +void encode_block_str(uint8_t *video_frames, int video_frame_count, uint8_t *output, settings_t *settings); diff --git a/psxavenc/decoding.c b/psxavenc/decoding.c new file mode 100644 index 0000000..48aff71 --- /dev/null +++ b/psxavenc/decoding.c @@ -0,0 +1,350 @@ +/* +psxavenc: MDEC video + SPU/XA-ADPCM audio encoder frontend + +Copyright (c) 2019, 2020 Adrian "asie" Siekierka +Copyright (c) 2019 Ben "GreaseMonkey" Russell + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#include "common.h" + +static void poll_av_packet(settings_t *settings, AVPacket *packet); + +int decode_audio_frame(AVCodecContext *codec, AVFrame *frame, int *frame_size, AVPacket *packet) { + int ret; + + if (packet != NULL) { + ret = avcodec_send_packet(codec, packet); + if (ret != 0) { + return 0; + } + } + + ret = avcodec_receive_frame(codec, frame); + if (ret >= 0) { + *frame_size = ret; + return 1; + } else { + return ret == AVERROR(EAGAIN) ? 1 : 0; + } +} + +int decode_video_frame(AVCodecContext *codec, AVFrame *frame, int *frame_size, AVPacket *packet) { + int ret; + + if (packet != NULL) { + ret = avcodec_send_packet(codec, packet); + if (ret != 0) { + return 0; + } + } + + ret = avcodec_receive_frame(codec, frame); + if (ret >= 0) { + *frame_size = ret; + return 1; + } else { + return ret == AVERROR(EAGAIN) ? 1 : 0; + } +} + +bool open_av_data(const char *filename, settings_t *settings) +{ + AVPacket packet; + + av_decoder_state_t* av = &(settings->decoder_state_av); + av->video_next_pts = 0.0; + av->frame = NULL; + av->video_frame_src_size = 0; + av->video_frame_dst_size = 0; + av->audio_stream_index = -1; + av->video_stream_index = -1; + av->format = NULL; + av->audio_stream = NULL; + av->video_stream = NULL; + av->audio_codec_context = NULL; + av->video_codec_context = NULL; + av->audio_codec = NULL; + av->video_codec = NULL; + av->resampler = NULL; + av->scaler = NULL; + + av->format = avformat_alloc_context(); + if (avformat_open_input(&(av->format), filename, NULL, NULL)) { + return false; + } + if (avformat_find_stream_info(av->format, NULL) < 0) { + return false; + } + + for (int i = 0; i < av->format->nb_streams; i++) { + if (av->format->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { + if (av->audio_stream_index >= 0) { + fprintf(stderr, "open_av_data: found multiple audio tracks?\n"); + return false; + } + av->audio_stream_index = i; + } + } + if (av->audio_stream_index == -1) { + return false; + } + + for (int i = 0; i < av->format->nb_streams; i++) { + if (av->format->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + if (av->video_stream_index >= 0) { + fprintf(stderr, "open_av_data: found multiple video tracks?\n"); + return false; + } + av->video_stream_index = i; + } + } + + av->audio_stream = av->format->streams[av->audio_stream_index]; + av->video_stream = (av->video_stream_index != -1 ? av->format->streams[av->video_stream_index] : NULL); + av->audio_codec = avcodec_find_decoder(av->audio_stream->codecpar->codec_id); + av->audio_codec_context = avcodec_alloc_context3(av->audio_codec); + if (av->audio_codec_context == NULL) { + return false; + } + if (avcodec_parameters_to_context(av->audio_codec_context, av->audio_stream->codecpar) < 0) { + return false; + } + if (avcodec_open2(av->audio_codec_context, av->audio_codec, NULL) < 0) { + return false; + } + + av->resampler = swr_alloc(); + av_opt_set_int(av->resampler, "in_channel_count", av->audio_codec_context->channels, 0); + av_opt_set_int(av->resampler, "in_channel_layout", av->audio_codec_context->channel_layout, 0); + av_opt_set_int(av->resampler, "in_sample_rate", av->audio_codec_context->sample_rate, 0); + av_opt_set_sample_fmt(av->resampler, "in_sample_fmt", av->audio_codec_context->sample_fmt, 0); + + av->sample_count_mul = settings->stereo ? 2 : 1; + av_opt_set_int(av->resampler, "out_channel_count", settings->stereo ? 2 : 1, 0); + av_opt_set_int(av->resampler, "out_channel_layout", settings->stereo ? AV_CH_LAYOUT_STEREO : AV_CH_LAYOUT_MONO, 0); + av_opt_set_int(av->resampler, "out_sample_rate", settings->frequency, 0); + av_opt_set_sample_fmt(av->resampler, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0); + + if (swr_init(av->resampler) < 0) { + return false; + } + + if (av->video_stream != NULL) { + av->video_codec = avcodec_find_decoder(av->video_stream->codecpar->codec_id); + av->video_codec_context = avcodec_alloc_context3(av->video_codec); + if(av->video_codec_context == NULL) { + return false; + } + if (avcodec_parameters_to_context(av->video_codec_context, av->video_stream->codecpar) < 0) { + return false; + } + if (avcodec_open2(av->video_codec_context, av->video_codec, NULL) < 0) { + return false; + } + + av->scaler = sws_getContext( + av->video_codec_context->width, + av->video_codec_context->height, + av->video_codec_context->pix_fmt, + settings->video_width, + settings->video_height, + AV_PIX_FMT_RGBA, + SWS_BICUBIC, + NULL, + NULL, + NULL); + + av->video_frame_src_size = 4*av->video_codec_context->width*av->video_codec_context->height; + av->video_frame_dst_size = 4*settings->video_width*settings->video_height; + } + + av_init_packet(&packet); + av->frame = av_frame_alloc(); + if (av->frame == NULL) { + return false; + } + + settings->audio_samples = NULL; + settings->audio_sample_count = 0; + settings->video_frames = NULL; + settings->video_frame_count = 0; + + return true; +} + +static void poll_av_packet_audio(settings_t *settings, AVPacket *packet) +{ + av_decoder_state_t* av = &(settings->decoder_state_av); + + int frame_size, frame_sample_count; + uint8_t *buffer[1]; + + if (decode_audio_frame(av->audio_codec_context, av->frame, &frame_size, packet)) { + size_t buffer_size = sizeof(int16_t) * av->sample_count_mul * swr_get_out_samples(av->resampler, av->frame->nb_samples); + buffer[0] = malloc(buffer_size); + memset(buffer[0], 0, buffer_size); + frame_sample_count = swr_convert(av->resampler, buffer, av->frame->nb_samples, (const uint8_t**)av->frame->data, av->frame->nb_samples); + settings->audio_samples = realloc(settings->audio_samples, (settings->audio_sample_count + ((frame_sample_count + 4032) * av->sample_count_mul)) * sizeof(int16_t)); + memmove(&(settings->audio_samples[settings->audio_sample_count]), buffer[0], sizeof(int16_t) * frame_sample_count * av->sample_count_mul); + settings->audio_sample_count += frame_sample_count * av->sample_count_mul; + free(buffer[0]); + } +} + +static void poll_av_packet_video(settings_t *settings, AVPacket *packet) +{ + av_decoder_state_t* av = &(settings->decoder_state_av); + + int frame_size; + + if (decode_video_frame(av->video_codec_context, av->frame, &frame_size, packet)) { + double pts = (((double)av->frame->pts)*(double)av->video_stream->time_base.num)/av->video_stream->time_base.den; + //fprintf(stderr, "%f\n", pts); + // Drop frames with negative PTS values + if(pts < 0.0) { + // do nothing + return; + } + if((settings->video_frame_count) >= 1 && pts < av->video_next_pts) { + // do nothing + return; + } + if((settings->video_frame_count) < 1) { + av->video_next_pts = pts; + } + + double pts_step = ((double)1.0*(double)settings->video_fps_den)/(double)settings->video_fps_num; + //fprintf(stderr, "%d %f %f %f\n", (settings->video_frame_count), pts, av->video_next_pts, pts_step); + av->video_next_pts += pts_step; + // FIXME: increasing framerate doesn't fill it in with duplicate frames! + assert(av->video_next_pts > pts); + //size_t buffer_size = frame_count_mul; + //buffer[0] = malloc(buffer_size); + //memset(buffer[0], 0, buffer_size); + settings->video_frames = realloc(settings->video_frames, (settings->video_frame_count + 1) * av->video_frame_dst_size); + int dst_strides[1] = { + settings->video_width*4, + }; + uint8_t *dst_pointers[1] = { + (settings->video_frames) + av->video_frame_dst_size*(settings->video_frame_count), + }; + sws_scale(av->scaler, av->frame->data, av->frame->linesize, 0, av->frame->height, dst_pointers, dst_strides); + + settings->video_frame_count += 1; + //free(buffer[0]); + } +} + +static void poll_av_packet(settings_t *settings, AVPacket *packet) +{ + av_decoder_state_t* av = &(settings->decoder_state_av); + + if (packet->stream_index == av->audio_stream_index) { + poll_av_packet_audio(settings, packet); + } + else if (packet->stream_index == av->video_stream_index) { + poll_av_packet_video(settings, packet); + } +} + +bool poll_av_data(settings_t *settings) +{ + av_decoder_state_t* av = &(settings->decoder_state_av); + AVPacket packet; + + if (av_read_frame(av->format, &packet) >= 0) { + poll_av_packet(settings, &packet); + av_packet_unref(&packet); + return true; + } else { + // out is always padded out with 4032 "0" samples, this makes calculations elsewhere easier + memset((settings->audio_samples) + (settings->audio_sample_count), 0, 4032 * av->sample_count_mul * sizeof(int16_t)); + + return false; + } +} + +bool ensure_av_data(settings_t *settings, int needed_audio_samples, int needed_video_frames) +{ + // + av_decoder_state_t* av = &(settings->decoder_state_av); + + + while (settings->audio_sample_count < needed_audio_samples || settings->video_frame_count < needed_video_frames) { + //fprintf(stderr, "ensure %d -> %d, %d -> %d\n", settings->audio_sample_count, needed_audio_samples, settings->video_frame_count, needed_video_frames); + if(!poll_av_data(settings)) { + //fprintf(stderr, "cannot ensure\n"); + return false; + } + } + //fprintf(stderr, "ensure %d -> %d, %d -> %d\n", settings->audio_sample_count, needed_audio_samples, settings->video_frame_count, needed_video_frames); + + return true; +} + +void pull_all_av_data(settings_t *settings) +{ + while (poll_av_data(settings)) { + // do nothing + } + + fprintf(stderr, "Loaded %d samples.\n", settings->audio_sample_count); + fprintf(stderr, "Loaded %d frames.\n", settings->video_frame_count); +} + +void retire_av_data(settings_t *settings, int retired_audio_samples, int retired_video_frames) +{ + av_decoder_state_t* av = &(settings->decoder_state_av); + + //fprintf(stderr, "retire %d -> %d, %d -> %d\n", settings->audio_sample_count, retired_audio_samples, settings->video_frame_count, retired_video_frames); + assert(retired_audio_samples <= settings->audio_sample_count); + assert(retired_video_frames <= settings->video_frame_count); + + int sample_size = sizeof(int16_t); + if (settings->audio_sample_count > retired_audio_samples) { + memmove(settings->audio_samples, settings->audio_samples + retired_audio_samples, (settings->audio_sample_count - retired_audio_samples)*sample_size); + settings->audio_sample_count -= retired_audio_samples; + } + + int frame_size = av->video_frame_dst_size; + if (settings->video_frame_count > retired_video_frames) { + memmove(settings->video_frames, settings->video_frames + retired_video_frames*frame_size, (settings->video_frame_count - retired_video_frames)*frame_size); + settings->video_frame_count -= retired_video_frames; + } +} + +void close_av_data(settings_t *settings) +{ + av_decoder_state_t* av = &(settings->decoder_state_av); + + av_frame_free(&(av->frame)); + swr_free(&(av->resampler)); + avcodec_close(av->audio_codec_context); + avcodec_free_context(&(av->audio_codec_context)); + avformat_free_context(av->format); + + if(settings->audio_samples != NULL) { + free(settings->audio_samples); + settings->audio_samples = NULL; + } + if(settings->video_frames != NULL) { + free(settings->video_frames); + settings->video_frames = NULL; + } +} diff --git a/psxavenc/filefmt.c b/psxavenc/filefmt.c new file mode 100644 index 0000000..51db14e --- /dev/null +++ b/psxavenc/filefmt.c @@ -0,0 +1,136 @@ +/* +psxavenc: MDEC video + SPU/XA-ADPCM audio encoder frontend + +Copyright (c) 2019, 2020 Adrian "asie" Siekierka +Copyright (c) 2019 Ben "GreaseMonkey" Russell + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#include "common.h" +#include "libpsxav.h" + +static psx_audio_xa_settings_t settings_to_libpsxav_xa_audio(settings_t *settings) { + psx_audio_xa_settings_t new_settings; + new_settings.bits_per_sample = settings->bits_per_sample; + new_settings.frequency = settings->frequency; + new_settings.stereo = settings->stereo; + new_settings.file_number = settings->file_number; + new_settings.channel_number = settings->channel_number; + + switch (settings->format) { + case FORMAT_XA: + new_settings.format = PSX_AUDIO_XA_FORMAT_XA; + break; + default: + new_settings.format = PSX_AUDIO_XA_FORMAT_XACD; + break; + } + + return new_settings; +}; + +void encode_file_spu(int16_t *audio_samples, int audio_sample_count, settings_t *settings, FILE *output) { + psx_audio_encoder_state_t audio_state; + int audio_samples_per_block = psx_audio_spu_get_samples_per_block(); + uint8_t buffer[16]; + + memset(&audio_state, 0, sizeof(psx_audio_encoder_state_t)); + + for (int i = 0; i < audio_sample_count; i += audio_samples_per_block) { + int samples_length = audio_sample_count - i; + if (samples_length > audio_samples_per_block) samples_length = audio_samples_per_block; + int length = psx_audio_spu_encode(&audio_state, audio_samples + i, samples_length, buffer); + if (i == 0) { + buffer[1] = PSX_AUDIO_SPU_LOOP_START; + } else if ((i + audio_samples_per_block) >= audio_sample_count) { + buffer[1] = PSX_AUDIO_SPU_LOOP_END; + } + fwrite(buffer, length, 1, output); + } +} + +void encode_file_xa(int16_t *audio_samples, int audio_sample_count, settings_t *settings, FILE *output) { + psx_audio_xa_settings_t xa_settings = settings_to_libpsxav_xa_audio(settings); + psx_audio_encoder_state_t audio_state; + int audio_samples_per_sector = psx_audio_xa_get_samples_per_sector(xa_settings); + int av_sample_mul = settings->stereo ? 2 : 1; + uint8_t buffer[2352]; + + memset(&audio_state, 0, sizeof(psx_audio_encoder_state_t)); + + for (int i = 0; i < audio_sample_count; i += audio_samples_per_sector) { + int samples_length = audio_sample_count - i; + if (samples_length > audio_samples_per_sector) samples_length = audio_samples_per_sector; + int length = psx_audio_xa_encode(xa_settings, &audio_state, audio_samples + (i * av_sample_mul), samples_length, buffer); + if ((i + audio_samples_per_sector) >= audio_sample_count) { + psx_audio_xa_encode_finalize(xa_settings, buffer, length); + } + fwrite(buffer, length, 1, output); + } +} + +void encode_file_str(settings_t *settings, FILE *output) { + uint8_t buffer[2352*8]; + psx_audio_xa_settings_t xa_settings = settings_to_libpsxav_xa_audio(settings); + psx_audio_encoder_state_t audio_state; + int audio_samples_per_sector = psx_audio_xa_get_samples_per_sector(xa_settings); + int av_sample_mul = settings->stereo ? 2 : 1; + + memset(&audio_state, 0, sizeof(psx_audio_encoder_state_t)); + + settings->state_vid.frame_index = 0; + settings->state_vid.bits_value = 0; + settings->state_vid.bits_left = 16; + settings->state_vid.frame_block_index = 0; + settings->state_vid.frame_block_count = 0; + + settings->state_vid.frame_block_overflow_num = 0; + + // Number of total sectors per second: 150 + // Proportion of sectors for video due to A/V interleave: 7/8 + // 15FPS = (150*7/8/15) = 8.75 blocks per frame + settings->state_vid.frame_block_base_overflow = 150*7*settings->video_fps_den; + settings->state_vid.frame_block_overflow_den = 8*settings->video_fps_num; + //fprintf(stderr, "%f\n", ((double)settings->state_vid.frame_block_base_overflow)/((double)settings->state_vid.frame_block_overflow_den)); abort(); + + // FIXME: this needs an extra frame to prevent A/V desync + const int frames_needed = 2; + for (int j = 0; ensure_av_data(settings, audio_samples_per_sector*av_sample_mul*frames_needed, 1*frames_needed); j+=18) { + psx_audio_xa_encode(xa_settings, &audio_state, settings->audio_samples, audio_samples_per_sector, buffer + 2352 * 7); + + // TODO: the final buffer + for(int k = 0; k < 7; k++) { + init_sector_buffer_video(buffer + 2352*k, settings); + } + encode_block_str(settings->video_frames, settings->video_frame_count, buffer, settings); + for(int k = 0; k < 8; k++) { + int t = k + (j/18)*8 + 75*2; + + // Put the time in + buffer[0x00C + 2352*k] = ((t/75/60)%10)|(((t/75/60)/10)<<4); + buffer[0x00D + 2352*k] = (((t/75)%60)%10)|((((t/75)%60)/10)<<4); + buffer[0x00E + 2352*k] = ((t%75)%10)|(((t%75)/10)<<4); + + if(k != 7) { + calculate_edc_data(buffer + 2352*k); + } + } + retire_av_data(settings, audio_samples_per_sector*av_sample_mul, 0); + fwrite(buffer, 2352*8, 1, output); + } +} diff --git a/psxavenc/mdec.c b/psxavenc/mdec.c new file mode 100644 index 0000000..1e35bbd --- /dev/null +++ b/psxavenc/mdec.c @@ -0,0 +1,644 @@ +/* +psxavenc: MDEC video + SPU/XA-ADPCM audio encoder frontend + +Copyright (c) 2019, 2020 Adrian "asie" Siekierka +Copyright (c) 2019 Ben "GreaseMonkey" Russell + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#include "common.h" + +// high 8 bits = bit count +// low 24 bits = value +uint32_t huffman_encoding_map[0x10000]; +bool dct_done_init = false; + +#define MAKE_HUFFMAN_PAIR(zeroes, value) (((zeroes)<<10)|((+(value))&0x3FF)),(((zeroes)<<10)|((-(value))&0x3FF)) +const struct { + int c_bits; + uint32_t c_value; + uint16_t u_hword_pos; + uint16_t u_hword_neg; +} huffman_lookup[] = { + // Fuck this Huffman tree in particular --GM + 2,0x3,MAKE_HUFFMAN_PAIR(0,1), + 3,0x3,MAKE_HUFFMAN_PAIR(1,1), + 4,0x4,MAKE_HUFFMAN_PAIR(0,2), + 4,0x5,MAKE_HUFFMAN_PAIR(2,1), + 5,0x05,MAKE_HUFFMAN_PAIR(0,3), + 5,0x06,MAKE_HUFFMAN_PAIR(4,1), + 5,0x07,MAKE_HUFFMAN_PAIR(3,1), + 6,0x04,MAKE_HUFFMAN_PAIR(7,1), + 6,0x05,MAKE_HUFFMAN_PAIR(6,1), + 6,0x06,MAKE_HUFFMAN_PAIR(1,2), + 6,0x07,MAKE_HUFFMAN_PAIR(5,1), + 7,0x04,MAKE_HUFFMAN_PAIR(2,2), + 7,0x05,MAKE_HUFFMAN_PAIR(9,1), + 7,0x06,MAKE_HUFFMAN_PAIR(0,4), + 7,0x07,MAKE_HUFFMAN_PAIR(8,1), + 8,0x20,MAKE_HUFFMAN_PAIR(13,1), + 8,0x21,MAKE_HUFFMAN_PAIR(0,6), + 8,0x22,MAKE_HUFFMAN_PAIR(12,1), + 8,0x23,MAKE_HUFFMAN_PAIR(11,1), + 8,0x24,MAKE_HUFFMAN_PAIR(3,2), + 8,0x25,MAKE_HUFFMAN_PAIR(1,3), + 8,0x26,MAKE_HUFFMAN_PAIR(0,5), + 8,0x27,MAKE_HUFFMAN_PAIR(10,1), + 10,0x008,MAKE_HUFFMAN_PAIR(16,1), + 10,0x009,MAKE_HUFFMAN_PAIR(5,2), + 10,0x00A,MAKE_HUFFMAN_PAIR(0,7), + 10,0x00B,MAKE_HUFFMAN_PAIR(2,3), + 10,0x00C,MAKE_HUFFMAN_PAIR(1,4), + 10,0x00D,MAKE_HUFFMAN_PAIR(15,1), + 10,0x00E,MAKE_HUFFMAN_PAIR(14,1), + 10,0x00F,MAKE_HUFFMAN_PAIR(4,2), + 12,0x010,MAKE_HUFFMAN_PAIR(0,11), + 12,0x011,MAKE_HUFFMAN_PAIR(8,2), + 12,0x012,MAKE_HUFFMAN_PAIR(4,3), + 12,0x013,MAKE_HUFFMAN_PAIR(0,10), + 12,0x014,MAKE_HUFFMAN_PAIR(2,4), + 12,0x015,MAKE_HUFFMAN_PAIR(7,2), + 12,0x016,MAKE_HUFFMAN_PAIR(21,1), + 12,0x017,MAKE_HUFFMAN_PAIR(20,1), + 12,0x018,MAKE_HUFFMAN_PAIR(0,9), + 12,0x019,MAKE_HUFFMAN_PAIR(19,1), + 12,0x01A,MAKE_HUFFMAN_PAIR(18,1), + 12,0x01B,MAKE_HUFFMAN_PAIR(1,5), + 12,0x01C,MAKE_HUFFMAN_PAIR(3,3), + 12,0x01D,MAKE_HUFFMAN_PAIR(0,8), + 12,0x01E,MAKE_HUFFMAN_PAIR(6,2), + 12,0x01F,MAKE_HUFFMAN_PAIR(17,1), + 13,0x0010,MAKE_HUFFMAN_PAIR(10,2), + 13,0x0011,MAKE_HUFFMAN_PAIR(9,2), + 13,0x0012,MAKE_HUFFMAN_PAIR(5,3), + 13,0x0013,MAKE_HUFFMAN_PAIR(3,4), + 13,0x0014,MAKE_HUFFMAN_PAIR(2,5), + 13,0x0015,MAKE_HUFFMAN_PAIR(1,7), + 13,0x0016,MAKE_HUFFMAN_PAIR(1,6), + 13,0x0017,MAKE_HUFFMAN_PAIR(0,15), + 13,0x0018,MAKE_HUFFMAN_PAIR(0,14), + 13,0x0019,MAKE_HUFFMAN_PAIR(0,13), + 13,0x001A,MAKE_HUFFMAN_PAIR(0,12), + 13,0x001B,MAKE_HUFFMAN_PAIR(26,1), + 13,0x001C,MAKE_HUFFMAN_PAIR(25,1), + 13,0x001D,MAKE_HUFFMAN_PAIR(24,1), + 13,0x001E,MAKE_HUFFMAN_PAIR(23,1), + 13,0x001F,MAKE_HUFFMAN_PAIR(22,1), + 14,0x0010,MAKE_HUFFMAN_PAIR(0,31), + 14,0x0011,MAKE_HUFFMAN_PAIR(0,30), + 14,0x0012,MAKE_HUFFMAN_PAIR(0,29), + 14,0x0013,MAKE_HUFFMAN_PAIR(0,28), + 14,0x0014,MAKE_HUFFMAN_PAIR(0,27), + 14,0x0015,MAKE_HUFFMAN_PAIR(0,26), + 14,0x0016,MAKE_HUFFMAN_PAIR(0,25), + 14,0x0017,MAKE_HUFFMAN_PAIR(0,24), + 14,0x0018,MAKE_HUFFMAN_PAIR(0,23), + 14,0x0019,MAKE_HUFFMAN_PAIR(0,22), + 14,0x001A,MAKE_HUFFMAN_PAIR(0,21), + 14,0x001B,MAKE_HUFFMAN_PAIR(0,20), + 14,0x001C,MAKE_HUFFMAN_PAIR(0,19), + 14,0x001D,MAKE_HUFFMAN_PAIR(0,18), + 14,0x001E,MAKE_HUFFMAN_PAIR(0,17), + 14,0x001F,MAKE_HUFFMAN_PAIR(0,16), + 15,0x0010,MAKE_HUFFMAN_PAIR(0,40), + 15,0x0011,MAKE_HUFFMAN_PAIR(0,39), + 15,0x0012,MAKE_HUFFMAN_PAIR(0,38), + 15,0x0013,MAKE_HUFFMAN_PAIR(0,37), + 15,0x0014,MAKE_HUFFMAN_PAIR(0,36), + 15,0x0015,MAKE_HUFFMAN_PAIR(0,35), + 15,0x0016,MAKE_HUFFMAN_PAIR(0,34), + 15,0x0017,MAKE_HUFFMAN_PAIR(0,33), + 15,0x0018,MAKE_HUFFMAN_PAIR(0,32), + 15,0x0019,MAKE_HUFFMAN_PAIR(1,14), + 15,0x001A,MAKE_HUFFMAN_PAIR(1,13), + 15,0x001B,MAKE_HUFFMAN_PAIR(1,12), + 15,0x001C,MAKE_HUFFMAN_PAIR(1,11), + 15,0x001D,MAKE_HUFFMAN_PAIR(1,10), + 15,0x001E,MAKE_HUFFMAN_PAIR(1,9), + 15,0x001F,MAKE_HUFFMAN_PAIR(1,8), + 16,0x0010,MAKE_HUFFMAN_PAIR(1,18), + 16,0x0011,MAKE_HUFFMAN_PAIR(1,17), + 16,0x0012,MAKE_HUFFMAN_PAIR(1,16), + 16,0x0013,MAKE_HUFFMAN_PAIR(1,15), + 16,0x0014,MAKE_HUFFMAN_PAIR(6,3), + 16,0x0015,MAKE_HUFFMAN_PAIR(16,2), + 16,0x0016,MAKE_HUFFMAN_PAIR(15,2), + 16,0x0017,MAKE_HUFFMAN_PAIR(14,2), + 16,0x0018,MAKE_HUFFMAN_PAIR(13,2), + 16,0x0019,MAKE_HUFFMAN_PAIR(12,2), + 16,0x001A,MAKE_HUFFMAN_PAIR(11,2), + 16,0x001B,MAKE_HUFFMAN_PAIR(31,1), + 16,0x001C,MAKE_HUFFMAN_PAIR(30,1), + 16,0x001D,MAKE_HUFFMAN_PAIR(29,1), + 16,0x001E,MAKE_HUFFMAN_PAIR(28,1), + 16,0x001F,MAKE_HUFFMAN_PAIR(27,1), +}; +#undef MAKE_HUFFMAN_PAIR + +const uint8_t quant_dec[8*8] = { + 2, 16, 19, 22, 26, 27, 29, 34, + 16, 16, 22, 24, 27, 29, 34, 37, + 19, 22, 26, 27, 29, 34, 34, 38, + 22, 22, 26, 27, 29, 34, 37, 40, + 22, 26, 27, 29, 32, 35, 40, 48, + 26, 27, 29, 32, 35, 40, 48, 58, + 26, 27, 29, 34, 38, 46, 56, 69, + 27, 29, 35, 38, 46, 56, 69, 83, +}; + +const uint8_t dct_zigzag_table[8*8] = { + 0x00,0x01,0x05,0x06,0x0E,0x0F,0x1B,0x1C, + 0x02,0x04,0x07,0x0D,0x10,0x1A,0x1D,0x2A, + 0x03,0x08,0x0C,0x11,0x19,0x1E,0x29,0x2B, + 0x09,0x0B,0x12,0x18,0x1F,0x28,0x2C,0x35, + 0x0A,0x13,0x17,0x20,0x27,0x2D,0x34,0x36, + 0x14,0x16,0x21,0x26,0x2E,0x33,0x37,0x3C, + 0x15,0x22,0x25,0x2F,0x32,0x38,0x3B,0x3D, + 0x23,0x24,0x30,0x31,0x39,0x3A,0x3E,0x3F, +}; + +const uint8_t dct_zagzig_table[8*8] = { + 0x00,0x01,0x08,0x10,0x09,0x02,0x03,0x0A, + 0x11,0x18,0x20,0x19,0x12,0x0B,0x04,0x05, + 0x0C,0x13,0x1A,0x21,0x28,0x30,0x29,0x22, + 0x1B,0x14,0x0D,0x06,0x07,0x0E,0x15,0x1C, + 0x23,0x2A,0x31,0x38,0x39,0x32,0x2B,0x24, + 0x1D,0x16,0x0F,0x17,0x1E,0x25,0x2C,0x33, + 0x3A,0x3B,0x34,0x2D,0x26,0x1F,0x27,0x2E, + 0x35,0x3C,0x3D,0x36,0x2F,0x37,0x3E,0x3F, +}; + +const int16_t dct_scale_table[8*8] = { + +0x5A82, +0x5A82, +0x5A82, +0x5A82, +0x5A82, +0x5A82, +0x5A82, +0x5A82, + +0x7D8A, +0x6A6D, +0x471C, +0x18F8, -0x18F9, -0x471D, -0x6A6E, -0x7D8B, + +0x7641, +0x30FB, -0x30FC, -0x7642, -0x7642, -0x30FC, +0x30FB, +0x7641, + +0x6A6D, -0x18F9, -0x7D8B, -0x471D, +0x471C, +0x7D8A, +0x18F8, -0x6A6E, + +0x5A82, -0x5A83, -0x5A83, +0x5A82, +0x5A82, -0x5A83, -0x5A83, +0x5A82, + +0x471C, -0x7D8B, +0x18F8, +0x6A6D, -0x6A6E, -0x18F9, +0x7D8A, -0x471D, + +0x30FB, -0x7642, +0x7641, -0x30FC, -0x30FC, +0x7641, -0x7642, +0x30FB, + +0x18F8, -0x471D, +0x6A6D, -0x7D8B, +0x7D8A, -0x6A6E, +0x471C, -0x18F9, +}; + +static void init_dct_data(void) +{ + for(int i = 0; i <= 0xFFFF; i++) { + huffman_encoding_map[i] = ((6+16)<<24)|((0x01<<16)|(i)); + } + + for(int i = 0; i < sizeof(huffman_lookup)/sizeof(huffman_lookup[0]); i++) { + int bits = huffman_lookup[i].c_bits+1; + uint32_t base_value = huffman_lookup[i].c_value; + huffman_encoding_map[huffman_lookup[i].u_hword_pos] = (bits<<24)|(base_value<<1)|0; + huffman_encoding_map[huffman_lookup[i].u_hword_neg] = (bits<<24)|(base_value<<1)|1; + } + +} + +static void flush_bits(vid_encoder_state_t *state) +{ + if(state->bits_left < 16) { + assert(state->bytes_used < sizeof(state->unmuxed)); + state->unmuxed[state->bytes_used++] = (uint8_t)state->bits_value; + assert(state->bytes_used < sizeof(state->unmuxed)); + assert(state->bytes_used < 2016*state->frame_block_count); + state->unmuxed[state->bytes_used++] = (uint8_t)(state->bits_value>>8); + } + state->bits_left = 16; + state->bits_value = 0; +} + +static void encode_bits(vid_encoder_state_t *state, int bits, uint32_t val) +{ + assert(val < (1< 16 + // and I have no idea why, so I have to split this up --GM + if (bits > 16) { + encode_bits(state, bits-16, val>>16); + bits = 16; + val &= 0xFFFF; + } + + if (state->bits_left == 0) { + flush_bits(state); + } + + while (bits > state->bits_left) { + // Bits need truncating + uint32_t outval = val; + outval >>= bits - state->bits_left; + assert(outval < (1<<16)); + uint16_t old_value = state->bits_value; + assert((state->bits_value & outval) == 0); + state->bits_value |= (uint16_t)outval; + //fprintf(stderr, "trunc %2d %2d %08X %04X %04X\n", bits, state->bits_left, val, old_value, state->bits_value); + bits -= state->bits_left; + uint32_t mask = (1<= 1); + assert(val < (1<= 1) { + assert(bits <= 16); + // Bits may need shifting into place + uint32_t outval = val; + outval <<= state->bits_left - bits; + assert(outval < (1<<16)); + uint16_t old_value = state->bits_value; + assert((state->bits_value & outval) == 0); + state->bits_value |= (uint16_t)outval; + //fprintf(stderr, "plop %2d %2d %08X %04X %04X\n", bits, state->bits_left, val, state->bits_value); + state->bits_left -= bits; + } +} + +static void encode_ac_value(vid_encoder_state_t *state, uint16_t value) +{ + assert(0 <= value && value <= 0xFFFF); + +#if 0 + for(int i = 0; i < sizeof(huffman_lookup)/sizeof(huffman_lookup[0]); i++) { + if(value == huffman_lookup[i].u_hword_pos) { + encode_bits(state, huffman_lookup[i].c_bits+1, (((uint32_t)huffman_lookup[i].c_value)<<1)|0); + return; + } + else if(value == huffman_lookup[i].u_hword_neg) { + encode_bits(state, huffman_lookup[i].c_bits+1, (((uint32_t)huffman_lookup[i].c_value)<<1)|1); + return; + } + } + + // Use an escape + encode_bits(state, 6+16, (0x01<<16)|(0xFFFF&(uint32_t)value)); +#else + uint32_t outword = huffman_encoding_map[value]; + encode_bits(state, outword>>24, outword&0xFFFFFF); +#endif +} + +static void transform_dct_block(vid_encoder_state_t *state, int32_t *block) +{ + // Apply DCT to block + int32_t midblock[8*8]; + + for (int reps = 0; reps < 2; reps++) { + for (int i = 0; i < 8; i++) { + for (int j = 0; j < 8; j++) { + int32_t v = 0; + for(int k = 0; k < 8; k++) { + v += block[8*j+k]*dct_scale_table[8*i+k]; + } + midblock[8*i+j] = (v + (1<<((14)-1)))>>(14); + } + } + memcpy(block, midblock, sizeof(midblock)); + } + + // FIXME: Work out why the math has to go this way + block[0] /= 8; + for (int i = 0; i < 64; i++) { + // Finish reducing it + block[i] /= 4; + + // If it's below the quantisation threshold, zero it + if(abs(block[i]) < quant_dec[i]) { + block[i] = 0; + } + } + +} + +static void encode_dct_block(vid_encoder_state_t *state, int32_t *block) +{ + int dc_value = 0; + + for (int i = 0; i < 64; i++) { + // Quantise it + block[i] = (block[i])/quant_dec[i]; + + // Clamp it + if (block[i] < -0x200) { block[i] = -0x200; } + if (block[i] > +0x1FF) { block[i] = +0x1FF; } + } + + // Get DC value + dc_value = block[0]; + //dc_value = 0; + encode_bits(state, 10, dc_value&0x3FF); + + // Build RLE output + uint16_t zero_rle_data[8*8]; + int zero_rle_words = 0; + for (int i = 1, zeroes = 0; i < 64; i++) { + int ri = dct_zagzig_table[i]; + //int ri = dct_zigzag_table[i]; + if (block[ri] == 0) { + zeroes++; + } else { + zero_rle_data[zero_rle_words++] = (zeroes<<10)|(block[ri]&0x3FF); + zeroes = 0; + state->uncomp_hwords_used += 1; + } + } + + // Now Huffman-code the data + for (int i = 0; i < zero_rle_words; i++) { + encode_ac_value(state, zero_rle_data[i]); + } + + //fprintf(stderr, "dc %08X rles %2d\n", dc_value, zero_rle_words); + //assert(dc_value >= -0x200); assert(dc_value < +0x200); + + // Store end of block + encode_bits(state, 2, 0x2); + state->uncomp_hwords_used += 2; + + state->uncomp_hwords_used = (state->uncomp_hwords_used+0xF)&~0xF; +} + +static int reduce_dct_block(vid_encoder_state_t *state, int32_t *block, int32_t min_val, int *values_to_shed) +{ + // Reduce so it can all fit + int nonzeroes = 0; + + for (int i = 1; i < 64; i++) { + //int ri = dct_zigzag_table[i]; + if (block[i] != 0) { + //if (abs(block[i])+(ri>>3) < min_val+(64>>3)) { + if ((*values_to_shed) > 0 && abs(block[i]) < min_val*1) { + block[i] = 0; + (*values_to_shed)--; + } else { + nonzeroes++; + } + } + } + + // Factor in DC + EOF values + return nonzeroes+2; +} + +static void encode_frame_str(uint8_t *video_frames, int video_frame_count, uint8_t *output, settings_t *settings) +{ + int pitch = settings->video_width*4; + int real_index = (settings->state_vid.frame_index-1); + if (real_index > video_frame_count-1) { + real_index = video_frame_count-1; + } + //uint8_t *video_frame = video_frames + settings->video_width*settings->video_height*4*real_index; + uint8_t *video_frame = video_frames; + + if (!dct_done_init) { + init_dct_data(); + dct_done_init = true; + } + + if (settings->state_vid.dct_block_lists[0] == NULL) { + int dct_block_count_x = (settings->video_width+15)/16; + int dct_block_count_y = (settings->video_height+15)/16; + int dct_block_size = dct_block_count_x*dct_block_count_y*sizeof(int32_t)*8*8; + for (int i = 0; i < 6; i++) { + settings->state_vid.dct_block_lists[i] = malloc(dct_block_size); + } + } + + memset(settings->state_vid.unmuxed, 0, sizeof(settings->state_vid.unmuxed)); + + settings->state_vid.quant_scale = 1; + settings->state_vid.uncomp_hwords_used = 0; + settings->state_vid.bytes_used = 8; + settings->state_vid.blocks_used = 0; + + // TODO: non-16x16-aligned videos + assert((settings->video_width % 16) == 0); + assert((settings->video_height % 16) == 0); + + // Do the initial transform + for(int fx = 0; fx < settings->video_width; fx += 16) { + for(int fy = 0; fy < settings->video_height; fy += 16) { + // Order: Cr Cb [Y1|Y2\nY3|Y4] + int block_offs = 8*8*((fy>>4)*((settings->video_width+15)/16)+(fx>>4)); + int32_t *blocks[6] = { + settings->state_vid.dct_block_lists[0] + block_offs, + settings->state_vid.dct_block_lists[1] + block_offs, + settings->state_vid.dct_block_lists[2] + block_offs, + settings->state_vid.dct_block_lists[3] + block_offs, + settings->state_vid.dct_block_lists[4] + block_offs, + settings->state_vid.dct_block_lists[5] + block_offs, + }; + + for(int y = 0; y < 8; y++) { + for(int x = 0; x < 8; x++) { + int k = y*8+x; + + int cr = 0; + int cg = 0; + int cb = 0; + for(int cy = 0; cy < 2; cy++) { + for(int cx = 0; cx < 2; cx++) { + int coffs = pitch*(fy+y*2+cy) + 4*(fx+x*2+cx); + cr += video_frame[coffs+0]; + cg += video_frame[coffs+1]; + cb += video_frame[coffs+2]; + } + } + + // TODO: Get the real math for this + int cluma = cr+cg*2+cb; +#if 1 + blocks[0][k] = ((cr<<2) - cluma + (1<<(4-1)))>>4; + blocks[1][k] = ((cb<<2) - cluma + (1<<(4-1)))>>4; +#else + blocks[0][k] = 0; + blocks[1][k] = 0; +#endif + + for(int ly = 0; ly < 2; ly++) { + for(int lx = 0; lx < 2; lx++) { + int loffs = pitch*(fy+ly*8+y) + 4*(fx+lx*8+x); + int lr = video_frame[loffs+0]; + int lg = video_frame[loffs+1]; + int lb = video_frame[loffs+2]; + + // TODO: Get the real math for this + int lluma = (lr+lg*2+lb+2)-0x200; + if(lluma < -0x200) { lluma = -0x200; } + if(lluma > +0x1FF) { lluma = +0x1FF; } + lluma >>= 1; + blocks[2+2*ly+lx][k] = lluma; + } + } + } + } + for(int i = 0; i < 6; i++) { + transform_dct_block(&(settings->state_vid), blocks[i]); + } + } + } + + // Now reduce all the blocks + // TODO: Base this on actual bit count + //const int accum_threshold = 6500; + const int accum_threshold = 1025*settings->state_vid.frame_block_count; + //const int accum_threshold = 900*settings->state_vid.frame_block_count; + int values_to_shed = 0; + for(int min_val = 0;; min_val += 1) { + int accum = 0; + for(int fx = 0; fx < settings->video_width; fx += 16) { + for(int fy = 0; fy < settings->video_height; fy += 16) { + // Order: Cr Cb [Y1|Y2\nY3|Y4] + int block_offs = 8*8*((fy>>4)*((settings->video_width+15)/16)+(fx>>4)); + int32_t *blocks[6] = { + settings->state_vid.dct_block_lists[0] + block_offs, + settings->state_vid.dct_block_lists[1] + block_offs, + settings->state_vid.dct_block_lists[2] + block_offs, + settings->state_vid.dct_block_lists[3] + block_offs, + settings->state_vid.dct_block_lists[4] + block_offs, + settings->state_vid.dct_block_lists[5] + block_offs, + }; + const int luma_reduce_mul = 8; + const int chroma_reduce_mul = 8; + for(int i = 6-1; i >= 0; i--) { + accum += reduce_dct_block(&(settings->state_vid), blocks[i], (i < 2 ? min_val*luma_reduce_mul+1 : min_val*chroma_reduce_mul+1), &values_to_shed); + } + } + } + + if(accum <= accum_threshold) { + break; + } + + values_to_shed = accum - accum_threshold; + } + + // Now encode all the blocks + for(int fx = 0; fx < settings->video_width; fx += 16) { + for(int fy = 0; fy < settings->video_height; fy += 16) { + // Order: Cr Cb [Y1|Y2\nY3|Y4] + int block_offs = 8*8*((fy>>4)*((settings->video_width+15)/16)+(fx>>4)); + int32_t *blocks[6] = { + settings->state_vid.dct_block_lists[0] + block_offs, + settings->state_vid.dct_block_lists[1] + block_offs, + settings->state_vid.dct_block_lists[2] + block_offs, + settings->state_vid.dct_block_lists[3] + block_offs, + settings->state_vid.dct_block_lists[4] + block_offs, + settings->state_vid.dct_block_lists[5] + block_offs, + }; + for(int i = 0; i < 6; i++) { + encode_dct_block(&(settings->state_vid), blocks[i]); + } + } + } + + encode_bits(&(settings->state_vid), 10, 0x1FF); + encode_bits(&(settings->state_vid), 2, 0x2); + settings->state_vid.uncomp_hwords_used += 2; + settings->state_vid.uncomp_hwords_used = (settings->state_vid.uncomp_hwords_used+0xF)&~0xF; + + flush_bits(&(settings->state_vid)); + + settings->state_vid.blocks_used = ((settings->state_vid.uncomp_hwords_used+0xF)&~0xF)>>4; + + // We need a multiple of 4 + settings->state_vid.bytes_used = (settings->state_vid.bytes_used+0x3)&~0x3; + + // Build the demuxed header + settings->state_vid.unmuxed[0x000] = (uint8_t)settings->state_vid.blocks_used; + settings->state_vid.unmuxed[0x001] = (uint8_t)(settings->state_vid.blocks_used>>8); + settings->state_vid.unmuxed[0x002] = (uint8_t)0x00; + settings->state_vid.unmuxed[0x003] = (uint8_t)0x38; + settings->state_vid.unmuxed[0x004] = (uint8_t)settings->state_vid.quant_scale; + settings->state_vid.unmuxed[0x005] = (uint8_t)(settings->state_vid.quant_scale>>8); + settings->state_vid.unmuxed[0x006] = 0x02; // Version 2 + settings->state_vid.unmuxed[0x007] = 0x00; + + retire_av_data(settings, 0, 1); +} + +void encode_block_str(uint8_t *video_frames, int video_frame_count, uint8_t *output, settings_t *settings) +{ + uint8_t header[32]; + memset(header, 0, sizeof(header)); + + for(int i = 0; i < 7; i++) { + while(settings->state_vid.frame_block_index >= settings->state_vid.frame_block_count) { + settings->state_vid.frame_index++; + // TODO: work out an optimal block count for this + // TODO: calculate this all based on FPS + settings->state_vid.frame_block_overflow_num += settings->state_vid.frame_block_base_overflow; + settings->state_vid.frame_block_count = settings->state_vid.frame_block_overflow_num / settings->state_vid.frame_block_overflow_den; + settings->state_vid.frame_block_overflow_num %= settings->state_vid.frame_block_overflow_den; + settings->state_vid.frame_block_index = 0; + encode_frame_str(video_frames, video_frame_count, output, settings); + } + // Header: MDEC0 register + header[0x000] = 0x60; + header[0x001] = 0x01; + header[0x002] = 0x01; + header[0x003] = 0x80; + + // Muxed chunk index/count + int chunk_index = settings->state_vid.frame_block_index; + int chunk_count = settings->state_vid.frame_block_count; + header[0x004] = (uint8_t)chunk_index; + header[0x005] = (uint8_t)(chunk_index>>8); + header[0x006] = (uint8_t)chunk_count; + header[0x007] = (uint8_t)(chunk_count>>8); + + // Frame index + header[0x008] = (uint8_t)settings->state_vid.frame_index; + header[0x009] = (uint8_t)(settings->state_vid.frame_index>>8); + header[0x00A] = (uint8_t)(settings->state_vid.frame_index>>16); + header[0x00B] = (uint8_t)(settings->state_vid.frame_index>>24); + + // Video frame size + header[0x010] = (uint8_t)settings->video_width; + header[0x011] = (uint8_t)(settings->video_width>>8); + header[0x012] = (uint8_t)settings->video_height; + header[0x013] = (uint8_t)(settings->video_height>>8); + + // 32-byte blocks required for MDEC data + header[0x014] = (uint8_t)settings->state_vid.blocks_used; + header[0x015] = (uint8_t)(settings->state_vid.blocks_used>>8); + + // Some weird thing + header[0x016] = 0x00; + header[0x017] = 0x38; + + // Quantization scale + header[0x018] = (uint8_t)settings->state_vid.quant_scale; + header[0x019] = (uint8_t)(settings->state_vid.quant_scale>>8); + + // Version + header[0x01A] = 0x02; // Version 2 + header[0x01B] = 0x00; + + // Demuxed bytes used as a multiple of 4 + header[0x00C] = (uint8_t)settings->state_vid.bytes_used; + header[0x00D] = (uint8_t)(settings->state_vid.bytes_used>>8); + header[0x00E] = (uint8_t)(settings->state_vid.bytes_used>>16); + header[0x00F] = (uint8_t)(settings->state_vid.bytes_used>>24); + + memcpy(output + 2352*i + 0x018, header, sizeof(header)); + memcpy(output + 2352*i + 0x018 + 0x020, settings->state_vid.unmuxed + 2016*settings->state_vid.frame_block_index, 2016); + + settings->state_vid.frame_block_index++; + } +} diff --git a/psxavenc/psxavenc.c b/psxavenc/psxavenc.c new file mode 100644 index 0000000..8ee15ba --- /dev/null +++ b/psxavenc/psxavenc.c @@ -0,0 +1,187 @@ +/* +psxavenc: MDEC video + SPU/XA-ADPCM audio encoder frontend + +Copyright (c) 2019, 2020 Adrian "asie" Siekierka +Copyright (c) 2019 Ben "GreaseMonkey" Russell + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +#include "common.h" + +void print_help(void) { + fprintf(stderr, "Usage: psxavenc [-f freq] [-b bitdepth] [-c channels] [-F num] [-C num] [-t xa|xacd|spu|str2] \n\n"); + fprintf(stderr, " -f freq Use specified frequency\n"); + fprintf(stderr, " -t format Use specified output type:\n"); + fprintf(stderr, " xa [A.] .xa 2336-byte sectors\n"); + fprintf(stderr, " xacd [A.] .xa 2352-byte sectors\n"); + fprintf(stderr, " spu [A.] raw SPU-ADPCM data\n"); + fprintf(stderr, " str2 [AV] v2 .str video 2352-byte sectors\n"); + fprintf(stderr, " -b bitdepth Use specified bit depth (only 4 bits supported)\n"); + fprintf(stderr, " -c channels Use specified channel count (1 or 2)\n"); + fprintf(stderr, " -F num [.xa] Set the file number to num (0-255)\n"); + fprintf(stderr, " -C num [.xa] Set the channel number to num (0-31)\n"); +} + +int parse_args(settings_t* settings, int argc, char** argv) { + int c; + while ((c = getopt(argc, argv, "t:f:b:c:F:C:")) != -1) { + switch (c) { + case 't': { + if (strcmp(optarg, "xa") == 0) { + settings->format = FORMAT_XA; + } else if (strcmp(optarg, "xacd") == 0) { + settings->format = FORMAT_XACD; + } else if (strcmp(optarg, "spu") == 0) { + settings->format = FORMAT_SPU; + } else if (strcmp(optarg, "str2") == 0) { + settings->format = FORMAT_STR2; + } else { + fprintf(stderr, "Invalid format: %s\n", optarg); + return -1; + } + } break; + case 'f': { + settings->frequency = atoi(optarg); + } break; + case 'b': { + settings->bits_per_sample = atoi(optarg); + if (settings->bits_per_sample != 4) { + fprintf(stderr, "Invalid bit depth: %d\n", settings->frequency); + return -1; + } + } break; + case 'c': { + int ch = atoi(optarg); + if (ch <= 0 || ch > 2) { + fprintf(stderr, "Invalid channel count: %d\n", ch); + return -1; + } + settings->stereo = (ch == 2 ? 1 : 0); + } break; + case 'F': { + settings->file_number = atoi(optarg); + if (settings->file_number < 0 || settings->file_number > 255) { + fprintf(stderr, "Invalid file number: %d\n", settings->file_number); + return -1; + } + } break; + case 'C': { + settings->channel_number = atoi(optarg); + if (settings->channel_number < 0 || settings->channel_number > 31) { + fprintf(stderr, "Invalid channel number: %d\n", settings->channel_number); + return -1; + } + } break; + case '?': + case 'h': { + print_help(); + return -1; + } break; + } + } + + if (settings->format == FORMAT_XA || settings->format == FORMAT_XACD) { + if (settings->frequency != PSX_AUDIO_XA_FREQ_SINGLE && settings->frequency != PSX_AUDIO_XA_FREQ_DOUBLE) { + fprintf(stderr, "Invalid frequency: %d Hz\n", settings->frequency); + return -1; + } + } + + if (settings->format == FORMAT_SPU) { + settings->stereo = false; + } + + return optind; +} + +int main(int argc, char **argv) { + settings_t settings; + int arg_offset; + FILE* output; + + memset(&settings,0,sizeof(settings_t)); + + settings.file_number = 0; + settings.channel_number = 0; + settings.stereo = true; + settings.frequency = PSX_AUDIO_XA_FREQ_DOUBLE; + settings.bits_per_sample = 4; + + settings.video_width = 320; + settings.video_height = 240; + + settings.audio_samples = NULL; + settings.audio_sample_count = 0; + settings.video_frames = NULL; + settings.video_frame_count = 0; + + // TODO: make this adjustable + // also for some reason ffmpeg seems to hard-code the framerate to 15fps + settings.video_fps_num = 15; + settings.video_fps_den = 1; + for(int i = 0; i < 6; i++) { + settings.state_vid.dct_block_lists[i] = NULL; + } + + arg_offset = parse_args(&settings, argc, argv); + if (arg_offset < 0) { + return 1; + } else if (argc < arg_offset + 2) { + print_help(); + return 1; + } + + fprintf(stderr, "Using settings: %d Hz @ %d bit depth, %s. F%d C%d\n", + settings.frequency, settings.bits_per_sample, + settings.stereo ? "stereo" : "mono", + settings.file_number, settings.channel_number + ); + + bool did_open_data = open_av_data(argv[arg_offset + 0], &settings); + if (!did_open_data) { + fprintf(stderr, "Could not open input file!\n"); + return 1; + } + + output = fopen(argv[arg_offset + 1], "wb"); + if (output == NULL) { + fprintf(stderr, "Could not open output file!\n"); + return 1; + } + + int av_sample_mul = settings.stereo ? 2 : 1; + + switch (settings.format) { + case FORMAT_XA: + case FORMAT_XACD: + pull_all_av_data(&settings); + encode_file_xa(settings.audio_samples, settings.audio_sample_count / av_sample_mul, &settings, output); + break; + case FORMAT_SPU: + pull_all_av_data(&settings); + encode_file_spu(settings.audio_samples, settings.audio_sample_count / av_sample_mul, &settings, output); + break; + case FORMAT_STR2: + encode_file_str(&settings, output); + break; + } + + fclose(output); + close_av_data(&settings); + return 0; +}