├── .editorconfig ├── .github ├── scripts │ ├── build.sh │ └── mingw-cross.txt └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── libpsxav ├── adpcm.c ├── cdrom.c └── libpsxav.h ├── meson.build └── psxavenc ├── args.c ├── args.h ├── decoding.c ├── decoding.h ├── filefmt.c ├── filefmt.h ├── main.c ├── mdec.c └── mdec.h /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | charset = utf-8 7 | end_of_line = lf 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.github/scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ROOT_DIR="$(pwd)" 4 | FFMPEG_VERSION="7.1.1" 5 | NUM_JOBS="4" 6 | 7 | if [ $# -eq 1 ]; then 8 | PACKAGE_NAME="$1" 9 | FFMPEG_OPTIONS="" 10 | PSXAVENC_OPTIONS="" 11 | elif [ $# -eq 3 ]; then 12 | PACKAGE_NAME="$1" 13 | FFMPEG_OPTIONS="--arch=x86 --target-os=mingw32 --cross-prefix=$2-" 14 | PSXAVENC_OPTIONS="--cross-file $3" 15 | else 16 | echo "Usage: $0 [cross prefix] [cross file]" 17 | exit 0 18 | fi 19 | 20 | ## Download FFmpeg 21 | 22 | if [ ! -d ffmpeg-$FFMPEG_VERSION ]; then 23 | wget "https://ffmpeg.org/releases/ffmpeg-$FFMPEG_VERSION.tar.xz" \ 24 | || exit 1 25 | tar Jxf ffmpeg-$FFMPEG_VERSION.tar.xz \ 26 | || exit 1 27 | 28 | rm -f ffmpeg-$FFMPEG_VERSION.tar.xz 29 | fi 30 | 31 | ## Build FFmpeg 32 | 33 | mkdir -p ffmpeg-build 34 | cd ffmpeg-build 35 | 36 | ../ffmpeg-$FFMPEG_VERSION/configure \ 37 | --prefix="$ROOT_DIR/ffmpeg-dist" \ 38 | $FFMPEG_OPTIONS \ 39 | --enable-gpl \ 40 | --enable-version3 \ 41 | --enable-static \ 42 | --disable-shared \ 43 | --enable-small \ 44 | --disable-programs \ 45 | --disable-doc \ 46 | --disable-avdevice \ 47 | --disable-postproc \ 48 | --disable-avfilter \ 49 | --disable-network \ 50 | --disable-encoders \ 51 | --disable-hwaccels \ 52 | --disable-muxers \ 53 | --disable-bsfs \ 54 | --disable-devices \ 55 | --disable-filters \ 56 | --disable-mmx \ 57 | || exit 2 58 | make -j $NUM_JOBS \ 59 | || exit 2 60 | make install \ 61 | || exit 2 62 | 63 | cd .. 64 | rm -rf ffmpeg-build 65 | 66 | ## Build psxavenc 67 | 68 | meson setup \ 69 | --buildtype release \ 70 | -Db_lto=true \ 71 | --strip \ 72 | --prefix $ROOT_DIR/psxavenc-dist \ 73 | --pkg-config-path $ROOT_DIR/ffmpeg-dist/lib/pkgconfig \ 74 | $PSXAVENC_OPTIONS \ 75 | psxavenc-build \ 76 | psxavenc \ 77 | || exit 3 78 | meson compile -C psxavenc-build \ 79 | || exit 3 80 | meson install -C psxavenc-build \ 81 | || exit 3 82 | 83 | rm -rf ffmpeg-dist psxavenc-build 84 | 85 | ## Package psxavenc 86 | 87 | cd psxavenc-dist 88 | 89 | zip -9 -r ../$PACKAGE_NAME.zip . \ 90 | || exit 4 91 | 92 | cd .. 93 | rm -rf psxavenc-dist 94 | -------------------------------------------------------------------------------- /.github/scripts/mingw-cross.txt: -------------------------------------------------------------------------------- 1 | [binaries] 2 | c = 'x86_64-w64-mingw32-gcc' 3 | cpp = 'x86_64-w64-mingw32-g++' 4 | ar = 'x86_64-w64-mingw32-ar' 5 | strip = 'x86_64-w64-mingw32-strip' 6 | pkgconfig = 'pkg-config' 7 | 8 | [host_machine] 9 | system = 'windows' 10 | cpu_family = 'x86_64' 11 | cpu = 'x86_64' 12 | endian = 'little' 13 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Build psxavenc 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | build: 7 | name: Build and create release 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Install prerequisites 12 | run: | 13 | sudo apt-get update -y 14 | sudo apt-get install -y --no-install-recommends meson ninja-build gcc-mingw-w64-x86-64 15 | 16 | - name: Fetch repo contents 17 | uses: actions/checkout@v3 18 | with: 19 | path: psxavenc 20 | fetch-depth: 0 21 | 22 | - name: Build psxavenc for Windows 23 | run: | 24 | psxavenc/.github/scripts/build.sh psxavenc-windows x86_64-w64-mingw32 psxavenc/.github/scripts/mingw-cross.txt 25 | 26 | - name: Upload Windows build artifacts 27 | uses: actions/upload-artifact@v4 28 | with: 29 | name: psxavenc-windows 30 | path: psxavenc-windows.zip 31 | 32 | - name: Build psxavenc for Linux 33 | run: | 34 | psxavenc/.github/scripts/build.sh psxavenc-linux 35 | 36 | - name: Upload Linux build artifacts 37 | uses: actions/upload-artifact@v4 38 | with: 39 | name: psxavenc-linux 40 | path: psxavenc-linux.zip 41 | 42 | - name: Publish release 43 | if: ${{ github.ref_type == 'tag' }} 44 | uses: softprops/action-gh-release@v1 45 | with: 46 | #fail_on_unmatched_files: true 47 | files: | 48 | psxavenc-windows.zip 49 | psxavenc-linux.zip 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | desktop.ini 2 | .DS_Store 3 | .vscode/ 4 | build/ 5 | .cache/ 6 | *.code-workspace 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Ben "GreaseMonkey" Russell 2 | Copyright (c) 2019, 2020, 2023 Adrian "asie" Siekierka 3 | 4 | This software is provided 'as-is', without any express or implied 5 | warranty. In no event will the authors be held liable for any damages 6 | arising from the use of this software. 7 | 8 | Permission is granted to anyone to use this software for any purpose, 9 | including commercial applications, and to alter it and redistribute it 10 | freely, subject to the following restrictions: 11 | 12 | 1. The origin of this software must not be misrepresented; you must not 13 | claim that you wrote the original software. If you use this software 14 | in a product, an acknowledgment in the product documentation would be 15 | appreciated but is not required. 16 | 2. Altered source versions must be plainly marked as such, and must not be 17 | misrepresented as being the original software. 18 | 3. This notice may not be removed or altered from any source distribution. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # psxavenc 3 | 4 | psxavenc is an open-source command-line tool for encoding audio and video data 5 | into formats commonly used on the original PlayStation and PlayStation 2. 6 | 7 | ## Installation 8 | 9 | Requirements: 10 | 11 | - a recent version of FFmpeg libraries (`libavformat`, `libavcodec`, 12 | `libavutil`, `libswresample`, `libswscale`); 13 | - a recent version of Meson. 14 | 15 | ```shell 16 | $ meson setup build 17 | $ meson compile -C build 18 | $ meson install -C build 19 | ``` 20 | 21 | ## Usage 22 | 23 | Run `psxavenc -h`. 24 | 25 | ### Examples 26 | 27 | Rescale a video file to ≤320x240 pixels (preserving aspect ratio) and encode it 28 | into a 15 fps version 2 .str file with 37800 Hz 4-bit stereo audio and 2352-byte 29 | sectors, meant to be played at 2x CD-ROM speed: 30 | 31 | ```shell 32 | $ psxavenc -t strcd -v v2 -f 37800 -b 4 -c 2 -s 320x240 -r 15 -x 2 in.mp4 out.str 33 | ``` 34 | 35 | Convert a mono audio sample to 22050 Hz raw SPU-ADPCM data: 36 | 37 | ```shell 38 | $ psxavenc -t spu -f 22050 in.ogg out.snd 39 | ``` 40 | 41 | Convert a stereo audio file to a 44100 Hz interleaved .vag file with 2048-byte 42 | interleave and loop flags set at the end of each interleaved chunk: 43 | 44 | ```shell 45 | $ psxavenc -t vagi -f 44100 -c 2 -L -i 2048 in.wav out.vag 46 | ``` 47 | 48 | ## Supported output formats 49 | 50 | The output format must be set using the `-t` option. 51 | 52 | | Format | Audio codec | Audio channels | Video codec | Sector size | 53 | | :------ | :------------------- | :------------- | :------------ | :---------- | 54 | | `xa` | XA-ADPCM | 1 or 2 | | 2336 bytes | 55 | | `xacd` | XA-ADPCM | 1 or 2 | | 2352 bytes | 56 | | `spu` | SPU-ADPCM | 1 | | | 57 | | `vag` | SPU-ADPCM | 1 | | | 58 | | `spui` | SPU-ADPCM | Any | | | 59 | | `vagi` | SPU-ADPCM | Any | | | 60 | | `str` | XA-ADPCM (optional) | 1 or 2 | BS v2/v3/v3dc | 2336 bytes | 61 | | `strcd` | XA-ADPCM (optional) | 1 or 2 | BS v2/v3/v3dc | 2352 bytes | 62 | | `strv` | | | BS v2/v3/v3dc | 2048 bytes | 63 | | `sbs` | | | BS v2/v3/v3dc | | 64 | 65 | Notes: 66 | 67 | - The `xa`, `xacd`, `str` and `strcd` formats will output files with 2336- or 68 | 2352-byte CD-ROM sectors, containing the appropriate CD-XA subheaders and 69 | dummy EDC/ECC placeholders in addition to the actual sector data. Such files 70 | **cannot be added to a disc image as-is** and must instead be parsed by an 71 | authoring tool capable of rebuilding the EDC/ECC data (as it is dependent on 72 | the file's absolute location on the disc) and generating a Mode 2 CD-ROM image 73 | with "native" 2352-byte sectors. 74 | - Similarly, files generated with `-t xa` or `-t xacd` **must be interleaved** 75 | **with other XA-ADPCM tracks or empty padding using an external tool** before 76 | they can be played. 77 | - `vag` and `vagi` are similar to `spu` and `spui` respectively, but add a .vag 78 | header at the beginning of the file. The header is always 48 bytes long for 79 | `vag` files, while in the case of `vagi` files it is padded to the size 80 | specified using the `-a` option (2048 bytes by default). Note that `vagi` 81 | files with more than 2 channels and/or alignment other than 2048 bytes are not 82 | standardized. 83 | - ~~The `strspu` format encodes the input file's audio track as a series of~~ 84 | ~~custom .str chunks (type ID `0x0001` by default) holding interleaved~~ 85 | ~~SPU-ADPCM data in the same format as `spui`, rather than XA-ADPCM. As .str~~ 86 | ~~chunks do not require custom XA subheaders, a file with standard 2048-byte~~ 87 | ~~sectors that does not need any special handling will be generated.~~ *This* 88 | *format has not yet been implemented.* 89 | - The `strv` format disables audio altogether and is equivalent to `strspu` on 90 | an input file with no audio track. 91 | - The `sbs` format (used in some System 573 games) consists of a series of 92 | concatenated BS frames, each padded to the size specified by the `-a` option 93 | (the default setting is 8192 bytes), with no additional headers besides the BS 94 | frame headers. 95 | 96 | ## Supported video codecs 97 | 98 | All formats with a video track (`str`, `strcd`, `strv` and `sbs`) can use any of 99 | the codecs listed below. The codec can be set using the `-v` option. 100 | 101 | | Codec | Supported by | Typ. decoder CPU usage | 102 | | :------------- | :-------------------- | :--------------------- | 103 | | `v2` (default) | All players/decoders | Medium | 104 | | `v3` | Most players/decoders | High | 105 | | `v3dc` | Few players/decoders | High | 106 | 107 | Notes: 108 | 109 | - The `v3dc` format is a variant of `v3` with a slightly better compression 110 | ratio, however most tools and playback libraries (including FFmpeg, jPSXdec 111 | and earlier versions of Sony's own BS decoder) are unable to decode it 112 | correctly; its use is thus highly discouraged. Refer to 113 | [the psx-spx section on DC coefficient encoding](https://psx-spx.consoledev.net/cdromfileformats/#dc-v3) 114 | for more details. 115 | -------------------------------------------------------------------------------- /libpsxav/adpcm.c: -------------------------------------------------------------------------------- 1 | /* 2 | libpsxav: MDEC video + SPU/XA-ADPCM audio library 3 | 4 | Copyright (c) 2019, 2020 Adrian "asie" Siekierka 5 | Copyright (c) 2019 Ben "GreaseMonkey" Russell 6 | Copyright (c) 2023 spicyjpeg 7 | 8 | This software is provided 'as-is', without any express or implied 9 | warranty. In no event will the authors be held liable for any damages 10 | arising from the use of this software. 11 | 12 | Permission is granted to anyone to use this software for any purpose, 13 | including commercial applications, and to alter it and redistribute it 14 | freely, subject to the following restrictions: 15 | 16 | 1. The origin of this software must not be misrepresented; you must not 17 | claim that you wrote the original software. If you use this software 18 | in a product, an acknowledgment in the product documentation would be 19 | appreciated but is not required. 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 3. This notice may not be removed or altered from any source distribution. 23 | */ 24 | 25 | #include 26 | #include 27 | #include "libpsxav.h" 28 | 29 | #define SHIFT_RANGE_4BPS 12 30 | #define SHIFT_RANGE_8BPS 8 31 | 32 | #define ADPCM_FILTER_COUNT 5 33 | #define XA_ADPCM_FILTER_COUNT 4 34 | #define SPU_ADPCM_FILTER_COUNT 5 35 | 36 | static const int16_t filter_k1[ADPCM_FILTER_COUNT] = {0, 60, 115, 98, 122}; 37 | static const int16_t filter_k2[ADPCM_FILTER_COUNT] = {0, 0, -52, -55, -60}; 38 | 39 | static int find_min_shift( 40 | const psx_audio_encoder_channel_state_t *state, 41 | const int16_t *samples, 42 | int sample_limit, 43 | int pitch, 44 | int filter, 45 | int shift_range 46 | ) { 47 | // Assumption made: 48 | // 49 | // There is value in shifting right one step further to allow the nibbles to clip. 50 | // However, given a possible shift value, there is no value in shifting one step less. 51 | // 52 | // Having said that, this is not a completely accurate model of the encoder, 53 | // so maybe we will need to shift one step less. 54 | // 55 | int prev1 = state->prev1; 56 | int prev2 = state->prev2; 57 | int k1 = filter_k1[filter]; 58 | int k2 = filter_k2[filter]; 59 | 60 | int right_shift = 0; 61 | 62 | int32_t s_min = 0; 63 | int32_t s_max = 0; 64 | for (int i = 0; i < PSX_AUDIO_SPU_SAMPLES_PER_BLOCK; i++) { 65 | int32_t raw_sample = (i >= sample_limit) ? 0 : samples[i * pitch]; 66 | int32_t previous_values = (k1*prev1 + k2*prev2 + (1<<5))>>6; 67 | int32_t sample = raw_sample - previous_values; 68 | if (sample < s_min) { s_min = sample; } 69 | if (sample > s_max) { s_max = sample; } 70 | prev2 = prev1; 71 | prev1 = raw_sample; 72 | } 73 | while(right_shift < shift_range && (s_max>>right_shift) > (+0x7FFF >> shift_range)) { right_shift += 1; }; 74 | while(right_shift < shift_range && (s_min>>right_shift) < (-0x8000 >> shift_range)) { right_shift += 1; }; 75 | 76 | int min_shift = shift_range - right_shift; 77 | assert(0 <= min_shift && min_shift <= shift_range); 78 | return min_shift; 79 | } 80 | 81 | static uint8_t attempt_to_encode( 82 | psx_audio_encoder_channel_state_t *outstate, 83 | const psx_audio_encoder_channel_state_t *instate, 84 | const int16_t *samples, 85 | int sample_limit, 86 | int pitch, 87 | uint8_t *data, 88 | int data_shift, 89 | int data_pitch, 90 | int filter, 91 | int sample_shift, 92 | int shift_range 93 | ) { 94 | uint8_t sample_mask = 0xFFFF >> shift_range; 95 | uint8_t nondata_mask = ~(sample_mask << data_shift); 96 | 97 | int min_shift = sample_shift; 98 | int k1 = filter_k1[filter]; 99 | int k2 = filter_k2[filter]; 100 | 101 | uint8_t hdr = (min_shift & 0x0F) | (filter << 4); 102 | 103 | if (outstate != instate) { 104 | memcpy(outstate, instate, sizeof(psx_audio_encoder_channel_state_t)); 105 | } 106 | 107 | outstate->mse = 0; 108 | 109 | for (int i = 0; i < PSX_AUDIO_SPU_SAMPLES_PER_BLOCK; i++) { 110 | int32_t sample = ((i >= sample_limit) ? 0 : samples[i * pitch]) + outstate->qerr; 111 | int32_t previous_values = (k1*outstate->prev1 + k2*outstate->prev2 + (1<<5))>>6; 112 | int32_t sample_enc = sample - previous_values; 113 | sample_enc <<= min_shift; 114 | sample_enc += (1<<(shift_range-1)); 115 | sample_enc >>= shift_range; 116 | if(sample_enc < (-0x8000 >> shift_range)) { sample_enc = -0x8000 >> shift_range; } 117 | if(sample_enc > (+0x7FFF >> shift_range)) { sample_enc = +0x7FFF >> shift_range; } 118 | sample_enc &= sample_mask; 119 | 120 | int32_t sample_dec = (int16_t) ((sample_enc & sample_mask) << shift_range); 121 | sample_dec >>= min_shift; 122 | sample_dec += previous_values; 123 | if (sample_dec > +0x7FFF) { sample_dec = +0x7FFF; } 124 | if (sample_dec < -0x8000) { sample_dec = -0x8000; } 125 | int64_t sample_error = sample_dec - sample; 126 | 127 | assert(sample_error < (1<<30)); 128 | assert(sample_error > -(1<<30)); 129 | 130 | data[i * data_pitch] = (data[i * data_pitch] & nondata_mask) | (sample_enc << data_shift); 131 | // FIXME: dithering is hard to predict 132 | //outstate->qerr += sample_error; 133 | outstate->mse += ((uint64_t)sample_error) * (uint64_t)sample_error; 134 | 135 | outstate->prev2 = outstate->prev1; 136 | outstate->prev1 = sample_dec; 137 | } 138 | 139 | return hdr; 140 | } 141 | 142 | static uint8_t encode( 143 | psx_audio_encoder_channel_state_t *state, 144 | const int16_t *samples, 145 | int sample_limit, 146 | int pitch, 147 | uint8_t *data, 148 | int data_shift, 149 | int data_pitch, 150 | int filter_count, 151 | int shift_range 152 | ) { 153 | psx_audio_encoder_channel_state_t proposed; 154 | int64_t best_mse = ((int64_t)1<<(int64_t)50); 155 | int best_filter = 0; 156 | int best_sample_shift = 0; 157 | 158 | for (int filter = 0; filter < filter_count; filter++) { 159 | int true_min_shift = find_min_shift(state, samples, sample_limit, pitch, filter, shift_range); 160 | 161 | // Testing has shown that the optimal shift can be off the true minimum shift 162 | // by 1 in *either* direction. 163 | // This is NOT the case when dither is used. 164 | int min_shift = true_min_shift - 1; 165 | int max_shift = true_min_shift + 1; 166 | if (min_shift < 0) { min_shift = 0; } 167 | if (max_shift > shift_range) { max_shift = shift_range; } 168 | 169 | for (int sample_shift = min_shift; sample_shift <= max_shift; sample_shift++) { 170 | // ignore header here 171 | attempt_to_encode( 172 | &proposed, state, 173 | samples, sample_limit, pitch, 174 | data, data_shift, data_pitch, 175 | filter, sample_shift, shift_range); 176 | 177 | if (best_mse > proposed.mse) { 178 | best_mse = proposed.mse; 179 | best_filter = filter; 180 | best_sample_shift = sample_shift; 181 | } 182 | } 183 | } 184 | 185 | // now go with the encoder 186 | return attempt_to_encode( 187 | state, state, 188 | samples, sample_limit, pitch, 189 | data, data_shift, data_pitch, 190 | best_filter, best_sample_shift, shift_range); 191 | } 192 | 193 | static void encode_block_xa( 194 | const int16_t *audio_samples, 195 | int audio_samples_limit, 196 | uint8_t *data, 197 | psx_audio_xa_settings_t settings, 198 | psx_audio_encoder_state_t *state 199 | ) { 200 | if (settings.bits_per_sample == 4) { 201 | if (settings.stereo) { 202 | data[0] = encode(&(state->left), audio_samples, audio_samples_limit, 2, data + 0x10, 0, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_4BPS); 203 | data[1] = encode(&(state->right), audio_samples + 1, audio_samples_limit, 2, data + 0x10, 4, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_4BPS); 204 | data[2] = encode(&(state->left), audio_samples + 56, audio_samples_limit - 28, 2, data + 0x11, 0, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_4BPS); 205 | data[3] = encode(&(state->right), audio_samples + 56 + 1, audio_samples_limit - 28, 2, data + 0x11, 4, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_4BPS); 206 | data[8] = encode(&(state->left), audio_samples + 56*2, audio_samples_limit - 28*2, 2, data + 0x12, 0, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_4BPS); 207 | data[9] = encode(&(state->right), audio_samples + 56*2 + 1, audio_samples_limit - 28*2, 2, data + 0x12, 4, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_4BPS); 208 | data[10] = encode(&(state->left), audio_samples + 56*3, audio_samples_limit - 28*3, 2, data + 0x13, 0, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_4BPS); 209 | data[11] = encode(&(state->right), audio_samples + 56*3 + 1, audio_samples_limit - 28*3, 2, data + 0x13, 4, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_4BPS); 210 | } else { 211 | data[0] = encode(&(state->left), audio_samples, audio_samples_limit, 1, data + 0x10, 0, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_4BPS); 212 | data[1] = encode(&(state->left), audio_samples + 28, audio_samples_limit - 28, 1, data + 0x10, 4, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_4BPS); 213 | data[2] = encode(&(state->left), audio_samples + 28*2, audio_samples_limit - 28*2, 1, data + 0x11, 0, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_4BPS); 214 | data[3] = encode(&(state->left), audio_samples + 28*3, audio_samples_limit - 28*3, 1, data + 0x11, 4, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_4BPS); 215 | data[8] = encode(&(state->left), audio_samples + 28*4, audio_samples_limit - 28*4, 1, data + 0x12, 0, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_4BPS); 216 | data[9] = encode(&(state->left), audio_samples + 28*5, audio_samples_limit - 28*5, 1, data + 0x12, 4, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_4BPS); 217 | data[10] = encode(&(state->left), audio_samples + 28*6, audio_samples_limit - 28*6, 1, data + 0x13, 0, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_4BPS); 218 | data[11] = encode(&(state->left), audio_samples + 28*7, audio_samples_limit - 28*7, 1, data + 0x13, 4, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_4BPS); 219 | } 220 | } else { 221 | if (settings.stereo) { 222 | data[0] = encode(&(state->left), audio_samples, audio_samples_limit, 2, data + 0x10, 0, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_8BPS); 223 | data[1] = encode(&(state->right), audio_samples + 1, audio_samples_limit, 2, data + 0x11, 0, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_8BPS); 224 | data[2] = encode(&(state->left), audio_samples + 56, audio_samples_limit - 28, 2, data + 0x12, 0, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_8BPS); 225 | data[3] = encode(&(state->right), audio_samples + 56 + 1, audio_samples_limit - 28, 2, data + 0x13, 0, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_8BPS); 226 | } else { 227 | data[0] = encode(&(state->left), audio_samples, audio_samples_limit, 1, data + 0x10, 0, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_8BPS); 228 | data[1] = encode(&(state->left), audio_samples + 28, audio_samples_limit - 28, 1, data + 0x11, 0, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_8BPS); 229 | data[2] = encode(&(state->left), audio_samples + 28*2, audio_samples_limit - 28*2, 1, data + 0x12, 0, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_8BPS); 230 | data[3] = encode(&(state->left), audio_samples + 28*3, audio_samples_limit - 28*3, 1, data + 0x13, 0, 4, XA_ADPCM_FILTER_COUNT, SHIFT_RANGE_8BPS); 231 | } 232 | } 233 | } 234 | 235 | uint32_t psx_audio_xa_get_buffer_size(psx_audio_xa_settings_t settings, int sample_count) { 236 | int sample_pitch = psx_audio_xa_get_samples_per_sector(settings); 237 | int xa_sectors = ((sample_count + sample_pitch - 1) / sample_pitch); 238 | int xa_sector_size = psx_audio_xa_get_buffer_size_per_sector(settings); 239 | return xa_sectors * xa_sector_size; 240 | } 241 | 242 | uint32_t psx_audio_spu_get_buffer_size(int sample_count) { 243 | return ((sample_count + PSX_AUDIO_SPU_SAMPLES_PER_BLOCK - 1) / PSX_AUDIO_SPU_SAMPLES_PER_BLOCK) << 4; 244 | } 245 | 246 | uint32_t psx_audio_xa_get_buffer_size_per_sector(psx_audio_xa_settings_t settings) { 247 | return settings.format == PSX_AUDIO_XA_FORMAT_XA ? 2336 : 2352; 248 | } 249 | 250 | uint32_t psx_audio_xa_get_samples_per_sector(psx_audio_xa_settings_t settings) { 251 | return (((settings.bits_per_sample == 8) ? 112 : 224) >> (settings.stereo ? 1 : 0)) * 18; 252 | } 253 | 254 | uint32_t psx_audio_xa_get_sector_interleave(psx_audio_xa_settings_t settings) { 255 | // 1/2 interleave for 37800 Hz 8-bit stereo at 1x speed 256 | int interleave = settings.stereo ? 2 : 4; 257 | if (settings.frequency == PSX_AUDIO_XA_FREQ_SINGLE) { interleave <<= 1; } 258 | if (settings.bits_per_sample == 4) { interleave <<= 1; } 259 | return interleave; 260 | } 261 | 262 | static inline void psx_audio_xa_sync_subheader_copy(psx_cdrom_sector_mode2_t *buffer) { 263 | memcpy(buffer->subheader + 1, buffer->subheader, sizeof(psx_cdrom_sector_xa_subheader_t)); 264 | } 265 | 266 | static void psx_audio_xa_encode_init_sector(psx_cdrom_sector_mode2_t *buffer, int lba, psx_audio_xa_settings_t settings) { 267 | if (settings.format == PSX_AUDIO_XA_FORMAT_XACD) 268 | psx_cdrom_init_sector((psx_cdrom_sector_t *)buffer, lba, PSX_CDROM_SECTOR_TYPE_MODE2_FORM2); 269 | 270 | buffer->subheader[0].file = settings.file_number; 271 | buffer->subheader[0].channel = settings.channel_number & PSX_CDROM_SECTOR_XA_CHANNEL_MASK; 272 | buffer->subheader[0].submode = 273 | PSX_CDROM_SECTOR_XA_SUBMODE_AUDIO 274 | | PSX_CDROM_SECTOR_XA_SUBMODE_FORM2 275 | | PSX_CDROM_SECTOR_XA_SUBMODE_RT; 276 | 277 | if (settings.stereo) 278 | buffer->subheader[0].coding |= PSX_CDROM_SECTOR_XA_CODING_STEREO; 279 | else 280 | buffer->subheader[0].coding |= PSX_CDROM_SECTOR_XA_CODING_MONO; 281 | if (settings.frequency == PSX_AUDIO_XA_FREQ_DOUBLE) 282 | buffer->subheader[0].coding |= PSX_CDROM_SECTOR_XA_CODING_FREQ_DOUBLE; 283 | else 284 | buffer->subheader[0].coding |= PSX_CDROM_SECTOR_XA_CODING_FREQ_SINGLE; 285 | if (settings.bits_per_sample == 8) 286 | buffer->subheader[0].coding |= PSX_CDROM_SECTOR_XA_CODING_BITS_8; 287 | else 288 | buffer->subheader[0].coding |= PSX_CDROM_SECTOR_XA_CODING_BITS_4; 289 | 290 | psx_audio_xa_sync_subheader_copy(buffer); 291 | } 292 | 293 | int psx_audio_xa_encode( 294 | psx_audio_xa_settings_t settings, 295 | psx_audio_encoder_state_t *state, 296 | const int16_t *samples, 297 | int sample_count, 298 | int lba, 299 | uint8_t *output 300 | ) { 301 | int sample_jump = (settings.bits_per_sample == 8) ? 112 : 224; 302 | int i, j; 303 | int xa_sector_size = psx_audio_xa_get_buffer_size_per_sector(settings); 304 | int xa_offset = PSX_CDROM_SECTOR_SIZE - xa_sector_size; 305 | uint8_t init_sector = 1; 306 | 307 | if (settings.stereo) 308 | sample_count *= 2; 309 | 310 | for (i = 0, j = 0; i < sample_count || ((j % 18) != 0); i += sample_jump, j++) { 311 | psx_cdrom_sector_mode2_t *sector_data = (psx_cdrom_sector_mode2_t*) (output + ((j/18) * xa_sector_size) - xa_offset); 312 | uint8_t *block_data = sector_data->data + ((j%18) * 0x80); 313 | 314 | if (init_sector) { 315 | psx_audio_xa_encode_init_sector(sector_data, lba, settings); 316 | init_sector = 0; 317 | } 318 | 319 | encode_block_xa(samples + i, sample_count - i, block_data, settings, state); 320 | 321 | memcpy(block_data + 4, block_data, 4); 322 | memcpy(block_data + 12, block_data + 8, 4); 323 | 324 | if ((j+1)%18 == 0) { 325 | psx_cdrom_calculate_checksums((psx_cdrom_sector_t *)sector_data, PSX_CDROM_SECTOR_TYPE_MODE2_FORM2); 326 | init_sector = 1; 327 | lba++; 328 | } 329 | } 330 | 331 | return (((j + 17) / 18) * xa_sector_size); 332 | } 333 | 334 | void psx_audio_xa_encode_finalize(psx_audio_xa_settings_t settings, uint8_t *output, int output_length) { 335 | if (output_length >= 2336) { 336 | psx_cdrom_sector_mode2_t *sector = (psx_cdrom_sector_mode2_t*) &output[output_length - PSX_CDROM_SECTOR_SIZE]; 337 | sector->subheader[0].submode |= PSX_CDROM_SECTOR_XA_SUBMODE_EOF; 338 | psx_audio_xa_sync_subheader_copy(sector); 339 | } 340 | } 341 | 342 | int psx_audio_xa_encode_simple( 343 | psx_audio_xa_settings_t settings, 344 | const int16_t *samples, 345 | int sample_count, 346 | int lba, 347 | uint8_t *output 348 | ) { 349 | psx_audio_encoder_state_t state; 350 | memset(&state, 0, sizeof(psx_audio_encoder_state_t)); 351 | int length = psx_audio_xa_encode(settings, &state, samples, sample_count, lba, output); 352 | psx_audio_xa_encode_finalize(settings, output, length); 353 | return length; 354 | } 355 | 356 | int psx_audio_spu_encode( 357 | psx_audio_encoder_channel_state_t *state, 358 | const int16_t *samples, 359 | int sample_count, 360 | int pitch, 361 | uint8_t *output 362 | ) { 363 | uint8_t prebuf[PSX_AUDIO_SPU_SAMPLES_PER_BLOCK]; 364 | uint8_t *buffer = output; 365 | 366 | for (int i = 0; i < sample_count; i += PSX_AUDIO_SPU_SAMPLES_PER_BLOCK, buffer += PSX_AUDIO_SPU_BLOCK_SIZE) { 367 | buffer[0] = encode(state, samples + i * pitch, sample_count - i, pitch, prebuf, 0, 1, SPU_ADPCM_FILTER_COUNT, SHIFT_RANGE_4BPS); 368 | buffer[1] = 0; 369 | 370 | for (int j = 0; j < PSX_AUDIO_SPU_SAMPLES_PER_BLOCK; j+=2) { 371 | buffer[2 + (j>>1)] = (prebuf[j] & 0x0F) | (prebuf[j+1] << 4); 372 | } 373 | } 374 | 375 | return buffer - output; 376 | } 377 | 378 | int psx_audio_spu_encode_simple(const int16_t *samples, int sample_count, uint8_t *output, int loop_start) { 379 | psx_audio_encoder_channel_state_t state; 380 | memset(&state, 0, sizeof(psx_audio_encoder_channel_state_t)); 381 | int length = psx_audio_spu_encode(&state, samples, sample_count, 1, output); 382 | 383 | if (length >= PSX_AUDIO_SPU_BLOCK_SIZE) { 384 | uint8_t *last_block = output + length - PSX_AUDIO_SPU_BLOCK_SIZE; 385 | 386 | if (loop_start < 0) { 387 | last_block[1] |= PSX_AUDIO_SPU_LOOP_END; 388 | 389 | // Insert trailing looping block 390 | memset(output + length, 0, PSX_AUDIO_SPU_BLOCK_SIZE); 391 | output[length + 1] = PSX_AUDIO_SPU_LOOP_START | PSX_AUDIO_SPU_LOOP_END; 392 | 393 | length += PSX_AUDIO_SPU_BLOCK_SIZE; 394 | } else { 395 | int loop_start_offset = loop_start / PSX_AUDIO_SPU_SAMPLES_PER_BLOCK * PSX_AUDIO_SPU_BLOCK_SIZE; 396 | 397 | last_block[1] |= PSX_AUDIO_SPU_LOOP_REPEAT; 398 | output[loop_start_offset + 1] |= PSX_AUDIO_SPU_LOOP_START; 399 | } 400 | } 401 | 402 | return length; 403 | } 404 | -------------------------------------------------------------------------------- /libpsxav/cdrom.c: -------------------------------------------------------------------------------- 1 | /* 2 | libpsxav: MDEC video + SPU/XA-ADPCM audio library 3 | 4 | Copyright (c) 2019, 2020 Adrian "asie" Siekierka 5 | Copyright (c) 2019 Ben "GreaseMonkey" Russell 6 | 7 | This software is provided 'as-is', without any express or implied 8 | warranty. In no event will the authors be held liable for any damages 9 | arising from the use of this software. 10 | 11 | Permission is granted to anyone to use this software for any purpose, 12 | including commercial applications, and to alter it and redistribute it 13 | freely, subject to the following restrictions: 14 | 15 | 1. The origin of this software must not be misrepresented; you must not 16 | claim that you wrote the original software. If you use this software 17 | in a product, an acknowledgment in the product documentation would be 18 | appreciated but is not required. 19 | 2. Altered source versions must be plainly marked as such, and must not be 20 | misrepresented as being the original software. 21 | 3. This notice may not be removed or altered from any source distribution. 22 | */ 23 | 24 | #include 25 | #include 26 | #include "libpsxav.h" 27 | 28 | #define EDC_CRC32_POLYNOMIAL 0xD8018001 29 | 30 | static uint32_t edc_crc32(uint8_t *data, int length) { 31 | uint32_t edc = 0; 32 | 33 | for (int i = 0; i < length; i++) { 34 | edc ^= 0xFF & (uint32_t)data[i]; 35 | 36 | for (int j = 0; j < 8; j++) 37 | edc = (edc >> 1) ^ (EDC_CRC32_POLYNOMIAL * (edc & 0x1)); 38 | } 39 | 40 | return edc; 41 | } 42 | 43 | #define TO_BCD(x) ((x) + ((x) / 10) * 6) 44 | 45 | void psx_cdrom_init_xa_subheader(psx_cdrom_sector_xa_subheader_t *subheader, psx_cdrom_sector_type_t type) { 46 | memset(subheader, 0, sizeof(psx_cdrom_sector_xa_subheader_t) * 2); 47 | subheader->submode = PSX_CDROM_SECTOR_XA_SUBMODE_DATA; 48 | 49 | if (type == PSX_CDROM_SECTOR_TYPE_MODE2_FORM2) 50 | subheader->submode |= PSX_CDROM_SECTOR_XA_SUBMODE_FORM2; 51 | 52 | memcpy(subheader + 1, subheader, sizeof(psx_cdrom_sector_xa_subheader_t)); 53 | } 54 | 55 | void psx_cdrom_init_sector(psx_cdrom_sector_t *sector, int lba, psx_cdrom_sector_type_t type) { 56 | // Sync sequence 57 | memset(sector->mode1.sync + 1, 0xff, 10); 58 | sector->mode1.sync[0x0] = 0x00; 59 | sector->mode1.sync[0xB] = 0x00; 60 | 61 | // Timecode 62 | lba += 150; 63 | sector->mode1.header.minute = TO_BCD(lba / 4500); 64 | sector->mode1.header.second = TO_BCD((lba / 75) % 60); 65 | sector->mode1.header.sector = TO_BCD(lba % 75); 66 | 67 | // Mode 68 | if (type == PSX_CDROM_SECTOR_TYPE_MODE1) { 69 | sector->mode1.header.mode = 0x01; 70 | } else { 71 | sector->mode2.header.mode = 0x02; 72 | psx_cdrom_init_xa_subheader(sector->mode2.subheader, type); 73 | } 74 | } 75 | 76 | void psx_cdrom_calculate_checksums(psx_cdrom_sector_t *sector, psx_cdrom_sector_type_t type) { 77 | uint8_t *data = (uint8_t *)sector; 78 | uint32_t edc; 79 | 80 | switch (type) { 81 | case PSX_CDROM_SECTOR_TYPE_MODE1: 82 | edc = edc_crc32(data, 0x810); 83 | 84 | data[0x810] = (uint8_t)(edc); 85 | data[0x811] = (uint8_t)(edc >> 8); 86 | data[0x812] = (uint8_t)(edc >> 16); 87 | data[0x813] = (uint8_t)(edc >> 24); 88 | memset(sector + 0x814, 0, 8); 89 | // TODO: ECC 90 | break; 91 | 92 | case PSX_CDROM_SECTOR_TYPE_MODE2_FORM1: 93 | edc = edc_crc32(data + 0x10, 0x808); 94 | 95 | data[0x818] = (uint8_t)(edc); 96 | data[0x819] = (uint8_t)(edc >> 8); 97 | data[0x81A] = (uint8_t)(edc >> 16); 98 | data[0x81B] = (uint8_t)(edc >> 24); 99 | // TODO: ECC 100 | break; 101 | 102 | case PSX_CDROM_SECTOR_TYPE_MODE2_FORM2: 103 | edc = edc_crc32(data + 0x10, 0x91C); 104 | 105 | data[0x92C] = (uint8_t)(edc); 106 | data[0x92D] = (uint8_t)(edc >> 8); 107 | data[0x92E] = (uint8_t)(edc >> 16); 108 | data[0x92F] = (uint8_t)(edc >> 24); 109 | break; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /libpsxav/libpsxav.h: -------------------------------------------------------------------------------- 1 | /* 2 | libpsxav: MDEC video + SPU/XA-ADPCM audio library 3 | 4 | Copyright (c) 2019, 2020 Adrian "asie" Siekierka 5 | Copyright (c) 2019 Ben "GreaseMonkey" Russell 6 | 7 | This software is provided 'as-is', without any express or implied 8 | warranty. In no event will the authors be held liable for any damages 9 | arising from the use of this software. 10 | 11 | Permission is granted to anyone to use this software for any purpose, 12 | including commercial applications, and to alter it and redistribute it 13 | freely, subject to the following restrictions: 14 | 15 | 1. The origin of this software must not be misrepresented; you must not 16 | claim that you wrote the original software. If you use this software 17 | in a product, an acknowledgment in the product documentation would be 18 | appreciated but is not required. 19 | 2. Altered source versions must be plainly marked as such, and must not be 20 | misrepresented as being the original software. 21 | 3. This notice may not be removed or altered from any source distribution. 22 | */ 23 | 24 | #pragma once 25 | 26 | #include 27 | #include 28 | 29 | // audio.c 30 | 31 | #define PSX_AUDIO_SPU_BLOCK_SIZE 16 32 | #define PSX_AUDIO_SPU_SAMPLES_PER_BLOCK 28 33 | 34 | enum { 35 | PSX_AUDIO_XA_FREQ_SINGLE = 18900, 36 | PSX_AUDIO_XA_FREQ_DOUBLE = 37800 37 | }; 38 | 39 | typedef enum { 40 | PSX_AUDIO_XA_FORMAT_XA, // .xa file 41 | PSX_AUDIO_XA_FORMAT_XACD // 2352-byte sector 42 | } psx_audio_xa_format_t; 43 | 44 | typedef struct { 45 | psx_audio_xa_format_t format; 46 | bool stereo; // false or true 47 | int frequency; // 18900 or 37800 Hz 48 | int bits_per_sample; // 4 or 8 49 | int file_number; // 00-FF 50 | int channel_number; // 00-1F 51 | } psx_audio_xa_settings_t; 52 | 53 | typedef struct { 54 | int qerr; // quanitisation error 55 | uint64_t mse; // mean square error 56 | int prev1, prev2; 57 | } psx_audio_encoder_channel_state_t; 58 | 59 | typedef struct { 60 | psx_audio_encoder_channel_state_t left; 61 | psx_audio_encoder_channel_state_t right; 62 | } psx_audio_encoder_state_t; 63 | 64 | enum { 65 | PSX_AUDIO_SPU_LOOP_END = 1 << 0, 66 | PSX_AUDIO_SPU_LOOP_REPEAT = 3 << 0, 67 | PSX_AUDIO_SPU_LOOP_START = 1 << 2 68 | }; 69 | 70 | uint32_t psx_audio_xa_get_buffer_size(psx_audio_xa_settings_t settings, int sample_count); 71 | uint32_t psx_audio_spu_get_buffer_size(int sample_count); 72 | uint32_t psx_audio_xa_get_buffer_size_per_sector(psx_audio_xa_settings_t settings); 73 | uint32_t psx_audio_xa_get_samples_per_sector(psx_audio_xa_settings_t settings); 74 | uint32_t psx_audio_xa_get_sector_interleave(psx_audio_xa_settings_t settings); 75 | int psx_audio_xa_encode( 76 | psx_audio_xa_settings_t settings, 77 | psx_audio_encoder_state_t *state, 78 | const int16_t *samples, 79 | int sample_count, 80 | int lba, 81 | uint8_t *output 82 | ); 83 | int psx_audio_xa_encode_simple( 84 | psx_audio_xa_settings_t settings, 85 | const int16_t *samples, 86 | int sample_count, 87 | int lba, 88 | uint8_t *output 89 | ); 90 | int psx_audio_spu_encode( 91 | psx_audio_encoder_channel_state_t *state, 92 | const int16_t *samples, 93 | int sample_count, 94 | int pitch, 95 | uint8_t *output 96 | ); 97 | int psx_audio_spu_encode_simple(const int16_t *samples, int sample_count, uint8_t *output, int loop_start); 98 | void psx_audio_xa_encode_finalize(psx_audio_xa_settings_t settings, uint8_t *output, int output_length); 99 | 100 | // cdrom.c 101 | 102 | #define PSX_CDROM_SECTOR_SIZE 2352 103 | 104 | typedef struct { 105 | uint8_t minute; 106 | uint8_t second; 107 | uint8_t sector; 108 | uint8_t mode; 109 | } psx_cdrom_sector_header_t; 110 | 111 | typedef struct { 112 | uint8_t file; 113 | uint8_t channel; 114 | uint8_t submode; 115 | uint8_t coding; 116 | } psx_cdrom_sector_xa_subheader_t; 117 | 118 | typedef struct { 119 | uint8_t sync[12]; 120 | psx_cdrom_sector_header_t header; 121 | uint8_t data[0x920]; 122 | } psx_cdrom_sector_mode1_t; 123 | 124 | typedef struct { 125 | uint8_t sync[12]; 126 | psx_cdrom_sector_header_t header; 127 | psx_cdrom_sector_xa_subheader_t subheader[2]; 128 | uint8_t data[0x918]; 129 | } psx_cdrom_sector_mode2_t; 130 | 131 | typedef union { 132 | psx_cdrom_sector_mode1_t mode1; 133 | psx_cdrom_sector_mode2_t mode2; 134 | } psx_cdrom_sector_t; 135 | 136 | _Static_assert(sizeof(psx_cdrom_sector_mode1_t) == PSX_CDROM_SECTOR_SIZE, "Invalid Mode1 sector size"); 137 | _Static_assert(sizeof(psx_cdrom_sector_mode2_t) == PSX_CDROM_SECTOR_SIZE, "Invalid Mode2 sector size"); 138 | 139 | #define PSX_CDROM_SECTOR_XA_CHANNEL_MASK 0x1F 140 | 141 | enum { 142 | PSX_CDROM_SECTOR_XA_SUBMODE_EOR = 1 << 0, 143 | PSX_CDROM_SECTOR_XA_SUBMODE_VIDEO = 1 << 1, 144 | PSX_CDROM_SECTOR_XA_SUBMODE_AUDIO = 1 << 2, 145 | PSX_CDROM_SECTOR_XA_SUBMODE_DATA = 1 << 3, 146 | PSX_CDROM_SECTOR_XA_SUBMODE_TRIGGER = 1 << 4, 147 | PSX_CDROM_SECTOR_XA_SUBMODE_FORM2 = 1 << 5, 148 | PSX_CDROM_SECTOR_XA_SUBMODE_RT = 1 << 6, 149 | PSX_CDROM_SECTOR_XA_SUBMODE_EOF = 1 << 7 150 | }; 151 | 152 | enum { 153 | PSX_CDROM_SECTOR_XA_CODING_MONO = 0 << 0, 154 | PSX_CDROM_SECTOR_XA_CODING_STEREO = 1 << 0, 155 | PSX_CDROM_SECTOR_XA_CODING_CHANNEL_MASK = 3 << 0, 156 | PSX_CDROM_SECTOR_XA_CODING_FREQ_DOUBLE = 0 << 2, 157 | PSX_CDROM_SECTOR_XA_CODING_FREQ_SINGLE = 1 << 2, 158 | PSX_CDROM_SECTOR_XA_CODING_FREQ_MASK = 3 << 2, 159 | PSX_CDROM_SECTOR_XA_CODING_BITS_4 = 0 << 4, 160 | PSX_CDROM_SECTOR_XA_CODING_BITS_8 = 1 << 4, 161 | PSX_CDROM_SECTOR_XA_CODING_BITS_MASK = 3 << 4, 162 | PSX_CDROM_SECTOR_XA_CODING_EMPHASIS = 1 << 6 163 | }; 164 | 165 | typedef enum { 166 | PSX_CDROM_SECTOR_TYPE_MODE1, 167 | PSX_CDROM_SECTOR_TYPE_MODE2_FORM1, 168 | PSX_CDROM_SECTOR_TYPE_MODE2_FORM2 169 | } psx_cdrom_sector_type_t; 170 | 171 | void psx_cdrom_init_xa_subheader(psx_cdrom_sector_xa_subheader_t *subheader, psx_cdrom_sector_type_t type); 172 | void psx_cdrom_init_sector(psx_cdrom_sector_t *sector, int lba, psx_cdrom_sector_type_t type); 173 | void psx_cdrom_calculate_checksums(psx_cdrom_sector_t *sector, psx_cdrom_sector_type_t type); 174 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('psxavenc', 'c', default_options: ['c_std=c11']) 2 | 3 | add_project_arguments('-D_POSIX_C_SOURCE=201112L', '-ffast-math', language : 'c') 4 | 5 | conf_data = configuration_data() 6 | conf_data.set('VERSION', '"' + run_command('git', '-C', meson.project_source_root(), 'describe', '--tags', '--always', '--dirty', '--match=v*', check: true).stdout().strip() + '"') 7 | configure_file(output: 'config.h', configuration: conf_data) 8 | 9 | libm_dep = meson.get_compiler('c').find_library('m') 10 | 11 | ffmpeg = [ 12 | dependency('libavformat'), 13 | dependency('libavcodec'), 14 | dependency('libavutil'), 15 | dependency('libswresample'), 16 | dependency('libswscale') 17 | ] 18 | 19 | libpsxav = static_library('psxav', [ 20 | 'libpsxav/adpcm.c', 21 | 'libpsxav/cdrom.c', 22 | 'libpsxav/libpsxav.h' 23 | ]) 24 | libpsxav_dep = declare_dependency(include_directories: include_directories('libpsxav'), link_with: libpsxav) 25 | 26 | executable('psxavenc', [ 27 | 'psxavenc/args.c', 28 | 'psxavenc/decoding.c', 29 | 'psxavenc/filefmt.c', 30 | 'psxavenc/main.c', 31 | 'psxavenc/mdec.c' 32 | ], dependencies: [libm_dep, ffmpeg, libpsxav_dep], install: true) 33 | -------------------------------------------------------------------------------- /psxavenc/args.c: -------------------------------------------------------------------------------- 1 | /* 2 | psxavenc: MDEC video + SPU/XA-ADPCM audio encoder frontend 3 | 4 | Copyright (c) 2019, 2020 Adrian "asie" Siekierka 5 | Copyright (c) 2019 Ben "GreaseMonkey" Russell 6 | Copyright (c) 2023, 2025 spicyjpeg 7 | 8 | This software is provided 'as-is', without any express or implied 9 | warranty. In no event will the authors be held liable for any damages 10 | arising from the use of this software. 11 | 12 | Permission is granted to anyone to use this software for any purpose, 13 | including commercial applications, and to alter it and redistribute it 14 | freely, subject to the following restrictions: 15 | 16 | 1. The origin of this software must not be misrepresented; you must not 17 | claim that you wrote the original software. If you use this software 18 | in a product, an acknowledgment in the product documentation would be 19 | appreciated but is not required. 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 3. This notice may not be removed or altered from any source distribution. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "args.h" 30 | #include "config.h" 31 | 32 | #define INVALID_PARAM -1 33 | 34 | static int parse_int( 35 | int *output, 36 | const char *name, 37 | const char *value, 38 | int min_value, 39 | int max_value 40 | ) { 41 | if (value == NULL) { 42 | fprintf(stderr, "Missing %s value after option\n", name); 43 | return INVALID_PARAM; 44 | } 45 | 46 | *output = strtol(value, NULL, 0); 47 | 48 | if ( 49 | (*output < min_value) || 50 | (max_value >= 0 && *output > max_value) 51 | ) { 52 | if (max_value >= 0) 53 | fprintf(stderr, "Invalid %s: %d (must be in %d-%d range)\n", name, *output, min_value, max_value); 54 | else 55 | fprintf(stderr, "Invalid %s: %d (must be %d or greater)\n", name, *output, min_value); 56 | return INVALID_PARAM; 57 | } 58 | 59 | return 2; 60 | } 61 | 62 | static int parse_int_one_of( 63 | int *output, 64 | const char *name, 65 | const char *value, 66 | int value_a, 67 | int value_b 68 | ) { 69 | if (value == NULL) { 70 | fprintf(stderr, "Missing %s value after option\n", name); 71 | return INVALID_PARAM; 72 | } 73 | 74 | *output = strtol(value, NULL, 0); 75 | 76 | if (*output != value_a && *output != value_b) { 77 | fprintf(stderr, "Invalid %s: %d (must be %d or %d)\n", name, *output, value_a, value_b); 78 | return INVALID_PARAM; 79 | } 80 | 81 | return 2; 82 | } 83 | 84 | static int parse_enum( 85 | int *output, 86 | const char *name, 87 | const char *value, 88 | const char *const *choices, 89 | int count 90 | ) { 91 | if (value == NULL) { 92 | fprintf(stderr, "Missing %s value after option\n", name); 93 | return INVALID_PARAM; 94 | } 95 | for (int i = 0; i < count; i++) { 96 | if (strcmp(value, choices[i]) == 0) { 97 | *output = i; 98 | return 2; 99 | } 100 | } 101 | 102 | fprintf( 103 | stderr, 104 | "Invalid %s: %s\n" 105 | "Must be one of the following values:\n", 106 | name, 107 | value 108 | ); 109 | for (int i = 0; i < count; i++) 110 | fprintf(stderr, " %s\n", choices[i]); 111 | return INVALID_PARAM; 112 | } 113 | 114 | static const char *const general_options_help = 115 | "General options:\n" 116 | " -h Show this help message and exit\n" 117 | " -V Show version information and exit\n" 118 | " -q Suppress all non-error messages\n" 119 | " -t format Use (or show help for) specified output format\n" 120 | " xa: [A.] XA-ADPCM, 2336-byte sectors\n" 121 | " xacd: [A.] XA-ADPCM, 2352-byte sectors\n" 122 | " spu: [A.] raw SPU-ADPCM mono data\n" 123 | " spui: [A.] raw SPU-ADPCM interleaved data\n" 124 | " vag: [A.] .vag SPU-ADPCM mono\n" 125 | " vagi: [A.] .vag SPU-ADPCM interleaved\n" 126 | " str: [AV] .str video + XA-ADPCM, 2336-byte sectors\n" 127 | " strcd: [AV] .str video + XA-ADPCM, 2352-byte sectors\n" 128 | //" strspu: [AV] .str video + SPU-ADPCM, 2048-byte sectors\n" 129 | " strv: [.V] .str video, 2048-byte sectors\n" 130 | " sbs: [.V] .sbs video\n" 131 | " -R key=value,... Pass custom options to libswresample (see FFmpeg docs)\n" 132 | " -S key=value,... Pass custom options to libswscale (see FFmpeg docs)\n" 133 | "\n"; 134 | 135 | static const char *const format_names[NUM_FORMATS] = { 136 | "xa", 137 | "xacd", 138 | "spu", 139 | "vag", 140 | "spui", 141 | "vagi", 142 | "str", 143 | "strcd", 144 | "strspu", 145 | "strv", 146 | "sbs" 147 | }; 148 | 149 | static void init_default_args(args_t *args) { 150 | if ( 151 | args->format == FORMAT_XA || 152 | args->format == FORMAT_XACD || 153 | args->format == FORMAT_STR || 154 | args->format == FORMAT_STRCD 155 | ) 156 | args->audio_frequency = 37800; 157 | else 158 | args->audio_frequency = 44100; 159 | 160 | if (args->format == FORMAT_SPU || args->format == FORMAT_VAG) 161 | args->audio_channels = 1; 162 | else 163 | args->audio_channels = 2; 164 | 165 | args->audio_bit_depth = 4; 166 | args->audio_xa_file = 0; 167 | args->audio_xa_channel = 0; 168 | args->audio_interleave = 2048; 169 | args->audio_loop_point = -1; 170 | 171 | args->video_codec = BS_CODEC_V2; 172 | args->video_width = 320; 173 | args->video_height = 240; 174 | 175 | args->str_fps_num = 15; 176 | args->str_fps_den = 1; 177 | args->str_cd_speed = 2; 178 | args->str_video_id = 0x8001; 179 | args->str_audio_id = 0x0001; 180 | 181 | if (args->format == FORMAT_SPU || args->format == FORMAT_VAG) 182 | args->alignment = 64; // Default SPU DMA chunk size 183 | else if (args->format == FORMAT_SBS) 184 | args->alignment = 8192; // Default for System 573 games 185 | else 186 | args->alignment = 2048; 187 | } 188 | 189 | static int parse_general_option(args_t *args, char option, const char *param) { 190 | int parsed; 191 | 192 | switch (option) { 193 | case '-': 194 | args->flags |= FLAG_IGNORE_OPTIONS; 195 | return 1; 196 | 197 | case 'h': 198 | args->flags |= FLAG_PRINT_HELP; 199 | return 1; 200 | 201 | case 'V': 202 | args->flags |= FLAG_PRINT_VERSION; 203 | return 1; 204 | 205 | case 'q': 206 | args->flags |= FLAG_QUIET | FLAG_HIDE_PROGRESS; 207 | return 1; 208 | 209 | case 't': 210 | parsed = parse_enum(&(args->format), "format", param, format_names, NUM_FORMATS); 211 | if (parsed > 0) 212 | init_default_args(args); 213 | return parsed; 214 | 215 | case 'R': 216 | if (param == NULL) { 217 | fprintf(stderr, "Missing libswresample parameter list after option\n"); 218 | return INVALID_PARAM; 219 | } 220 | 221 | args->swresample_options = param; 222 | return 2; 223 | 224 | case 'S': 225 | if (param == NULL) { 226 | fprintf(stderr, "Missing libswscale parameter list after option\n"); 227 | return INVALID_PARAM; 228 | } 229 | 230 | args->swscale_options = param; 231 | return 2; 232 | 233 | default: 234 | return 0; 235 | } 236 | } 237 | 238 | static const char *const xa_options_help = 239 | "XA-ADPCM options:\n" 240 | " [-f 18900|37800] [-c 1|2] [-b 4|8] [-F 0-255] [-C 0-31]\n" 241 | "\n" 242 | " -f 18900|37800 Use specified sample rate (default 37800)\n" 243 | " -c 1|2 Use specified channel count (default 2)\n" 244 | " -b 4|8 Use specified bit depth (default 4)\n" 245 | " -F 0-255 Set CD-XA file number (for both audio and video, default 0)\n" 246 | " -C 0-31 Set CD-XA channel number (for both audio and video, default 0)\n" 247 | "\n"; 248 | 249 | static int parse_xa_option(args_t *args, char option, const char *param) { 250 | switch (option) { 251 | case 'f': 252 | return parse_int_one_of(&(args->audio_frequency), "sample rate", param, 18900, 37800); 253 | 254 | case 'c': 255 | return parse_int_one_of(&(args->audio_channels), "channel count", param, 1, 2); 256 | 257 | case 'b': 258 | return parse_int_one_of(&(args->audio_bit_depth), "bit depth", param, 4, 8); 259 | 260 | case 'F': 261 | return parse_int(&(args->audio_xa_file), "file number", param, 0, 255); 262 | 263 | case 'C': 264 | return parse_int(&(args->audio_xa_channel), "channel number", param, 0, 31); 265 | 266 | default: 267 | return 0; 268 | } 269 | } 270 | 271 | static const char *const spu_options_help = 272 | "Mono SPU-ADPCM options:\n" 273 | " [-f freq] [-a size] [-l ms | -L] [-D]\n" 274 | "\n" 275 | " -f freq Use specified sample rate (default 44100)\n" 276 | " -a size Pad audio data excluding header to multiple of given size (default 64)\n" 277 | " -l ms Add loop point at specified offset (in milliseconds)\n" 278 | " -L Set loop end flag at the end of data but do not add a loop point\n" 279 | " -D Do not prepend encoded data with a dummy silent block\n" 280 | "\n"; 281 | 282 | static int parse_spu_option(args_t *args, char option, const char *param) { 283 | switch (option) { 284 | case 'f': 285 | return parse_int(&(args->audio_frequency), "sample rate", param, 1, -1); 286 | 287 | case 'a': 288 | return parse_int(&(args->alignment), "alignment", param, 1, -1); 289 | 290 | case 'l': 291 | args->flags |= FLAG_SPU_LOOP_END; 292 | return parse_int(&(args->audio_loop_point), "loop offset", param, 0, -1); 293 | 294 | case 'L': 295 | args->flags |= FLAG_SPU_LOOP_END; 296 | return 1; 297 | 298 | case 'D': 299 | args->flags |= FLAG_SPU_NO_LEADING_DUMMY; 300 | return 1; 301 | 302 | default: 303 | return 0; 304 | } 305 | } 306 | 307 | static const char *const spui_options_help = 308 | "Interleaved SPU-ADPCM options:\n" 309 | " [-f freq] [-c channels] [-i size] [-a size] [-L] [-D]\n" 310 | "\n" 311 | " -f freq Use specified sample rate (default 44100)\n" 312 | " -c channels Use specified channel count (default 2)\n" 313 | " -i size Use specified channel interleave size (default 2048)\n" 314 | " -a size Pad .vag header and each audio chunk to multiples of given size\n" 315 | " (default 2048)\n" 316 | " -L Set loop end flag at the end of each audio chunk\n" 317 | " -D Do not prepend first chunk's data with a dummy silent block\n" 318 | "\n"; 319 | 320 | static int parse_spui_option(args_t *args, char option, const char *param) { 321 | int parsed; 322 | 323 | switch (option) { 324 | case 'f': 325 | return parse_int(&(args->audio_frequency), "sample rate", param, 1, -1); 326 | 327 | case 'c': 328 | return parse_int(&(args->audio_channels), "channel count", param, 1, -1); 329 | 330 | case 'i': 331 | parsed = parse_int(&(args->audio_interleave), "interleave", param, 16, -1); 332 | 333 | // Round up to nearest multiple of 16 334 | args->audio_interleave = (args->audio_interleave + 15) & ~15; 335 | return parsed; 336 | 337 | case 'a': 338 | return parse_int(&(args->alignment), "alignment", param, 1, -1); 339 | 340 | case 'L': 341 | args->flags |= FLAG_SPU_LOOP_END; 342 | return 1; 343 | 344 | case 'D': 345 | args->flags |= FLAG_SPU_NO_LEADING_DUMMY; 346 | return 1; 347 | 348 | default: 349 | return 0; 350 | } 351 | } 352 | 353 | static const char *const bs_options_help = 354 | "Video options:\n" 355 | " [-v v2|v3|v3dc] [-s WxH] [-I]\n" 356 | "\n" 357 | " -v codec Use specified video codec\n" 358 | " v2: MDEC BS v2 (default)\n" 359 | " v3: MDEC BS v3\n" 360 | " v3dc: MDEC BS v3, expect decoder to wrap DC coefficients\n" 361 | " -s WxH Rescale input file to fit within specified size\n" 362 | " (16x16-640x512 in 16-pixel increments, default 320x240)\n" 363 | " -I Force stretching to given size without preserving aspect ratio\n" 364 | "\n"; 365 | 366 | const char *const bs_codec_names[NUM_BS_CODECS] = { 367 | "v2", 368 | "v3", 369 | "v3dc" 370 | }; 371 | 372 | static int parse_bs_option(args_t *args, char option, const char *param) { 373 | char *next = NULL; 374 | 375 | switch (option) { 376 | case 'v': 377 | return parse_enum(&(args->video_codec), "video codec", param, bs_codec_names, NUM_BS_CODECS); 378 | 379 | case 's': 380 | if (param == NULL) { 381 | fprintf(stderr, "Missing video size after option\n"); 382 | return INVALID_PARAM; 383 | } 384 | 385 | args->video_width = strtol(param, &next, 10); 386 | 387 | if (next && *next == 'x') { 388 | args->video_height = strtol(next + 1, NULL, 10); 389 | } else { 390 | fprintf(stderr, "Invalid video size (must be specified as x)\n"); 391 | return INVALID_PARAM; 392 | } 393 | 394 | if (args->video_width < 16 || args->video_width > 640) { 395 | fprintf(stderr, "Invalid video width: %d (must be in 16-640 range)\n", args->video_width); 396 | return INVALID_PARAM; 397 | } 398 | if (args->video_height < 16 || args->video_height > 512) { 399 | fprintf(stderr, "Invalid video height: %d (must be in 16-512 range)\n", args->video_height); 400 | return INVALID_PARAM; 401 | } 402 | 403 | // Round up to nearest multiples of 16 404 | args->video_width = (args->video_width + 15) & ~15; 405 | args->video_height = (args->video_height + 15) & ~15; 406 | return 2; 407 | 408 | case 'I': 409 | args->flags |= FLAG_BS_IGNORE_ASPECT; 410 | return 1; 411 | 412 | default: 413 | return 0; 414 | } 415 | } 416 | 417 | static const char *const str_options_help = 418 | ".str container options:\n" 419 | " [-r num[/den]] [-x 1|2] [-T id] [-A id] [-X]\n" 420 | "\n" 421 | " -r num[/den] Set video frame rate to specified integer or fraction (default 15)\n" 422 | " -x 1|2 Set CD-ROM speed the file is meant to played at (default 2)\n" 423 | " -T id Tag video sectors with specified .str type ID (default 0x8001)\n" 424 | " -A id Tag SPU-ADPCM sectors with specified .str type ID (default 0x0001)\n" 425 | " -X Place audio sectors after corresponding video sectors\n" 426 | " (rather than ahead of them)\n" 427 | "\n"; 428 | 429 | static int parse_str_option(args_t *args, char option, const char *param) { 430 | char *next = NULL; 431 | int fps; 432 | 433 | switch (option) { 434 | case 'r': 435 | if (param == NULL) { 436 | fprintf(stderr, "Missing frame rate value after option\n"); 437 | return INVALID_PARAM; 438 | } 439 | 440 | args->str_fps_num = strtol(param, &next, 10); 441 | 442 | if (next && *next == '/') 443 | args->str_fps_den = strtol(next + 1, NULL, 10); 444 | else 445 | args->str_fps_den = 1; 446 | 447 | if (args->str_fps_num <= 0 || args->str_fps_den <= 0) { 448 | fprintf(stderr, "Invalid frame rate (must be a non-zero integer or fraction)\n"); 449 | return INVALID_PARAM; 450 | } 451 | 452 | fps = args->str_fps_num / args->str_fps_den; 453 | 454 | if (fps < 1 || fps > 60) { 455 | fprintf(stderr, "Invalid frame rate: %d/%d (must be in 1-60 range)\n", args->str_fps_num, args->str_fps_den); 456 | return INVALID_PARAM; 457 | } 458 | return 2; 459 | 460 | case 'x': 461 | return parse_int_one_of(&(args->str_cd_speed), "CD-ROM speed", param, 1, 2); 462 | 463 | case 'T': 464 | return parse_int(&(args->str_video_id), "video track type ID", param, 0x0000, 0xFFFF); 465 | 466 | case 'A': 467 | return parse_int(&(args->str_audio_id), "audio track type ID", param, 0x0000, 0xFFFF); 468 | 469 | case 'X': 470 | args->flags |= FLAG_STR_TRAILING_AUDIO; 471 | return 1; 472 | 473 | default: 474 | return 0; 475 | } 476 | } 477 | 478 | static const char *const sbs_options_help = 479 | ".sbs container options:\n" 480 | " [-a size]\n" 481 | "\n" 482 | " -a size Set size of each video frame (default 8192)\n" 483 | "\n"; 484 | 485 | static int parse_sbs_option(args_t *args, char option, const char *param) { 486 | switch (option) { 487 | case 'a': 488 | return parse_int(&(args->alignment), "video frame size", param, 256, -1); 489 | 490 | default: 491 | return 0; 492 | } 493 | } 494 | 495 | static const char *const general_usage = 496 | "Usage:\n" 497 | " psxavenc -t xa|xacd [xa-options] \n" 498 | " psxavenc -t spu|vag [spu-options] \n" 499 | " psxavenc -t spui|vagi [spui-options] \n" 500 | " psxavenc -t str|strcd [xa-options] [bs-options] [str-options] \n" 501 | //" psxavenc -t strspu [spui-options] [bs-options] [str-options] \n" 502 | " psxavenc -t strv [bs-options] [str-options] \n" 503 | " psxavenc -t sbs [bs-options] [sbs-options] \n" 504 | "\n"; 505 | 506 | static const struct { 507 | const char *usage; 508 | const char *audio_options_help; 509 | const char *video_options_help; 510 | const char *container_options_help; 511 | int (*parse_audio_option)(args_t *, char, const char *); 512 | int (*parse_video_option)(args_t *, char, const char *); 513 | int (*parse_container_option)(args_t *, char, const char *); 514 | } format_info[NUM_FORMATS] = { 515 | { 516 | .usage = "psxavenc -t xa [xa-options] ", 517 | .audio_options_help = xa_options_help, 518 | .video_options_help = NULL, 519 | .container_options_help = NULL, 520 | .parse_audio_option = parse_xa_option, 521 | .parse_video_option = NULL, 522 | .parse_container_option = NULL 523 | }, { 524 | .usage = "psxavenc -t xacd [xa-options] ", 525 | .audio_options_help = xa_options_help, 526 | .video_options_help = NULL, 527 | .container_options_help = NULL, 528 | .parse_audio_option = parse_xa_option, 529 | .parse_video_option = NULL, 530 | .parse_container_option = NULL 531 | }, { 532 | .usage = "psxavenc -t spu [spu-options] ", 533 | .audio_options_help = spu_options_help, 534 | .video_options_help = NULL, 535 | .container_options_help = NULL, 536 | .parse_audio_option = parse_spu_option, 537 | .parse_video_option = NULL, 538 | .parse_container_option = NULL 539 | }, { 540 | .usage = "psxavenc -t vag [spu-options] ", 541 | .audio_options_help = spu_options_help, 542 | .video_options_help = NULL, 543 | .container_options_help = NULL, 544 | .parse_audio_option = parse_spu_option, 545 | .parse_video_option = NULL, 546 | .parse_container_option = NULL 547 | }, { 548 | .usage = "psxavenc -t spui [spui-options] ", 549 | .audio_options_help = spui_options_help, 550 | .video_options_help = NULL, 551 | .container_options_help = NULL, 552 | .parse_audio_option = parse_spui_option, 553 | .parse_video_option = NULL, 554 | .parse_container_option = NULL 555 | }, { 556 | .usage = "psxavenc -t vagi [spui-options] ", 557 | .audio_options_help = spui_options_help, 558 | .video_options_help = NULL, 559 | .container_options_help = NULL, 560 | .parse_audio_option = parse_spui_option, 561 | .parse_video_option = NULL, 562 | .parse_container_option = NULL 563 | }, { 564 | .usage = "psxavenc -t str [xa-options] [bs-options] [str-options] ", 565 | .audio_options_help = xa_options_help, 566 | .video_options_help = bs_options_help, 567 | .container_options_help = str_options_help, 568 | .parse_audio_option = parse_xa_option, 569 | .parse_video_option = parse_bs_option, 570 | .parse_container_option = parse_str_option 571 | }, { 572 | .usage = "psxavenc -t strcd [xa-options] [bs-options] [str-options] ", 573 | .audio_options_help = xa_options_help, 574 | .video_options_help = bs_options_help, 575 | .container_options_help = str_options_help, 576 | .parse_audio_option = parse_xa_option, 577 | .parse_video_option = parse_bs_option, 578 | .parse_container_option = parse_str_option 579 | }, { 580 | .usage = "psxavenc -t strspu [spui-options] [bs-options] [str-options] ", 581 | .audio_options_help = spui_options_help, 582 | .video_options_help = bs_options_help, 583 | .container_options_help = str_options_help, 584 | .parse_audio_option = parse_spui_option, 585 | .parse_video_option = parse_bs_option, 586 | .parse_container_option = parse_str_option 587 | }, { 588 | .usage = "psxavenc -t strv [bs-options] [str-options] ", 589 | .audio_options_help = NULL, 590 | .video_options_help = bs_options_help, 591 | .container_options_help = str_options_help, 592 | .parse_audio_option = NULL, 593 | .parse_video_option = parse_bs_option, 594 | .parse_container_option = parse_str_option 595 | }, { 596 | .usage = "psxavenc -t sbs [bs-options] [sbs-options] ", 597 | .audio_options_help = NULL, 598 | .video_options_help = bs_options_help, 599 | .container_options_help = sbs_options_help, 600 | .parse_audio_option = NULL, 601 | .parse_video_option = parse_bs_option, 602 | .parse_container_option = parse_sbs_option 603 | } 604 | }; 605 | 606 | static int parse_option(args_t *args, char option, const char *param) { 607 | int parsed = parse_general_option(args, option, param); 608 | 609 | if (parsed == 0 && args->format != FORMAT_INVALID) { 610 | if (format_info[args->format].parse_audio_option != NULL) 611 | parsed = format_info[args->format].parse_audio_option(args, option, param); 612 | } 613 | if (parsed == 0 && args->format != FORMAT_INVALID) { 614 | if (format_info[args->format].parse_video_option != NULL) 615 | parsed = format_info[args->format].parse_video_option(args, option, param); 616 | } 617 | if (parsed == 0 && args->format != FORMAT_INVALID) { 618 | if (format_info[args->format].parse_container_option != NULL) 619 | parsed = format_info[args->format].parse_container_option(args, option, param); 620 | } 621 | if (parsed == 0) { 622 | if (args->format == FORMAT_INVALID) 623 | fprintf( 624 | stderr, 625 | "Unknown general option: -%c\n" 626 | "(if this is a format-specific option, it shall be passed after -t)\n", 627 | option 628 | ); 629 | else 630 | fprintf(stderr, "Unknown option for format %s: -%c\n", format_names[args->format], option); 631 | } 632 | 633 | return parsed; 634 | } 635 | 636 | static void print_help(format_t format) { 637 | if (format == FORMAT_INVALID) { 638 | printf( 639 | "%s%s%s%s%s%s%s%s", 640 | general_usage, 641 | general_options_help, 642 | xa_options_help, 643 | spu_options_help, 644 | spui_options_help, 645 | bs_options_help, 646 | str_options_help, 647 | sbs_options_help 648 | ); 649 | return; 650 | } 651 | 652 | printf( 653 | "Usage:\n" 654 | " %s\n" 655 | "\n" 656 | "%s", 657 | format_info[format].usage, 658 | general_options_help 659 | ); 660 | if (format_info[format].audio_options_help != NULL) 661 | printf("%s", format_info[format].audio_options_help); 662 | if (format_info[format].video_options_help != NULL) 663 | printf("%s", format_info[format].video_options_help); 664 | if (format_info[format].container_options_help != NULL) 665 | printf("%s", format_info[format].container_options_help); 666 | } 667 | 668 | bool parse_args(args_t *args, const char *const *options, int count) { 669 | int arg_index = 0; 670 | 671 | while (arg_index < count) { 672 | const char *option = options[arg_index]; 673 | 674 | if (option[0] == '-' && option[2] == 0 && !(args->flags & FLAG_IGNORE_OPTIONS)) { 675 | const char *param; 676 | if ((arg_index + 1) < count) 677 | param = options[arg_index + 1]; 678 | else 679 | param = NULL; 680 | 681 | int parsed = parse_option(args, option[1], param); 682 | if (parsed <= 0) 683 | return false; 684 | 685 | arg_index += parsed; 686 | continue; 687 | } 688 | 689 | if (args->input_file == NULL) { 690 | args->input_file = option; 691 | } else if (args->output_file == NULL) { 692 | args->output_file = option; 693 | } else { 694 | fprintf(stderr, "There should be no arguments after the output file path\n"); 695 | return false; 696 | } 697 | arg_index++; 698 | } 699 | 700 | if (args->flags & FLAG_PRINT_HELP) { 701 | print_help(args->format); 702 | return false; 703 | } 704 | if (args->flags & FLAG_PRINT_VERSION) { 705 | printf("psxavenc " VERSION "\n"); 706 | return false; 707 | } 708 | if (args->format == FORMAT_INVALID || args->input_file == NULL || args->output_file == NULL) { 709 | fprintf( 710 | stderr, 711 | "%s" 712 | "For more information about the options supported for a given output format, run:\n" 713 | " psxavenc -t -h\n" 714 | "To view the full list of supported options, run:\n" 715 | " psxavenc -h\n", 716 | general_usage 717 | ); 718 | return false; 719 | } 720 | 721 | return true; 722 | } 723 | -------------------------------------------------------------------------------- /psxavenc/args.h: -------------------------------------------------------------------------------- 1 | /* 2 | psxavenc: MDEC video + SPU/XA-ADPCM audio encoder frontend 3 | 4 | Copyright (c) 2019, 2020 Adrian "asie" Siekierka 5 | Copyright (c) 2019 Ben "GreaseMonkey" Russell 6 | Copyright (c) 2023, 2025 spicyjpeg 7 | 8 | This software is provided 'as-is', without any express or implied 9 | warranty. In no event will the authors be held liable for any damages 10 | arising from the use of this software. 11 | 12 | Permission is granted to anyone to use this software for any purpose, 13 | including commercial applications, and to alter it and redistribute it 14 | freely, subject to the following restrictions: 15 | 16 | 1. The origin of this software must not be misrepresented; you must not 17 | claim that you wrote the original software. If you use this software 18 | in a product, an acknowledgment in the product documentation would be 19 | appreciated but is not required. 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 3. This notice may not be removed or altered from any source distribution. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include 28 | 29 | #define NUM_FORMATS 11 30 | #define NUM_BS_CODECS 3 31 | 32 | enum { 33 | FLAG_IGNORE_OPTIONS = 1 << 0, 34 | FLAG_QUIET = 1 << 1, 35 | FLAG_HIDE_PROGRESS = 1 << 2, 36 | FLAG_PRINT_HELP = 1 << 3, 37 | FLAG_PRINT_VERSION = 1 << 4, 38 | FLAG_SPU_LOOP_END = 1 << 5, 39 | FLAG_SPU_NO_LEADING_DUMMY = 1 << 6, 40 | FLAG_BS_IGNORE_ASPECT = 1 << 7, 41 | FLAG_STR_TRAILING_AUDIO = 1 << 8 42 | }; 43 | 44 | typedef enum { 45 | FORMAT_INVALID = -1, 46 | FORMAT_XA, 47 | FORMAT_XACD, 48 | FORMAT_SPU, 49 | FORMAT_VAG, 50 | FORMAT_SPUI, 51 | FORMAT_VAGI, 52 | FORMAT_STR, 53 | FORMAT_STRCD, 54 | FORMAT_STRSPU, 55 | FORMAT_STRV, 56 | FORMAT_SBS 57 | } format_t; 58 | 59 | typedef enum { 60 | BS_CODEC_INVALID = -1, 61 | BS_CODEC_V2, 62 | BS_CODEC_V3, 63 | BS_CODEC_V3DC 64 | } bs_codec_t; 65 | 66 | typedef struct { 67 | int flags; 68 | 69 | format_t format; 70 | const char *input_file; 71 | const char *output_file; 72 | const char *swresample_options; 73 | const char *swscale_options; 74 | 75 | int audio_frequency; // 18900 or 37800 Hz 76 | int audio_channels; 77 | int audio_bit_depth; // 4 or 8 78 | int audio_xa_file; // 00-FF 79 | int audio_xa_channel; // 00-1F 80 | int audio_interleave; 81 | int audio_loop_point; 82 | 83 | bs_codec_t video_codec; 84 | int video_width; 85 | int video_height; 86 | 87 | int str_fps_num; 88 | int str_fps_den; 89 | int str_cd_speed; // 1 or 2 90 | int str_video_id; 91 | int str_audio_id; 92 | int alignment; 93 | } args_t; 94 | 95 | bool parse_args(args_t *args, const char *const *options, int count); 96 | -------------------------------------------------------------------------------- /psxavenc/decoding.c: -------------------------------------------------------------------------------- 1 | /* 2 | psxavenc: MDEC video + SPU/XA-ADPCM audio encoder frontend 3 | 4 | Copyright (c) 2019, 2020 Adrian "asie" Siekierka 5 | Copyright (c) 2019 Ben "GreaseMonkey" Russell 6 | Copyright (c) 2023 spicyjpeg 7 | 8 | This software is provided 'as-is', without any express or implied 9 | warranty. In no event will the authors be held liable for any damages 10 | arising from the use of this software. 11 | 12 | Permission is granted to anyone to use this software for any purpose, 13 | including commercial applications, and to alter it and redistribute it 14 | freely, subject to the following restrictions: 15 | 16 | 1. The origin of this software must not be misrepresented; you must not 17 | claim that you wrote the original software. If you use this software 18 | in a product, an acknowledgment in the product documentation would be 19 | appreciated but is not required. 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 3. This notice may not be removed or altered from any source distribution. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include "args.h" 37 | #include "decoding.h" 38 | 39 | static bool decode_frame(AVCodecContext *codec, AVFrame *frame, int *frame_size, AVPacket *packet) { 40 | if (packet != NULL) { 41 | if (avcodec_send_packet(codec, packet) != 0) 42 | return false; 43 | } 44 | 45 | int ret = avcodec_receive_frame(codec, frame); 46 | 47 | if (ret >= 0) { 48 | *frame_size = ret; 49 | return true; 50 | } 51 | if (ret == AVERROR(EAGAIN)) 52 | return true; 53 | 54 | return false; 55 | } 56 | 57 | bool open_av_data(decoder_t *decoder, const args_t *args, int flags) { 58 | decoder->audio_samples = NULL; 59 | decoder->audio_sample_count = 0; 60 | decoder->video_frames = NULL; 61 | decoder->video_frame_count = 0; 62 | 63 | decoder->video_width = args->video_width; 64 | decoder->video_height = args->video_height; 65 | decoder->video_fps_num = args->str_fps_num; 66 | decoder->video_fps_den = args->str_fps_den; 67 | decoder->end_of_input = false; 68 | 69 | decoder_state_t *av = &(decoder->state); 70 | 71 | av->video_next_pts = 0.0; 72 | av->frame = NULL; 73 | av->video_frame_dst_size = 0; 74 | av->audio_stream_index = -1; 75 | av->video_stream_index = -1; 76 | av->format = NULL; 77 | av->audio_stream = NULL; 78 | av->video_stream = NULL; 79 | av->audio_codec_context = NULL; 80 | av->video_codec_context = NULL; 81 | av->resampler = NULL; 82 | av->scaler = NULL; 83 | 84 | if (args->flags & FLAG_QUIET) 85 | av_log_set_level(AV_LOG_QUIET); 86 | 87 | av->format = avformat_alloc_context(); 88 | 89 | if (avformat_open_input(&(av->format), args->input_file, NULL, NULL)) 90 | return false; 91 | if (avformat_find_stream_info(av->format, NULL) < 0) 92 | return false; 93 | 94 | if (flags & DECODER_USE_AUDIO) { 95 | for (int i = 0; i < av->format->nb_streams; i++) { 96 | if (av->format->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { 97 | if (av->audio_stream_index >= 0) { 98 | fprintf(stderr, "Input file must have a single audio track\n"); 99 | return false; 100 | } 101 | av->audio_stream_index = i; 102 | } 103 | } 104 | 105 | if ((flags & DECODER_AUDIO_REQUIRED) && av->audio_stream_index == -1) { 106 | fprintf(stderr, "Input file has no audio data\n"); 107 | return false; 108 | } 109 | } 110 | 111 | if (flags & DECODER_USE_VIDEO) { 112 | for (int i = 0; i < av->format->nb_streams; i++) { 113 | if (av->format->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { 114 | if (av->video_stream_index >= 0) { 115 | fprintf(stderr, "Input file must have a single video track\n"); 116 | return false; 117 | } 118 | av->video_stream_index = i; 119 | } 120 | } 121 | 122 | if ((flags & DECODER_VIDEO_REQUIRED) && av->video_stream_index == -1) { 123 | fprintf(stderr, "Input file has no video data\n"); 124 | return false; 125 | } 126 | } 127 | 128 | av->audio_stream = (av->audio_stream_index != -1 ? av->format->streams[av->audio_stream_index] : NULL); 129 | av->video_stream = (av->video_stream_index != -1 ? av->format->streams[av->video_stream_index] : NULL); 130 | 131 | if (av->audio_stream != NULL) { 132 | const AVCodec *codec = avcodec_find_decoder(av->audio_stream->codecpar->codec_id); 133 | av->audio_codec_context = avcodec_alloc_context3(codec); 134 | 135 | if (av->audio_codec_context == NULL) 136 | return false; 137 | if (avcodec_parameters_to_context(av->audio_codec_context, av->audio_stream->codecpar) < 0) 138 | return false; 139 | if (avcodec_open2(av->audio_codec_context, codec, NULL) < 0) 140 | return false; 141 | 142 | AVChannelLayout layout; 143 | layout.nb_channels = args->audio_channels; 144 | 145 | if (args->audio_channels == 1) { 146 | layout.order = AV_CHANNEL_ORDER_NATIVE; 147 | layout.u.mask = AV_CH_LAYOUT_MONO; 148 | } else if (args->audio_channels == 2) { 149 | layout.order = AV_CHANNEL_ORDER_NATIVE; 150 | layout.u.mask = AV_CH_LAYOUT_STEREO; 151 | } else { 152 | layout.order = AV_CHANNEL_ORDER_UNSPEC; 153 | } 154 | 155 | if (!(args->flags & FLAG_QUIET)) { 156 | if (args->audio_channels > av->audio_codec_context->ch_layout.nb_channels) 157 | fprintf(stderr, "Warning: input file has less than %d channels\n", args->audio_channels); 158 | } 159 | 160 | av->sample_count_mul = args->audio_channels; 161 | 162 | if (swr_alloc_set_opts2( 163 | &av->resampler, 164 | &layout, 165 | AV_SAMPLE_FMT_S16, 166 | args->audio_frequency, 167 | &av->audio_codec_context->ch_layout, 168 | av->audio_codec_context->sample_fmt, 169 | av->audio_codec_context->sample_rate, 170 | 0, 171 | NULL 172 | ) < 0) { 173 | return false; 174 | } 175 | if (args->swresample_options) { 176 | if (av_opt_set_from_string(av->resampler, args->swresample_options, NULL, "=", ":,") < 0) 177 | return false; 178 | } 179 | if (swr_init(av->resampler) < 0) 180 | return false; 181 | } 182 | 183 | if (av->video_stream != NULL) { 184 | const AVCodec *codec = avcodec_find_decoder(av->video_stream->codecpar->codec_id); 185 | av->video_codec_context = avcodec_alloc_context3(codec); 186 | 187 | if (av->video_codec_context == NULL) 188 | return false; 189 | if (avcodec_parameters_to_context(av->video_codec_context, av->video_stream->codecpar) < 0) 190 | return false; 191 | if (avcodec_open2(av->video_codec_context, codec, NULL) < 0) 192 | return false; 193 | 194 | if (!(args->flags & FLAG_QUIET)) { 195 | if ( 196 | decoder->video_width > av->video_codec_context->width || 197 | decoder->video_height > av->video_codec_context->height 198 | ) 199 | fprintf(stderr, "Warning: input file has resolution lower than %dx%d\n", decoder->video_width, decoder->video_height); 200 | } 201 | 202 | if (!(args->flags & FLAG_BS_IGNORE_ASPECT)) { 203 | // Reduce the provided size so that it matches the input file's 204 | // aspect ratio. 205 | double src_ratio = (double)av->video_codec_context->width / (double)av->video_codec_context->height; 206 | double dst_ratio = (double)decoder->video_width / (double)decoder->video_height; 207 | 208 | if (src_ratio < dst_ratio) { 209 | decoder->video_width = (int)((double)decoder->video_height * src_ratio + 15.0) & ~15; 210 | } else { 211 | decoder->video_height = (int)((double)decoder->video_width / src_ratio + 15.0) & ~15; 212 | } 213 | } 214 | 215 | av->scaler = sws_getContext( 216 | av->video_codec_context->width, 217 | av->video_codec_context->height, 218 | av->video_codec_context->pix_fmt, 219 | decoder->video_width, 220 | decoder->video_height, 221 | AV_PIX_FMT_NV21, 222 | SWS_BICUBIC, 223 | NULL, 224 | NULL, 225 | NULL 226 | ); 227 | if (av->scaler == NULL) 228 | return false; 229 | if (sws_setColorspaceDetails( 230 | av->scaler, 231 | sws_getCoefficients(av->video_codec_context->colorspace), 232 | av->video_codec_context->color_range == AVCOL_RANGE_JPEG, 233 | sws_getCoefficients(SWS_CS_ITU601), 234 | true, 235 | 0, 236 | 1 << 16, 237 | 1 << 16 238 | ) < 0) 239 | return false; 240 | if (args->swscale_options) { 241 | if (av_opt_set_from_string(av->scaler, args->swscale_options, NULL, "=", ":,") < 0) 242 | return false; 243 | } 244 | 245 | av->video_frame_dst_size = 3 * decoder->video_width * decoder->video_height / 2; 246 | } 247 | 248 | av->frame = av_frame_alloc(); 249 | 250 | if (av->frame == NULL) 251 | return false; 252 | 253 | return true; 254 | } 255 | 256 | static void poll_av_packet_audio(decoder_t *decoder, AVPacket *packet) { 257 | decoder_state_t *av = &(decoder->state); 258 | 259 | int frame_size; 260 | 261 | if (!decode_frame(av->audio_codec_context, av->frame, &frame_size, packet)) 262 | return; 263 | 264 | int frame_sample_count = swr_get_out_samples(av->resampler, av->frame->nb_samples); 265 | 266 | if (frame_sample_count == 0) 267 | return; 268 | 269 | size_t buffer_size = sizeof(int16_t) * av->sample_count_mul * frame_sample_count; 270 | uint8_t *buffer = malloc(buffer_size); 271 | memset(buffer, 0, buffer_size); 272 | 273 | frame_sample_count = swr_convert( 274 | av->resampler, 275 | &buffer, 276 | frame_sample_count, 277 | (const uint8_t**)av->frame->data, 278 | av->frame->nb_samples 279 | ); 280 | 281 | decoder->audio_samples = realloc( 282 | decoder->audio_samples, 283 | (decoder->audio_sample_count + ((frame_sample_count + 4032) * av->sample_count_mul)) * sizeof(int16_t) 284 | ); 285 | memmove( 286 | &(decoder->audio_samples[decoder->audio_sample_count]), 287 | buffer, 288 | sizeof(int16_t) * frame_sample_count * av->sample_count_mul 289 | ); 290 | decoder->audio_sample_count += frame_sample_count * av->sample_count_mul; 291 | free(buffer); 292 | } 293 | 294 | static void poll_av_packet_video(decoder_t *decoder, AVPacket *packet) { 295 | decoder_state_t *av = &(decoder->state); 296 | 297 | int frame_size; 298 | double pts_step = (double)decoder->video_fps_den / (double)decoder->video_fps_num; 299 | 300 | int plane_size = decoder->video_width * decoder->video_height; 301 | int dst_strides[2] = { 302 | decoder->video_width, decoder->video_width 303 | }; 304 | 305 | if (!decode_frame(av->video_codec_context, av->frame, &frame_size, packet)) 306 | return; 307 | if (!av->frame->width || !av->frame->height || !av->frame->data[0]) 308 | return; 309 | 310 | // Some files seem to have timestamps starting from a negative value 311 | // (but otherwise valid) for whatever reason. 312 | double pts = 313 | ((double)av->frame->pts * (double)av->video_stream->time_base.num) 314 | / av->video_stream->time_base.den; 315 | #if 0 316 | if (pts < 0.0) 317 | return; 318 | #endif 319 | if (decoder->video_frame_count >= 1 && pts < av->video_next_pts) 320 | return; 321 | if (decoder->video_frame_count < 1) 322 | av->video_next_pts = pts; 323 | else 324 | av->video_next_pts += pts_step; 325 | 326 | //fprintf(stderr, "%d %f %f %f\n", decoder->video_frame_count, pts, av->video_next_pts, pts_step); 327 | 328 | // Insert duplicate frames if the frame rate of the input stream is 329 | // lower than the target frame rate. 330 | int dupe_frames = (int) ceil((pts - av->video_next_pts) / pts_step); 331 | if (dupe_frames < 0) dupe_frames = 0; 332 | decoder->video_frames = realloc( 333 | decoder->video_frames, 334 | (decoder->video_frame_count + dupe_frames + 1) * av->video_frame_dst_size 335 | ); 336 | 337 | for (; dupe_frames; dupe_frames--) { 338 | memcpy( 339 | (decoder->video_frames) + av->video_frame_dst_size * decoder->video_frame_count, 340 | (decoder->video_frames) + av->video_frame_dst_size * (decoder->video_frame_count - 1), 341 | av->video_frame_dst_size 342 | ); 343 | decoder->video_frame_count += 1; 344 | av->video_next_pts += pts_step; 345 | } 346 | 347 | uint8_t *dst_frame = decoder->video_frames + av->video_frame_dst_size * decoder->video_frame_count; 348 | uint8_t *dst_pointers[2] = { 349 | dst_frame, dst_frame + plane_size 350 | }; 351 | sws_scale( 352 | av->scaler, 353 | (const uint8_t *const *) av->frame->data, 354 | av->frame->linesize, 355 | 0, 356 | av->frame->height, 357 | dst_pointers, 358 | dst_strides 359 | ); 360 | 361 | decoder->video_frame_count += 1; 362 | } 363 | 364 | bool poll_av_data(decoder_t *decoder) { 365 | decoder_state_t *av = &(decoder->state); 366 | 367 | if (decoder->end_of_input) 368 | return false; 369 | 370 | AVPacket packet; 371 | 372 | if (av_read_frame(av->format, &packet) >= 0) { 373 | if (packet.stream_index == av->audio_stream_index) 374 | poll_av_packet_audio(decoder, &packet); 375 | else if (packet.stream_index == av->video_stream_index) 376 | poll_av_packet_video(decoder, &packet); 377 | 378 | av_packet_unref(&packet); 379 | return true; 380 | } else { 381 | // out is always padded out with 4032 "0" samples, this makes calculations elsewhere easier 382 | if (av->audio_stream) 383 | memset( 384 | decoder->audio_samples + decoder->audio_sample_count, 385 | 0, 386 | 4032 * av->sample_count_mul * sizeof(int16_t) 387 | ); 388 | 389 | decoder->end_of_input = true; 390 | return false; 391 | } 392 | } 393 | 394 | bool ensure_av_data(decoder_t *decoder, int needed_audio_samples, int needed_video_frames) { 395 | // HACK: in order to update decoder->end_of_input as soon as all data has 396 | // been read from the input file, this loop waits for more data than 397 | // strictly needed. 398 | #if 0 399 | while (decoder->audio_sample_count < needed_audio_samples || decoder->video_frame_count < needed_video_frames) { 400 | #else 401 | while ( 402 | (needed_audio_samples && decoder->audio_sample_count <= needed_audio_samples) || 403 | (needed_video_frames && decoder->video_frame_count <= needed_video_frames) 404 | ) { 405 | #endif 406 | //fprintf(stderr, "ensure %d -> %d, %d -> %d\n", decoder->audio_sample_count, needed_audio_samples, decoder->video_frame_count, needed_video_frames); 407 | if (!poll_av_data(decoder)) { 408 | // Keep returning true even if the end of the input file has been 409 | // reached, if the buffer is not yet completely empty. 410 | return 411 | (decoder->audio_sample_count || !needed_audio_samples) && 412 | (decoder->video_frame_count || !needed_video_frames); 413 | } 414 | } 415 | //fprintf(stderr, "ensure %d -> %d, %d -> %d\n", decoder->audio_sample_count, needed_audio_samples, decoder->video_frame_count, needed_video_frames); 416 | 417 | return true; 418 | } 419 | 420 | void retire_av_data(decoder_t *decoder, int retired_audio_samples, int retired_video_frames) { 421 | //fprintf(stderr, "retire %d -> %d, %d -> %d\n", decoder->audio_sample_count, retired_audio_samples, decoder->video_frame_count, retired_video_frames); 422 | assert(retired_audio_samples <= decoder->audio_sample_count); 423 | assert(retired_video_frames <= decoder->video_frame_count); 424 | 425 | int sample_size = sizeof(int16_t); 426 | int frame_size = decoder->state.video_frame_dst_size; 427 | 428 | if (decoder->audio_sample_count > retired_audio_samples) 429 | memmove( 430 | decoder->audio_samples, 431 | decoder->audio_samples + retired_audio_samples, 432 | (decoder->audio_sample_count - retired_audio_samples) * sample_size 433 | ); 434 | if (decoder->video_frame_count > retired_video_frames) 435 | memmove( 436 | decoder->video_frames, 437 | decoder->video_frames + retired_video_frames * frame_size, 438 | (decoder->video_frame_count - retired_video_frames) * frame_size 439 | ); 440 | 441 | decoder->audio_sample_count -= retired_audio_samples; 442 | decoder->video_frame_count -= retired_video_frames; 443 | } 444 | 445 | void close_av_data(decoder_t *decoder) { 446 | decoder_state_t *av = &(decoder->state); 447 | 448 | av_frame_free(&(av->frame)); 449 | swr_free(&(av->resampler)); 450 | // Deprecated, kept for compatibility with older FFmpeg versions. 451 | avcodec_close(av->audio_codec_context); 452 | avcodec_free_context(&(av->audio_codec_context)); 453 | avformat_free_context(av->format); 454 | 455 | if(decoder->audio_samples != NULL) { 456 | free(decoder->audio_samples); 457 | decoder->audio_samples = NULL; 458 | } 459 | if(decoder->video_frames != NULL) { 460 | free(decoder->video_frames); 461 | decoder->video_frames = NULL; 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /psxavenc/decoding.h: -------------------------------------------------------------------------------- 1 | /* 2 | psxavenc: MDEC video + SPU/XA-ADPCM audio encoder frontend 3 | 4 | Copyright (c) 2019, 2020 Adrian "asie" Siekierka 5 | Copyright (c) 2019 Ben "GreaseMonkey" Russell 6 | Copyright (c) 2023, 2025 spicyjpeg 7 | 8 | This software is provided 'as-is', without any express or implied 9 | warranty. In no event will the authors be held liable for any damages 10 | arising from the use of this software. 11 | 12 | Permission is granted to anyone to use this software for any purpose, 13 | including commercial applications, and to alter it and redistribute it 14 | freely, subject to the following restrictions: 15 | 16 | 1. The origin of this software must not be misrepresented; you must not 17 | claim that you wrote the original software. If you use this software 18 | in a product, an acknowledgment in the product documentation would be 19 | appreciated but is not required. 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 3. This notice may not be removed or altered from any source distribution. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include "args.h" 35 | 36 | typedef struct { 37 | int video_frame_dst_size; 38 | int audio_stream_index; 39 | int video_stream_index; 40 | AVFormatContext* format; 41 | AVStream* audio_stream; 42 | AVStream* video_stream; 43 | AVCodecContext* audio_codec_context; 44 | AVCodecContext* video_codec_context; 45 | struct SwrContext* resampler; 46 | struct SwsContext* scaler; 47 | AVFrame* frame; 48 | 49 | int sample_count_mul; 50 | 51 | double video_next_pts; 52 | } decoder_state_t; 53 | 54 | typedef struct { 55 | int16_t *audio_samples; 56 | int audio_sample_count; 57 | uint8_t *video_frames; 58 | int video_frame_count; 59 | 60 | int video_width; 61 | int video_height; 62 | int video_fps_num; 63 | int video_fps_den; 64 | bool end_of_input; 65 | 66 | decoder_state_t state; 67 | } decoder_t; 68 | 69 | enum { 70 | DECODER_USE_AUDIO = 1 << 0, 71 | DECODER_USE_VIDEO = 1 << 1, 72 | DECODER_AUDIO_REQUIRED = 1 << 2, 73 | DECODER_VIDEO_REQUIRED = 1 << 3 74 | }; 75 | 76 | bool open_av_data(decoder_t *decoder, const args_t *args, int flags); 77 | bool poll_av_data(decoder_t *decoder); 78 | bool ensure_av_data(decoder_t *decoder, int needed_audio_samples, int needed_video_frames); 79 | void retire_av_data(decoder_t *decoder, int retired_audio_samples, int retired_video_frames); 80 | void close_av_data(decoder_t *decoder); 81 | -------------------------------------------------------------------------------- /psxavenc/filefmt.c: -------------------------------------------------------------------------------- 1 | /* 2 | psxavenc: MDEC video + SPU/XA-ADPCM audio encoder frontend 3 | 4 | Copyright (c) 2019, 2020 Adrian "asie" Siekierka 5 | Copyright (c) 2019 Ben "GreaseMonkey" Russell 6 | Copyright (c) 2023, 2025 spicyjpeg 7 | 8 | This software is provided 'as-is', without any express or implied 9 | warranty. In no event will the authors be held liable for any damages 10 | arising from the use of this software. 11 | 12 | Permission is granted to anyone to use this software for any purpose, 13 | including commercial applications, and to alter it and redistribute it 14 | freely, subject to the following restrictions: 15 | 16 | 1. The origin of this software must not be misrepresented; you must not 17 | claim that you wrote the original software. If you use this software 18 | in a product, an acknowledgment in the product documentation would be 19 | appreciated but is not required. 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 3. This notice may not be removed or altered from any source distribution. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include "args.h" 32 | #include "decoding.h" 33 | #include "mdec.h" 34 | 35 | static time_t start_time = 0; 36 | static time_t last_progress_update = 0; 37 | 38 | static time_t get_elapsed_time(void) { 39 | time_t t; 40 | 41 | if (start_time > 0) { 42 | t = time(NULL) - start_time; 43 | } else { 44 | t = 0; 45 | start_time = time(NULL); 46 | } 47 | 48 | if (t <= last_progress_update) 49 | return 0; 50 | 51 | last_progress_update = t; 52 | return t; 53 | } 54 | 55 | static psx_audio_xa_settings_t args_to_libpsxav_xa_audio(const args_t *args) { 56 | psx_audio_xa_settings_t settings; 57 | 58 | settings.bits_per_sample = args->audio_bit_depth; 59 | settings.frequency = args->audio_frequency; 60 | settings.stereo = (args->audio_channels == 2); 61 | settings.file_number = args->audio_xa_file; 62 | settings.channel_number = args->audio_xa_channel; 63 | 64 | if (args->format == FORMAT_XACD || args->format == FORMAT_STRCD) 65 | settings.format = PSX_AUDIO_XA_FORMAT_XACD; 66 | else 67 | settings.format = PSX_AUDIO_XA_FORMAT_XA; 68 | 69 | return settings; 70 | }; 71 | 72 | static void init_sector_buffer_video(const args_t *args, uint8_t *sector, int lba) { 73 | psx_cdrom_sector_xa_subheader_t *subheader = NULL; 74 | 75 | if (args->format == FORMAT_STRCD) { 76 | psx_cdrom_init_sector((psx_cdrom_sector_t *)sector, lba, PSX_CDROM_SECTOR_TYPE_MODE2_FORM1); 77 | subheader = ((psx_cdrom_sector_t *)sector)->mode2.subheader; 78 | } else if (args->format == FORMAT_STR) { 79 | subheader = (psx_cdrom_sector_xa_subheader_t *)sector; 80 | } 81 | 82 | if (subheader != NULL) { 83 | subheader->file = args->audio_xa_file; 84 | subheader->channel = args->audio_xa_channel & PSX_CDROM_SECTOR_XA_CHANNEL_MASK; 85 | subheader->submode = PSX_CDROM_SECTOR_XA_SUBMODE_DATA | PSX_CDROM_SECTOR_XA_SUBMODE_RT; 86 | subheader->coding = 0; 87 | 88 | memcpy(subheader + 1, subheader, sizeof(psx_cdrom_sector_xa_subheader_t)); 89 | } 90 | } 91 | 92 | #define VAG_HEADER_SIZE 0x30 93 | 94 | static void write_vag_header(const args_t *args, int size_per_channel, uint8_t *header) { 95 | memset(header, 0, VAG_HEADER_SIZE); 96 | 97 | // Magic 98 | header[0x00] = 'V'; 99 | header[0x01] = 'A'; 100 | header[0x02] = 'G'; 101 | 102 | if (args->format == FORMAT_VAGI) 103 | header[0x03] = 'i'; 104 | else 105 | header[0x03] = 'p'; 106 | 107 | // Version (big-endian) 108 | header[0x04] = 0x00; 109 | header[0x05] = 0x00; 110 | header[0x06] = 0x00; 111 | header[0x07] = 0x20; 112 | 113 | // Interleave (little-endian) 114 | if (args->format == FORMAT_VAGI) { 115 | header[0x08] = (uint8_t)args->audio_interleave; 116 | header[0x09] = (uint8_t)(args->audio_interleave >> 8); 117 | header[0x0A] = (uint8_t)(args->audio_interleave >> 16); 118 | header[0x0B] = (uint8_t)(args->audio_interleave >> 24); 119 | } 120 | 121 | // Length of data for each channel (big-endian) 122 | header[0x0C] = (uint8_t)(size_per_channel >> 24); 123 | header[0x0D] = (uint8_t)(size_per_channel >> 16); 124 | header[0x0E] = (uint8_t)(size_per_channel >> 8); 125 | header[0x0F] = (uint8_t)size_per_channel; 126 | 127 | // Sample rate (big-endian) 128 | header[0x10] = (uint8_t)(args->audio_frequency >> 24); 129 | header[0x11] = (uint8_t)(args->audio_frequency >> 16); 130 | header[0x12] = (uint8_t)(args->audio_frequency >> 8); 131 | header[0x13] = (uint8_t)args->audio_frequency; 132 | 133 | // Number of channels (little-endian) 134 | header[0x1E] = (uint8_t)args->audio_channels; 135 | header[0x1F] = 0x00; 136 | 137 | // Filename 138 | int name_offset = strlen(args->output_file); 139 | while ( 140 | name_offset > 0 && 141 | args->output_file[name_offset - 1] != '/' && 142 | args->output_file[name_offset - 1] != '\\' 143 | ) 144 | name_offset--; 145 | 146 | strncpy((char*)(header + 0x20), &args->output_file[name_offset], 16); 147 | } 148 | 149 | // The functions below are some peak spaghetti code I would rewrite if that 150 | // didn't also require scrapping the rest of the codebase. -- spicyjpeg 151 | 152 | void encode_file_xa(const args_t *args, decoder_t *decoder, FILE *output) { 153 | psx_audio_xa_settings_t xa_settings = args_to_libpsxav_xa_audio(args); 154 | 155 | int audio_samples_per_sector = psx_audio_xa_get_samples_per_sector(xa_settings); 156 | 157 | psx_audio_encoder_state_t audio_state; 158 | memset(&audio_state, 0, sizeof(psx_audio_encoder_state_t)); 159 | 160 | int sector_count = 0; 161 | 162 | for (; ensure_av_data(decoder, audio_samples_per_sector * args->audio_channels, 0); sector_count++) { 163 | int samples_length = decoder->audio_sample_count / args->audio_channels; 164 | 165 | if (samples_length > audio_samples_per_sector) 166 | samples_length = audio_samples_per_sector; 167 | 168 | uint8_t sector[PSX_CDROM_SECTOR_SIZE]; 169 | int length = psx_audio_xa_encode( 170 | xa_settings, 171 | &audio_state, 172 | decoder->audio_samples, 173 | samples_length, 174 | sector_count, 175 | sector 176 | ); 177 | 178 | if (decoder->end_of_input) 179 | psx_audio_xa_encode_finalize(xa_settings, sector, length); 180 | 181 | retire_av_data(decoder, samples_length * args->audio_channels, 0); 182 | fwrite(sector, length, 1, output); 183 | 184 | time_t t = get_elapsed_time(); 185 | 186 | if (!(args->flags & FLAG_HIDE_PROGRESS) && t) { 187 | fprintf( 188 | stderr, 189 | "\rLBA: %6d | Encoding speed: %5.2fx", 190 | sector_count, 191 | (double)(sector_count * audio_samples_per_sector) / (double)(args->audio_frequency * t) 192 | ); 193 | } 194 | } 195 | } 196 | 197 | void encode_file_spu(const args_t *args, decoder_t *decoder, FILE *output) { 198 | psx_audio_encoder_channel_state_t audio_state; 199 | memset(&audio_state, 0, sizeof(psx_audio_encoder_channel_state_t)); 200 | 201 | // The header must be written after the data as we don't yet know the 202 | // number of audio samples. 203 | if (args->format == FORMAT_VAG) 204 | fseek(output, VAG_HEADER_SIZE, SEEK_SET); 205 | 206 | uint8_t block[PSX_AUDIO_SPU_BLOCK_SIZE]; 207 | int block_count = 0; 208 | 209 | if (!(args->flags & FLAG_SPU_NO_LEADING_DUMMY)) { 210 | // Insert leading silent block 211 | memset(block, 0, PSX_AUDIO_SPU_BLOCK_SIZE); 212 | 213 | fwrite(block, PSX_AUDIO_SPU_BLOCK_SIZE, 1, output); 214 | block_count++; 215 | } 216 | 217 | int loop_start_block = -1; 218 | 219 | if (args->audio_loop_point >= 0) 220 | loop_start_block = block_count + (args->audio_loop_point * args->audio_frequency) / (PSX_AUDIO_SPU_SAMPLES_PER_BLOCK * 1000); 221 | 222 | for (; ensure_av_data(decoder, PSX_AUDIO_SPU_SAMPLES_PER_BLOCK, 0); block_count++) { 223 | int samples_length = decoder->audio_sample_count; 224 | 225 | if (samples_length > PSX_AUDIO_SPU_SAMPLES_PER_BLOCK) 226 | samples_length = PSX_AUDIO_SPU_SAMPLES_PER_BLOCK; 227 | 228 | int length = psx_audio_spu_encode( 229 | &audio_state, 230 | decoder->audio_samples, 231 | samples_length, 232 | 1, 233 | block 234 | ); 235 | 236 | if (block_count == loop_start_block) 237 | block[1] |= PSX_AUDIO_SPU_LOOP_START; 238 | if ((args->flags & FLAG_SPU_LOOP_END) && decoder->end_of_input) 239 | block[1] |= PSX_AUDIO_SPU_LOOP_REPEAT; 240 | 241 | retire_av_data(decoder, samples_length, 0); 242 | fwrite(block, length, 1, output); 243 | 244 | time_t t = get_elapsed_time(); 245 | 246 | if (!(args->flags & FLAG_HIDE_PROGRESS) && t) { 247 | fprintf( 248 | stderr, 249 | "\rBlock: %6d | Encoding speed: %5.2fx", 250 | block_count, 251 | (double)(block_count * PSX_AUDIO_SPU_SAMPLES_PER_BLOCK) / (double)(args->audio_frequency * t) 252 | ); 253 | } 254 | } 255 | 256 | if (!(args->flags & FLAG_SPU_LOOP_END)) { 257 | // Insert trailing looping block 258 | memset(block, 0, PSX_AUDIO_SPU_BLOCK_SIZE); 259 | block[1] = PSX_AUDIO_SPU_LOOP_START | PSX_AUDIO_SPU_LOOP_END; 260 | 261 | fwrite(block, PSX_AUDIO_SPU_BLOCK_SIZE, 1, output); 262 | block_count++; 263 | } 264 | 265 | int overflow = (block_count * PSX_AUDIO_SPU_BLOCK_SIZE) % args->alignment; 266 | 267 | if (overflow) { 268 | for (int i = 0; i < (args->alignment - overflow); i++) 269 | fputc(0, output); 270 | } 271 | if (args->format == FORMAT_VAG) { 272 | uint8_t header[VAG_HEADER_SIZE]; 273 | write_vag_header(args, block_count * PSX_AUDIO_SPU_BLOCK_SIZE, header); 274 | 275 | fseek(output, 0, SEEK_SET); 276 | fwrite(header, VAG_HEADER_SIZE, 1, output); 277 | } 278 | } 279 | 280 | void encode_file_spui(const args_t *args, decoder_t *decoder, FILE *output) { 281 | int audio_samples_per_chunk = args->audio_interleave / PSX_AUDIO_SPU_BLOCK_SIZE * PSX_AUDIO_SPU_SAMPLES_PER_BLOCK; 282 | 283 | // NOTE: since the interleaved .vag format is not standardized, some tools 284 | // (such as vgmstream) will not properly play files with interleave < 2048, 285 | // alignment != 2048 or channels != 2. 286 | int chunk_size = args->audio_interleave * args->audio_channels + args->alignment - 1; 287 | chunk_size -= chunk_size % args->alignment; 288 | 289 | int header_size = VAG_HEADER_SIZE + args->alignment - 1; 290 | header_size -= header_size % args->alignment; 291 | 292 | if (args->format == FORMAT_VAGI) 293 | fseek(output, header_size, SEEK_SET); 294 | 295 | int audio_state_size = sizeof(psx_audio_encoder_channel_state_t) * args->audio_channels; 296 | psx_audio_encoder_channel_state_t *audio_state = malloc(audio_state_size); 297 | memset(audio_state, 0, audio_state_size); 298 | 299 | uint8_t *chunk = malloc(chunk_size); 300 | int chunk_count = 0; 301 | 302 | for (; ensure_av_data(decoder, audio_samples_per_chunk * args->audio_channels, 0); chunk_count++) { 303 | int samples_length = decoder->audio_sample_count / args->audio_channels; 304 | 305 | if (samples_length > audio_samples_per_chunk) 306 | samples_length = audio_samples_per_chunk; 307 | 308 | memset(chunk, 0, chunk_size); 309 | uint8_t *chunk_ptr = chunk; 310 | 311 | // Insert leading silent block 312 | if (chunk_count == 0 && !(args->flags & FLAG_SPU_NO_LEADING_DUMMY)) { 313 | chunk_ptr += PSX_AUDIO_SPU_BLOCK_SIZE; 314 | samples_length -= PSX_AUDIO_SPU_SAMPLES_PER_BLOCK; 315 | } 316 | 317 | for (int ch = 0; ch < args->audio_channels; ch++, chunk_ptr += args->audio_interleave) { 318 | int length = psx_audio_spu_encode( 319 | audio_state + ch, 320 | decoder->audio_samples + ch, 321 | samples_length, 322 | args->audio_channels, 323 | chunk_ptr 324 | ); 325 | 326 | if (length > 0) { 327 | uint8_t *last_block = chunk_ptr + length - PSX_AUDIO_SPU_BLOCK_SIZE; 328 | 329 | if (args->flags & FLAG_SPU_LOOP_END) { 330 | last_block[1] = PSX_AUDIO_SPU_LOOP_REPEAT; 331 | } else if (decoder->end_of_input) { 332 | // HACK: the trailing block should in theory be appended to 333 | // the existing data, but it's easier to just zerofill and 334 | // repurpose the last encoded block 335 | memset(last_block, 0, PSX_AUDIO_SPU_BLOCK_SIZE); 336 | last_block[1] = PSX_AUDIO_SPU_LOOP_START | PSX_AUDIO_SPU_LOOP_END; 337 | } 338 | } 339 | } 340 | 341 | retire_av_data(decoder, samples_length * args->audio_channels, 0); 342 | fwrite(chunk, chunk_size, 1, output); 343 | 344 | time_t t = get_elapsed_time(); 345 | 346 | if (!(args->flags & FLAG_HIDE_PROGRESS) && t) { 347 | fprintf( 348 | stderr, 349 | "\rChunk: %6d | Encoding speed: %5.2fx", 350 | chunk_count, 351 | (double)(chunk_count * audio_samples_per_chunk) / (double)(args->audio_frequency * t) 352 | ); 353 | } 354 | 355 | } 356 | 357 | free(audio_state); 358 | free(chunk); 359 | 360 | if (args->format == FORMAT_VAGI) { 361 | uint8_t *header = malloc(header_size); 362 | memset(header, 0, header_size); 363 | write_vag_header(args, chunk_count * args->audio_interleave, header); 364 | 365 | fseek(output, 0, SEEK_SET); 366 | fwrite(header, header_size, 1, output); 367 | free(header); 368 | } 369 | } 370 | 371 | void encode_file_str(const args_t *args, decoder_t *decoder, FILE *output) { 372 | psx_audio_xa_settings_t xa_settings = args_to_libpsxav_xa_audio(args); 373 | int sector_size = psx_audio_xa_get_buffer_size_per_sector(xa_settings); 374 | 375 | int interleave; 376 | int audio_samples_per_sector; 377 | int video_sectors_per_block; 378 | 379 | if (decoder->state.audio_stream != NULL) { 380 | // 1/N audio, (N-1)/N video 381 | interleave = psx_audio_xa_get_sector_interleave(xa_settings) * args->str_cd_speed; 382 | audio_samples_per_sector = psx_audio_xa_get_samples_per_sector(xa_settings); 383 | video_sectors_per_block = interleave - 1; 384 | 385 | if (!(args->flags & FLAG_QUIET)) 386 | fprintf( 387 | stderr, 388 | "Interleave: %d/%d audio, %d/%d video\n", 389 | interleave - video_sectors_per_block, 390 | interleave, 391 | video_sectors_per_block, 392 | interleave 393 | ); 394 | } else { 395 | // 0/1 audio, 1/1 video 396 | interleave = 1; 397 | audio_samples_per_sector = 0; 398 | video_sectors_per_block = 1; 399 | } 400 | 401 | psx_audio_encoder_state_t audio_state; 402 | memset(&audio_state, 0, sizeof(psx_audio_encoder_state_t)); 403 | 404 | mdec_encoder_t encoder; 405 | init_mdec_encoder(&encoder, args->video_codec, args->video_width, args->video_height); 406 | 407 | // e.g. 15fps = (150*7/8/15) = 8.75 blocks per frame 408 | encoder.state.frame_block_base_overflow = (75 * args->str_cd_speed) * video_sectors_per_block * args->str_fps_den; 409 | encoder.state.frame_block_overflow_den = interleave * args->str_fps_num; 410 | double frame_size = (double)encoder.state.frame_block_base_overflow / (double)encoder.state.frame_block_overflow_den; 411 | 412 | if (!(args->flags & FLAG_QUIET)) 413 | fprintf(stderr, "Frame size: %.2f sectors\n", frame_size); 414 | 415 | encoder.state.frame_output = malloc(2016 * (int)ceil(frame_size)); 416 | encoder.state.frame_index = 0; 417 | encoder.state.frame_data_offset = 0; 418 | encoder.state.frame_max_size = 0; 419 | encoder.state.frame_block_overflow_num = 0; 420 | encoder.state.quant_scale_sum = 0; 421 | 422 | // FIXME: this needs an extra frame to prevent A/V desync 423 | int frames_needed = (int) ceil((double)video_sectors_per_block / frame_size); 424 | 425 | if (frames_needed < 2) 426 | frames_needed = 2; 427 | 428 | int sector_count = 0; 429 | 430 | for (; !decoder->end_of_input || encoder.state.frame_data_offset < encoder.state.frame_max_size; sector_count++) { 431 | ensure_av_data(decoder, audio_samples_per_sector * args->audio_channels, frames_needed); 432 | 433 | uint8_t sector[PSX_CDROM_SECTOR_SIZE]; 434 | bool is_video_sector; 435 | 436 | if (audio_samples_per_sector == 0) 437 | is_video_sector = true; 438 | else if (args->flags & FLAG_STR_TRAILING_AUDIO) 439 | is_video_sector = (sector_count % interleave) < video_sectors_per_block; 440 | else 441 | is_video_sector = (sector_count % interleave) > 0; 442 | 443 | if (is_video_sector) { 444 | init_sector_buffer_video(args, sector, sector_count); 445 | 446 | int frames_used = encode_sector_str( 447 | &encoder, 448 | args->format, 449 | args->str_video_id, 450 | decoder->video_frames, 451 | sector 452 | ); 453 | 454 | psx_cdrom_calculate_checksums((psx_cdrom_sector_t *)sector, PSX_CDROM_SECTOR_TYPE_MODE2_FORM1); 455 | retire_av_data(decoder, 0, frames_used); 456 | } else { 457 | int samples_length = decoder->audio_sample_count / args->audio_channels; 458 | 459 | if (samples_length > audio_samples_per_sector) 460 | samples_length = audio_samples_per_sector; 461 | 462 | // FIXME: this is an extremely hacky way to handle audio tracks 463 | // shorter than the video track 464 | if (!samples_length) 465 | video_sectors_per_block++; 466 | 467 | int length = psx_audio_xa_encode( 468 | xa_settings, 469 | &audio_state, 470 | decoder->audio_samples, 471 | samples_length, 472 | sector_count, 473 | sector 474 | ); 475 | 476 | if (decoder->end_of_input) 477 | psx_audio_xa_encode_finalize(xa_settings, sector, length); 478 | 479 | retire_av_data(decoder, samples_length * args->audio_channels, 0); 480 | } 481 | 482 | fwrite(sector, sector_size, 1, output); 483 | 484 | time_t t = get_elapsed_time(); 485 | 486 | if (!(args->flags & FLAG_HIDE_PROGRESS) && t) { 487 | fprintf( 488 | stderr, 489 | "\rFrame: %4d | LBA: %6d | Avg. q. scale: %5.2f | Encoding speed: %5.2fx", 490 | encoder.state.frame_index, 491 | sector_count, 492 | (double)encoder.state.quant_scale_sum / (double)encoder.state.frame_index, 493 | (double)(encoder.state.frame_index * args->str_fps_den) / (double)(t * args->str_fps_num) 494 | ); 495 | } 496 | } 497 | 498 | free(encoder.state.frame_output); 499 | destroy_mdec_encoder(&encoder); 500 | } 501 | 502 | void encode_file_strspu(const args_t *args, decoder_t *decoder, FILE *output) { 503 | int interleave; 504 | int audio_samples_per_sector; 505 | int video_sectors_per_block; 506 | 507 | if (decoder->state.audio_stream != NULL) { 508 | assert(false); // TODO: implement 509 | 510 | if (!(args->flags & FLAG_QUIET)) 511 | fprintf( 512 | stderr, 513 | "Interleave: %d/%d audio, %d/%d video\n", 514 | interleave - video_sectors_per_block, 515 | interleave, 516 | video_sectors_per_block, 517 | interleave 518 | ); 519 | } else { 520 | // 0/1 audio, 1/1 video 521 | interleave = 1; 522 | audio_samples_per_sector = 0; 523 | video_sectors_per_block = 1; 524 | } 525 | 526 | mdec_encoder_t encoder; 527 | init_mdec_encoder(&encoder, args->video_codec, args->video_width, args->video_height); 528 | 529 | // e.g. 15fps = (150*7/8/15) = 8.75 blocks per frame 530 | encoder.state.frame_block_base_overflow = (75 * args->str_cd_speed) * video_sectors_per_block * args->str_fps_den; 531 | encoder.state.frame_block_overflow_den = interleave * args->str_fps_num; 532 | double frame_size = (double)encoder.state.frame_block_base_overflow / (double)encoder.state.frame_block_overflow_den; 533 | 534 | if (!(args->flags & FLAG_QUIET)) 535 | fprintf(stderr, "Frame size: %.2f sectors\n", frame_size); 536 | 537 | encoder.state.frame_output = malloc(2016 * (int)ceil(frame_size)); 538 | encoder.state.frame_index = 0; 539 | encoder.state.frame_data_offset = 0; 540 | encoder.state.frame_max_size = 0; 541 | encoder.state.frame_block_overflow_num = 0; 542 | encoder.state.quant_scale_sum = 0; 543 | 544 | // FIXME: this needs an extra frame to prevent A/V desync 545 | int frames_needed = (int) ceil((double)video_sectors_per_block / frame_size); 546 | 547 | if (frames_needed < 2) 548 | frames_needed = 2; 549 | 550 | int sector_count = 0; 551 | 552 | for (; !decoder->end_of_input || encoder.state.frame_data_offset < encoder.state.frame_max_size; sector_count++) { 553 | ensure_av_data(decoder, audio_samples_per_sector * args->audio_channels, frames_needed); 554 | 555 | uint8_t sector[2048]; 556 | bool is_video_sector; 557 | 558 | if (audio_samples_per_sector == 0) 559 | is_video_sector = true; 560 | else if (args->flags & FLAG_STR_TRAILING_AUDIO) 561 | is_video_sector = (sector_count % interleave) < video_sectors_per_block; 562 | else 563 | is_video_sector = (sector_count % interleave) > 0; 564 | 565 | if (is_video_sector) { 566 | init_sector_buffer_video(args, sector, sector_count); 567 | 568 | int frames_used = encode_sector_str( 569 | &encoder, 570 | args->format, 571 | args->str_video_id, 572 | decoder->video_frames, 573 | sector 574 | ); 575 | 576 | retire_av_data(decoder, 0, frames_used); 577 | } else { 578 | int samples_length = decoder->audio_sample_count / args->audio_channels; 579 | 580 | if (samples_length > audio_samples_per_sector) 581 | samples_length = audio_samples_per_sector; 582 | 583 | // FIXME: this is an extremely hacky way to handle audio tracks 584 | // shorter than the video track 585 | if (!samples_length) 586 | video_sectors_per_block++; 587 | 588 | assert(false); // TODO: implement 589 | 590 | retire_av_data(decoder, samples_length * args->audio_channels, 0); 591 | } 592 | 593 | fwrite(sector, 2048, 1, output); 594 | 595 | time_t t = get_elapsed_time(); 596 | 597 | if (!(args->flags & FLAG_HIDE_PROGRESS) && t) { 598 | fprintf( 599 | stderr, 600 | "\rFrame: %4d | LBA: %6d | Avg. q. scale: %5.2f | Encoding speed: %5.2fx", 601 | encoder.state.frame_index, 602 | sector_count, 603 | (double)encoder.state.quant_scale_sum / (double)encoder.state.frame_index, 604 | (double)(encoder.state.frame_index * args->str_fps_den) / (double)(t * args->str_fps_num) 605 | ); 606 | } 607 | } 608 | 609 | free(encoder.state.frame_output); 610 | destroy_mdec_encoder(&encoder); 611 | } 612 | 613 | void encode_file_sbs(const args_t *args, decoder_t *decoder, FILE *output) { 614 | mdec_encoder_t encoder; 615 | init_mdec_encoder(&encoder, args->video_codec, args->video_width, args->video_height); 616 | 617 | encoder.state.frame_output = malloc(args->alignment); 618 | encoder.state.frame_data_offset = 0; 619 | encoder.state.frame_max_size = args->alignment; 620 | encoder.state.quant_scale_sum = 0; 621 | 622 | for (int j = 0; ensure_av_data(decoder, 0, 1); j++) { 623 | encode_frame_bs(&encoder, decoder->video_frames); 624 | 625 | retire_av_data(decoder, 0, 1); 626 | fwrite(encoder.state.frame_output, args->alignment, 1, output); 627 | 628 | time_t t = get_elapsed_time(); 629 | 630 | if (!(args->flags & FLAG_HIDE_PROGRESS) && t) { 631 | fprintf( 632 | stderr, 633 | "\rFrame: %4d | Avg. q. scale: %5.2f | Encoding speed: %5.2fx", 634 | j, 635 | (double)encoder.state.quant_scale_sum / (double)j, 636 | (double)(j * args->str_fps_den) / (double)(t * args->str_fps_num) 637 | ); 638 | } 639 | } 640 | 641 | free(encoder.state.frame_output); 642 | destroy_mdec_encoder(&encoder); 643 | } 644 | -------------------------------------------------------------------------------- /psxavenc/filefmt.h: -------------------------------------------------------------------------------- 1 | /* 2 | psxavenc: MDEC video + SPU/XA-ADPCM audio encoder frontend 3 | 4 | Copyright (c) 2019, 2020 Adrian "asie" Siekierka 5 | Copyright (c) 2019 Ben "GreaseMonkey" Russell 6 | Copyright (c) 2023, 2025 spicyjpeg 7 | 8 | This software is provided 'as-is', without any express or implied 9 | warranty. In no event will the authors be held liable for any damages 10 | arising from the use of this software. 11 | 12 | Permission is granted to anyone to use this software for any purpose, 13 | including commercial applications, and to alter it and redistribute it 14 | freely, subject to the following restrictions: 15 | 16 | 1. The origin of this software must not be misrepresented; you must not 17 | claim that you wrote the original software. If you use this software 18 | in a product, an acknowledgment in the product documentation would be 19 | appreciated but is not required. 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 3. This notice may not be removed or altered from any source distribution. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include 28 | #include "args.h" 29 | #include "decoding.h" 30 | 31 | void encode_file_xa(const args_t *args, decoder_t *decoder, FILE *output); 32 | void encode_file_spu(const args_t *args, decoder_t *decoder, FILE *output); 33 | void encode_file_spui(const args_t *args, decoder_t *decoder, FILE *output); 34 | void encode_file_str(const args_t *args, decoder_t *decoder, FILE *output); 35 | void encode_file_strspu(const args_t *args, decoder_t *decoder, FILE *output); 36 | void encode_file_sbs(const args_t *args, decoder_t *decoder, FILE *output); 37 | -------------------------------------------------------------------------------- /psxavenc/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | psxavenc: MDEC video + SPU/XA-ADPCM audio encoder frontend 3 | 4 | Copyright (c) 2019, 2020 Adrian "asie" Siekierka 5 | Copyright (c) 2019 Ben "GreaseMonkey" Russell 6 | Copyright (c) 2023 spicyjpeg 7 | 8 | This software is provided 'as-is', without any express or implied 9 | warranty. In no event will the authors be held liable for any damages 10 | arising from the use of this software. 11 | 12 | Permission is granted to anyone to use this software for any purpose, 13 | including commercial applications, and to alter it and redistribute it 14 | freely, subject to the following restrictions: 15 | 16 | 1. The origin of this software must not be misrepresented; you must not 17 | claim that you wrote the original software. If you use this software 18 | in a product, an acknowledgment in the product documentation would be 19 | appreciated but is not required. 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 3. This notice may not be removed or altered from any source distribution. 23 | */ 24 | 25 | #include 26 | #include 27 | #include "args.h" 28 | #include "decoding.h" 29 | #include "filefmt.h" 30 | 31 | static const char *const bs_codec_names[NUM_BS_CODECS] = { 32 | "BS v2", 33 | "BS v3", 34 | "BS v3 (with DC wrapping)" 35 | }; 36 | 37 | static const uint8_t decoder_flags[NUM_FORMATS] = { 38 | DECODER_USE_AUDIO | DECODER_AUDIO_REQUIRED, // xa 39 | DECODER_USE_AUDIO | DECODER_AUDIO_REQUIRED, // xacd 40 | DECODER_USE_AUDIO | DECODER_AUDIO_REQUIRED, // spu 41 | DECODER_USE_AUDIO | DECODER_AUDIO_REQUIRED, // vag 42 | DECODER_USE_AUDIO | DECODER_AUDIO_REQUIRED, // spui 43 | DECODER_USE_AUDIO | DECODER_AUDIO_REQUIRED, // vagi 44 | DECODER_USE_AUDIO | DECODER_USE_VIDEO | DECODER_VIDEO_REQUIRED, // str 45 | DECODER_USE_AUDIO | DECODER_USE_VIDEO | DECODER_VIDEO_REQUIRED, // strcd 46 | DECODER_USE_AUDIO | DECODER_USE_VIDEO | DECODER_VIDEO_REQUIRED, // strspu 47 | DECODER_USE_VIDEO | DECODER_VIDEO_REQUIRED, // strv 48 | DECODER_USE_VIDEO | DECODER_VIDEO_REQUIRED // sbs 49 | }; 50 | 51 | int main(int argc, const char **argv) { 52 | args_t args; 53 | decoder_t decoder; 54 | FILE *output; 55 | 56 | args.flags = 0; 57 | 58 | args.format = FORMAT_INVALID; 59 | args.input_file = NULL; 60 | args.output_file = NULL; 61 | args.swresample_options = NULL; 62 | args.swscale_options = NULL; 63 | 64 | if (!parse_args(&args, argv + 1, argc - 1)) 65 | return 1; 66 | if (!open_av_data(&decoder, &args, decoder_flags[args.format])) { 67 | fprintf(stderr, "Failed to open input file: %s\n", args.input_file); 68 | return 1; 69 | } 70 | 71 | output = fopen(args.output_file, "wb"); 72 | 73 | if (output == NULL) { 74 | fprintf(stderr, "Failed to open output file: %s\n", args.output_file); 75 | return 1; 76 | } 77 | 78 | switch (args.format) { 79 | case FORMAT_XA: 80 | case FORMAT_XACD: 81 | if (!(args.flags & FLAG_QUIET)) 82 | fprintf( 83 | stderr, 84 | "Audio format: XA-ADPCM, %d Hz %d-bit %s, F=%d C=%d\n", 85 | args.audio_frequency, 86 | args.audio_bit_depth, 87 | (args.audio_channels == 2) ? "stereo" : "mono", 88 | args.audio_xa_file, 89 | args.audio_xa_channel 90 | ); 91 | 92 | encode_file_xa(&args, &decoder, output); 93 | break; 94 | 95 | case FORMAT_SPU: 96 | case FORMAT_VAG: 97 | if (!(args.flags & FLAG_QUIET)) 98 | fprintf( 99 | stderr, 100 | "Audio format: SPU-ADPCM, %d Hz mono\n", 101 | args.audio_frequency 102 | ); 103 | 104 | encode_file_spu(&args, &decoder, output); 105 | break; 106 | 107 | case FORMAT_SPUI: 108 | case FORMAT_VAGI: 109 | if (!(args.flags & FLAG_QUIET)) 110 | fprintf( 111 | stderr, 112 | "Audio format: SPU-ADPCM, %d Hz %d channels, interleave=%d\n", 113 | args.audio_frequency, 114 | args.audio_channels, 115 | args.audio_interleave 116 | ); 117 | 118 | encode_file_spui(&args, &decoder, output); 119 | break; 120 | 121 | case FORMAT_STR: 122 | case FORMAT_STRCD: 123 | if (!(args.flags & FLAG_QUIET)) { 124 | if (decoder.state.audio_stream) 125 | fprintf( 126 | stderr, 127 | "Audio format: XA-ADPCM, %d Hz %d-bit %s, F=%d C=%d\n", 128 | args.audio_frequency, 129 | args.audio_bit_depth, 130 | (args.audio_channels == 2) ? "stereo" : "mono", 131 | args.audio_xa_file, 132 | args.audio_xa_channel 133 | ); 134 | 135 | fprintf( 136 | stderr, 137 | "Video format: %s, %dx%d, %.2f fps\n", 138 | bs_codec_names[args.video_codec], 139 | args.video_width, 140 | args.video_height, 141 | (double)args.str_fps_num / (double)args.str_fps_den 142 | ); 143 | } 144 | 145 | encode_file_str(&args, &decoder, output); 146 | break; 147 | 148 | case FORMAT_STRSPU: 149 | // TODO: implement and remove this check 150 | fprintf(stderr, "This format is not currently supported\n"); 151 | break; 152 | 153 | case FORMAT_STRV: 154 | if (!(args.flags & FLAG_QUIET)) { 155 | if (decoder.state.audio_stream) 156 | fprintf( 157 | stderr, 158 | "Audio format: SPU-ADPCM, %d Hz %d channels, interleave=%d\n", 159 | args.audio_frequency, 160 | args.audio_channels, 161 | args.audio_interleave 162 | ); 163 | 164 | fprintf( 165 | stderr, 166 | "Video format: %s, %dx%d, %.2f fps\n", 167 | bs_codec_names[args.video_codec], 168 | args.video_width, 169 | args.video_height, 170 | (double)args.str_fps_num / (double)args.str_fps_den 171 | ); 172 | } 173 | 174 | encode_file_strspu(&args, &decoder, output); 175 | break; 176 | 177 | case FORMAT_SBS: 178 | if (!(args.flags & FLAG_QUIET)) 179 | fprintf( 180 | stderr, 181 | "Video format: %s, %dx%d, %.2f fps\n", 182 | bs_codec_names[args.video_codec], 183 | args.video_width, 184 | args.video_height, 185 | (double)args.str_fps_num / (double)args.str_fps_den 186 | ); 187 | 188 | encode_file_sbs(&args, &decoder, output); 189 | break; 190 | 191 | default: 192 | ; 193 | } 194 | 195 | if (!(args.flags & FLAG_HIDE_PROGRESS)) 196 | fprintf(stderr, "\nDone.\n"); 197 | 198 | fclose(output); 199 | close_av_data(&decoder); 200 | return 0; 201 | } 202 | -------------------------------------------------------------------------------- /psxavenc/mdec.c: -------------------------------------------------------------------------------- 1 | /* 2 | psxavenc: MDEC video + SPU/XA-ADPCM audio encoder frontend 3 | 4 | Copyright (c) 2019, 2020 Adrian "asie" Siekierka 5 | Copyright (c) 2019 Ben "GreaseMonkey" Russell 6 | Copyright (c) 2023 spicyjpeg 7 | 8 | This software is provided 'as-is', without any express or implied 9 | warranty. In no event will the authors be held liable for any damages 10 | arising from the use of this software. 11 | 12 | Permission is granted to anyone to use this software for any purpose, 13 | including commercial applications, and to alter it and redistribute it 14 | freely, subject to the following restrictions: 15 | 16 | 1. The origin of this software must not be misrepresented; you must not 17 | claim that you wrote the original software. If you use this software 18 | in a product, an acknowledgment in the product documentation would be 19 | appreciated but is not required. 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 3. This notice may not be removed or altered from any source distribution. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include "args.h" 33 | #include "mdec.h" 34 | 35 | #define AC_PAIR(zeroes, value) \ 36 | (((zeroes) << 10) | ((+(value)) & 0x3FF)), \ 37 | (((zeroes) << 10) | ((-(value)) & 0x3FF)) 38 | 39 | static const struct { 40 | int c_bits; 41 | uint32_t c_value; 42 | uint16_t u_hword_pos; 43 | uint16_t u_hword_neg; 44 | } ac_huffman_tree[] = { 45 | // Fuck this Huffman tree in particular --GM 46 | { 2, 0x3, AC_PAIR( 0, 1)}, 47 | { 3, 0x3, AC_PAIR( 1, 1)}, 48 | { 4, 0x4, AC_PAIR( 0, 2)}, 49 | { 4, 0x5, AC_PAIR( 2, 1)}, 50 | { 5, 0x05, AC_PAIR( 0, 3)}, 51 | { 5, 0x06, AC_PAIR( 4, 1)}, 52 | { 5, 0x07, AC_PAIR( 3, 1)}, 53 | { 6, 0x04, AC_PAIR( 7, 1)}, 54 | { 6, 0x05, AC_PAIR( 6, 1)}, 55 | { 6, 0x06, AC_PAIR( 1, 2)}, 56 | { 6, 0x07, AC_PAIR( 5, 1)}, 57 | { 7, 0x04, AC_PAIR( 2, 2)}, 58 | { 7, 0x05, AC_PAIR( 9, 1)}, 59 | { 7, 0x06, AC_PAIR( 0, 4)}, 60 | { 7, 0x07, AC_PAIR( 8, 1)}, 61 | { 8, 0x20, AC_PAIR(13, 1)}, 62 | { 8, 0x21, AC_PAIR( 0, 6)}, 63 | { 8, 0x22, AC_PAIR(12, 1)}, 64 | { 8, 0x23, AC_PAIR(11, 1)}, 65 | { 8, 0x24, AC_PAIR( 3, 2)}, 66 | { 8, 0x25, AC_PAIR( 1, 3)}, 67 | { 8, 0x26, AC_PAIR( 0, 5)}, 68 | { 8, 0x27, AC_PAIR(10, 1)}, 69 | {10, 0x008, AC_PAIR(16, 1)}, 70 | {10, 0x009, AC_PAIR( 5, 2)}, 71 | {10, 0x00A, AC_PAIR( 0, 7)}, 72 | {10, 0x00B, AC_PAIR( 2, 3)}, 73 | {10, 0x00C, AC_PAIR( 1, 4)}, 74 | {10, 0x00D, AC_PAIR(15, 1)}, 75 | {10, 0x00E, AC_PAIR(14, 1)}, 76 | {10, 0x00F, AC_PAIR( 4, 2)}, 77 | {12, 0x010, AC_PAIR( 0, 11)}, 78 | {12, 0x011, AC_PAIR( 8, 2)}, 79 | {12, 0x012, AC_PAIR( 4, 3)}, 80 | {12, 0x013, AC_PAIR( 0, 10)}, 81 | {12, 0x014, AC_PAIR( 2, 4)}, 82 | {12, 0x015, AC_PAIR( 7, 2)}, 83 | {12, 0x016, AC_PAIR(21, 1)}, 84 | {12, 0x017, AC_PAIR(20, 1)}, 85 | {12, 0x018, AC_PAIR( 0, 9)}, 86 | {12, 0x019, AC_PAIR(19, 1)}, 87 | {12, 0x01A, AC_PAIR(18, 1)}, 88 | {12, 0x01B, AC_PAIR( 1, 5)}, 89 | {12, 0x01C, AC_PAIR( 3, 3)}, 90 | {12, 0x01D, AC_PAIR( 0, 8)}, 91 | {12, 0x01E, AC_PAIR( 6, 2)}, 92 | {12, 0x01F, AC_PAIR(17, 1)}, 93 | {13, 0x0010, AC_PAIR(10, 2)}, 94 | {13, 0x0011, AC_PAIR( 9, 2)}, 95 | {13, 0x0012, AC_PAIR( 5, 3)}, 96 | {13, 0x0013, AC_PAIR( 3, 4)}, 97 | {13, 0x0014, AC_PAIR( 2, 5)}, 98 | {13, 0x0015, AC_PAIR( 1, 7)}, 99 | {13, 0x0016, AC_PAIR( 1, 6)}, 100 | {13, 0x0017, AC_PAIR( 0, 15)}, 101 | {13, 0x0018, AC_PAIR( 0, 14)}, 102 | {13, 0x0019, AC_PAIR( 0, 13)}, 103 | {13, 0x001A, AC_PAIR( 0, 12)}, 104 | {13, 0x001B, AC_PAIR(26, 1)}, 105 | {13, 0x001C, AC_PAIR(25, 1)}, 106 | {13, 0x001D, AC_PAIR(24, 1)}, 107 | {13, 0x001E, AC_PAIR(23, 1)}, 108 | {13, 0x001F, AC_PAIR(22, 1)}, 109 | {14, 0x0010, AC_PAIR( 0, 31)}, 110 | {14, 0x0011, AC_PAIR( 0, 30)}, 111 | {14, 0x0012, AC_PAIR( 0, 29)}, 112 | {14, 0x0013, AC_PAIR( 0, 28)}, 113 | {14, 0x0014, AC_PAIR( 0, 27)}, 114 | {14, 0x0015, AC_PAIR( 0, 26)}, 115 | {14, 0x0016, AC_PAIR( 0, 25)}, 116 | {14, 0x0017, AC_PAIR( 0, 24)}, 117 | {14, 0x0018, AC_PAIR( 0, 23)}, 118 | {14, 0x0019, AC_PAIR( 0, 22)}, 119 | {14, 0x001A, AC_PAIR( 0, 21)}, 120 | {14, 0x001B, AC_PAIR( 0, 20)}, 121 | {14, 0x001C, AC_PAIR( 0, 19)}, 122 | {14, 0x001D, AC_PAIR( 0, 18)}, 123 | {14, 0x001E, AC_PAIR( 0, 17)}, 124 | {14, 0x001F, AC_PAIR( 0, 16)}, 125 | {15, 0x0010, AC_PAIR( 0, 40)}, 126 | {15, 0x0011, AC_PAIR( 0, 39)}, 127 | {15, 0x0012, AC_PAIR( 0, 38)}, 128 | {15, 0x0013, AC_PAIR( 0, 37)}, 129 | {15, 0x0014, AC_PAIR( 0, 36)}, 130 | {15, 0x0015, AC_PAIR( 0, 35)}, 131 | {15, 0x0016, AC_PAIR( 0, 34)}, 132 | {15, 0x0017, AC_PAIR( 0, 33)}, 133 | {15, 0x0018, AC_PAIR( 0, 32)}, 134 | {15, 0x0019, AC_PAIR( 1, 14)}, 135 | {15, 0x001A, AC_PAIR( 1, 13)}, 136 | {15, 0x001B, AC_PAIR( 1, 12)}, 137 | {15, 0x001C, AC_PAIR( 1, 11)}, 138 | {15, 0x001D, AC_PAIR( 1, 10)}, 139 | {15, 0x001E, AC_PAIR( 1, 9)}, 140 | {15, 0x001F, AC_PAIR( 1, 8)}, 141 | {16, 0x0010, AC_PAIR( 1, 18)}, 142 | {16, 0x0011, AC_PAIR( 1, 17)}, 143 | {16, 0x0012, AC_PAIR( 1, 16)}, 144 | {16, 0x0013, AC_PAIR( 1, 15)}, 145 | {16, 0x0014, AC_PAIR( 6, 3)}, 146 | {16, 0x0015, AC_PAIR(16, 2)}, 147 | {16, 0x0016, AC_PAIR(15, 2)}, 148 | {16, 0x0017, AC_PAIR(14, 2)}, 149 | {16, 0x0018, AC_PAIR(13, 2)}, 150 | {16, 0x0019, AC_PAIR(12, 2)}, 151 | {16, 0x001A, AC_PAIR(11, 2)}, 152 | {16, 0x001B, AC_PAIR(31, 1)}, 153 | {16, 0x001C, AC_PAIR(30, 1)}, 154 | {16, 0x001D, AC_PAIR(29, 1)}, 155 | {16, 0x001E, AC_PAIR(28, 1)}, 156 | {16, 0x001F, AC_PAIR(27, 1)} 157 | }; 158 | 159 | static const struct { 160 | int c_bits; 161 | uint32_t c_value; 162 | int dc_bits; 163 | } dc_c_huffman_tree[] = { 164 | {2, 0x1, 0}, 165 | {2, 0x2, 1}, 166 | {3, 0x6, 2}, 167 | {4, 0xE, 3}, 168 | {5, 0x1E, 4}, 169 | {6, 0x3E, 5}, 170 | {7, 0x7E, 6}, 171 | {8, 0xFE, 7} 172 | }; 173 | 174 | static const struct { 175 | int c_bits; 176 | uint32_t c_value; 177 | int dc_bits; 178 | } dc_y_huffman_tree[] = { 179 | {2, 0x0, 0}, 180 | {2, 0x1, 1}, 181 | {3, 0x5, 2}, 182 | {3, 0x6, 3}, 183 | {4, 0xE, 4}, 184 | {5, 0x1E, 5}, 185 | {6, 0x3E, 6}, 186 | {7, 0x7E, 7} 187 | }; 188 | 189 | static const uint8_t quant_dec[8*8] = { 190 | 2, 16, 19, 22, 26, 27, 29, 34, 191 | 16, 16, 22, 24, 27, 29, 34, 37, 192 | 19, 22, 26, 27, 29, 34, 34, 38, 193 | 22, 22, 26, 27, 29, 34, 37, 40, 194 | 22, 26, 27, 29, 32, 35, 40, 48, 195 | 26, 27, 29, 32, 35, 40, 48, 58, 196 | 26, 27, 29, 34, 38, 46, 56, 69, 197 | 27, 29, 35, 38, 46, 56, 69, 83 198 | }; 199 | 200 | #if 0 201 | static const uint8_t dct_zigzag_table[8*8] = { 202 | 0, 1, 5, 6, 14, 15, 27, 28, 203 | 2, 4, 7, 13, 16, 26, 29, 42, 204 | 3, 8, 12, 17, 25, 30, 41, 43, 205 | 9, 11, 18, 24, 31, 40, 44, 53, 206 | 10, 19, 23, 32, 39, 45, 52, 54, 207 | 20, 22, 33, 38, 46, 51, 55, 60, 208 | 21, 34, 37, 47, 50, 56, 59, 61, 209 | 35, 36, 48, 49, 57, 58, 62, 63 210 | }; 211 | #endif 212 | 213 | static const uint8_t dct_zagzig_table[8*8] = { 214 | 0, 1, 8, 16, 9, 2, 3, 10, 215 | 17, 24, 32, 25, 18, 11, 4, 5, 216 | 12, 19, 26, 33, 40, 48, 41, 34, 217 | 27, 20, 13, 6, 7, 14, 21, 28, 218 | 35, 42, 49, 56, 57, 50, 43, 36, 219 | 29, 22, 15, 23, 30, 37, 44, 51, 220 | 58, 59, 52, 45, 38, 31, 39, 46, 221 | 53, 60, 61, 54, 47, 55, 62, 63 222 | }; 223 | 224 | #if 0 225 | enum { 226 | SF0 = 0x5a82, // cos(0/16 * pi) * sqrt(2) 227 | SF1 = 0x7d8a, // cos(1/16 * pi) * 2 228 | SF2 = 0x7641, // cos(2/16 * pi) * 2 229 | SF3 = 0x6a6d, // cos(3/16 * pi) * 2 230 | SF4 = 0x5a82, // cos(4/16 * pi) * 2 231 | SF5 = 0x471c, // cos(5/16 * pi) * 2 232 | SF6 = 0x30fb, // cos(6/16 * pi) * 2 233 | SF7 = 0x18f8 // cos(7/16 * pi) * 2 234 | }; 235 | 236 | static const int16_t dct_scale_table[8*8] = { 237 | SF0, SF0, SF0, SF0, SF0, SF0, SF0, SF0, 238 | SF1, SF3, SF5, SF7, -SF7, -SF5, -SF3, -SF1, 239 | SF2, SF6, -SF6, -SF2, -SF2, -SF6, SF6, SF2, 240 | SF3, -SF7, -SF1, -SF5, SF5, SF1, SF7, -SF3, 241 | SF4, -SF4, -SF4, SF4, SF4, -SF4, -SF4, SF4, 242 | SF5, -SF1, SF7, SF3, -SF3, -SF7, SF1, -SF5, 243 | SF6, -SF2, SF2, -SF6, -SF6, SF2, -SF2, SF6, 244 | SF7, -SF5, SF3, -SF1, SF1, -SF3, SF5, -SF7 245 | }; 246 | #endif 247 | 248 | enum { 249 | INDEX_CR, 250 | INDEX_CB, 251 | INDEX_Y 252 | }; 253 | 254 | #define HUFFMAN_CODE(bits, value) (((bits) << 24) | (value)) 255 | 256 | static void init_dct_data(mdec_encoder_state_t *state, bs_codec_t codec) { 257 | for(int i = 0; i <= 0xFFFF; i++) { 258 | state->ac_huffman_map[i] = HUFFMAN_CODE(6 + 16, (0x1 << 16) | i); 259 | 260 | int16_t coeff = (int16_t)i; 261 | 262 | if (coeff < -0x200) 263 | coeff = -0x200; 264 | else if (coeff > +0x1FE) 265 | coeff = +0x1FE; // 0x1FF = v2 end of frame 266 | 267 | state->coeff_clamp_map[i] = coeff; 268 | } 269 | 270 | state->dc_huffman_map[(INDEX_CR << 9) | 0] = HUFFMAN_CODE(2, 0x0); 271 | state->dc_huffman_map[(INDEX_CB << 9) | 0] = HUFFMAN_CODE(2, 0x0); 272 | state->dc_huffman_map[(INDEX_Y << 9) | 0] = HUFFMAN_CODE(3, 0x4); 273 | 274 | int ac_tree_item_count = sizeof(ac_huffman_tree) / sizeof(ac_huffman_tree[0]); 275 | int dc_c_tree_item_count = sizeof(dc_c_huffman_tree) / sizeof(dc_c_huffman_tree[0]); 276 | int dc_y_tree_item_count = sizeof(dc_y_huffman_tree) / sizeof(dc_y_huffman_tree[0]); 277 | 278 | for (int i = 0; i < ac_tree_item_count; i++) { 279 | int bits = ac_huffman_tree[i].c_bits + 1; 280 | uint32_t base_value = ac_huffman_tree[i].c_value; 281 | 282 | state->ac_huffman_map[ac_huffman_tree[i].u_hword_pos] = HUFFMAN_CODE(bits, (base_value << 1) | 0); 283 | state->ac_huffman_map[ac_huffman_tree[i].u_hword_neg] = HUFFMAN_CODE(bits, (base_value << 1) | 1); 284 | } 285 | for (int i = 0; i < dc_c_tree_item_count; i++) { 286 | int dc_bits = dc_c_huffman_tree[i].dc_bits; 287 | int bits = dc_c_huffman_tree[i].c_bits + 1 + dc_bits; 288 | uint32_t base_value = dc_c_huffman_tree[i].c_value; 289 | 290 | int pos_offset = 1 << dc_bits; 291 | int neg_offset = pos_offset * 2 - 1; 292 | 293 | for (int j = 0; j < (1 << dc_bits); j++) { 294 | int pos = (j + pos_offset) & 0x1FF; 295 | int neg = (j - neg_offset) & 0x1FF; 296 | 297 | state->dc_huffman_map[(INDEX_CR << 9) | pos] = HUFFMAN_CODE(bits, (base_value << (dc_bits + 1)) | (1 << dc_bits) | j); 298 | state->dc_huffman_map[(INDEX_CR << 9) | neg] = HUFFMAN_CODE(bits, (base_value << (dc_bits + 1)) | (0 << dc_bits) | j); 299 | state->dc_huffman_map[(INDEX_CB << 9) | pos] = HUFFMAN_CODE(bits, (base_value << (dc_bits + 1)) | (1 << dc_bits) | j); 300 | state->dc_huffman_map[(INDEX_CB << 9) | neg] = HUFFMAN_CODE(bits, (base_value << (dc_bits + 1)) | (0 << dc_bits) | j); 301 | } 302 | } 303 | for (int i = 0; i < dc_y_tree_item_count; i++) { 304 | int dc_bits = dc_y_huffman_tree[i].dc_bits; 305 | int bits = dc_y_huffman_tree[i].c_bits + 1 + dc_bits; 306 | uint32_t base_value = dc_y_huffman_tree[i].c_value; 307 | 308 | int pos_offset = 1 << dc_bits; 309 | int neg_offset = pos_offset * 2 - 1; 310 | 311 | for (int j = 0; j < (1 << dc_bits); j++) { 312 | int pos = (j + pos_offset) & 0x1FF; 313 | int neg = (j - neg_offset) & 0x1FF; 314 | 315 | state->dc_huffman_map[(INDEX_Y << 9) | pos] = HUFFMAN_CODE(bits, (base_value << (dc_bits + 1)) | (1 << dc_bits) | j); 316 | state->dc_huffman_map[(INDEX_Y << 9) | neg] = HUFFMAN_CODE(bits, (base_value << (dc_bits + 1)) | (0 << dc_bits) | j); 317 | } 318 | } 319 | } 320 | 321 | static bool flush_bits(mdec_encoder_state_t *state) { 322 | if(state->bits_left < 16) { 323 | state->frame_output[state->bytes_used++] = (uint8_t)state->bits_value; 324 | if (state->bytes_used >= state->frame_max_size) 325 | return false; 326 | 327 | state->frame_output[state->bytes_used++] = (uint8_t)(state->bits_value>>8); 328 | } 329 | 330 | state->bits_left = 16; 331 | state->bits_value = 0; 332 | return true; 333 | } 334 | 335 | static bool encode_bits(mdec_encoder_state_t *state, int bits, uint32_t val) { 336 | assert(val < (1< 16 339 | // and I have no idea why, so I have to split this up --GM 340 | if (bits > 16) { 341 | if (!encode_bits(state, bits-16, val>>16)) 342 | return false; 343 | 344 | bits = 16; 345 | val &= 0xFFFF; 346 | } 347 | 348 | if (state->bits_left == 0) { 349 | if (!flush_bits(state)) 350 | return false; 351 | } 352 | 353 | while (bits > state->bits_left) { 354 | // Bits need truncating 355 | uint32_t outval = val; 356 | outval >>= bits - state->bits_left; 357 | assert(outval < (1<<16)); 358 | //uint16_t old_value = state->bits_value; 359 | assert((state->bits_value & outval) == 0); 360 | state->bits_value |= (uint16_t)outval; 361 | //fprintf(stderr, "trunc %2d %2d %08X %04X %04X\n", bits, state->bits_left, val, old_value, state->bits_value); 362 | bits -= state->bits_left; 363 | uint32_t mask = (1<= 1); 366 | assert(val < (1<= 1) { 372 | assert(bits <= 16); 373 | // Bits may need shifting into place 374 | uint32_t outval = val; 375 | outval <<= state->bits_left - bits; 376 | assert(outval < (1<<16)); 377 | //uint16_t old_value = state->bits_value; 378 | assert((state->bits_value & outval) == 0); 379 | state->bits_value |= (uint16_t)outval; 380 | //fprintf(stderr, "plop %2d %2d %08X %04X %04X\n", bits, state->bits_left, val, state->bits_value); 381 | state->bits_left -= bits; 382 | } 383 | 384 | return true; 385 | } 386 | 387 | #if 0 388 | static void transform_dct_block(int16_t *block) { 389 | // Apply DCT to block 390 | int midblock[8*8]; 391 | 392 | for (int i = 0; i < 8; i++) { 393 | for (int j = 0; j < 8; j++) { 394 | int v = 0; 395 | for(int k = 0; k < 8; k++) { 396 | v += (int)block[8*j+k] * (int)dct_scale_table[8*i+k] / 8; 397 | } 398 | midblock[8*i+j] = (v + 0xFFF) >> 13; 399 | } 400 | } 401 | for (int i = 0; i < 8; i++) { 402 | for (int j = 0; j < 8; j++) { 403 | int v = 0; 404 | for(int k = 0; k < 8; k++) { 405 | v += (int)midblock[8*j+k] * (int)dct_scale_table[8*i+k]; 406 | } 407 | block[8*i+j] = (int16_t)((v + 0xFFF) >> 13); 408 | } 409 | } 410 | } 411 | 412 | static int reduce_dct_block(mdec_encoder_state_t *state, int32_t *block, int32_t min_val, int *values_to_shed) { 413 | // Reduce so it can all fit 414 | int nonzeroes = 0; 415 | 416 | for (int i = 1; i < 64; i++) { 417 | //int ri = dct_zigzag_table[i]; 418 | if (block[i] != 0) { 419 | //if (abs(block[i])+(ri>>3) < min_val+(64>>3)) { 420 | if ((*values_to_shed) > 0 && abs(block[i]) < min_val*1) { 421 | block[i] = 0; 422 | (*values_to_shed)--; 423 | } else { 424 | nonzeroes++; 425 | } 426 | } 427 | } 428 | 429 | // Factor in DC + EOF values 430 | return nonzeroes+2; 431 | } 432 | #endif 433 | 434 | // https://stackoverflow.com/a/60011209 435 | #if 0 436 | #define DIVIDE_ROUNDED(n, d) (((n) >= 0) ? (((n) + (d)/2) / (d)) : (((n) - (d)/2) / (d))) 437 | #else 438 | #define DIVIDE_ROUNDED(n, d) ((int)round((double)(n) / (double)(d))) 439 | #endif 440 | 441 | static bool encode_dct_block( 442 | mdec_encoder_state_t *state, 443 | bs_codec_t codec, 444 | const int16_t *block, 445 | const int16_t *quant_table 446 | ) { 447 | int dc = DIVIDE_ROUNDED(block[0], quant_table[0]); 448 | 449 | dc = state->coeff_clamp_map[dc & 0xFFFF]; 450 | 451 | if (codec == BS_CODEC_V2) { 452 | if (!encode_bits(state, 10, dc & 0x3FF)) 453 | return false; 454 | } else { 455 | int index = state->block_type; 456 | 457 | if (index > INDEX_Y) 458 | index = INDEX_Y; 459 | 460 | int delta = DIVIDE_ROUNDED(dc - state->last_dc_values[index], 4); 461 | state->last_dc_values[index] += delta * 4; 462 | 463 | // Some versions of Sony's BS v3 decoder compute each DC coefficient as 464 | // ((last + delta * 4) & 0x3FF) instead of just (last + delta * 4). The 465 | // encoder can leverage this behavior to represent large coefficient 466 | // differences as smaller deltas that cause the decoder to overflow and 467 | // wrap around (e.g. -1 to encode -512 -> 511 as opposed to +1023). This 468 | // saves some space as larger DC values take up more bits. 469 | if (codec == BS_CODEC_V3DC) { 470 | if (delta < -0x80) 471 | delta += 0x100; 472 | else if (delta > +0x80) 473 | delta -= 0x100; 474 | } 475 | 476 | uint32_t outword = state->dc_huffman_map[(index << 9) | (delta & 0x1FF)]; 477 | 478 | if (!encode_bits(state, outword >> 24, outword & 0xFFFFFF)) 479 | return false; 480 | } 481 | 482 | for (int i = 1, zeroes = 0; i < 64; i++) { 483 | int ri = dct_zagzig_table[i]; 484 | int ac = DIVIDE_ROUNDED(block[ri], quant_table[ri]); 485 | 486 | ac = state->coeff_clamp_map[ac & 0xFFFF]; 487 | 488 | if (ac == 0) { 489 | zeroes++; 490 | } else { 491 | uint32_t outword = state->ac_huffman_map[(zeroes << 10) | (ac & 0x3FF)]; 492 | 493 | if (!encode_bits(state, outword >> 24, outword & 0xFFFFFF)) 494 | return false; 495 | 496 | zeroes = 0; 497 | state->uncomp_hwords_used++; 498 | } 499 | } 500 | 501 | // Store end of block 502 | if (!encode_bits(state, 2, 0x2)) 503 | return false; 504 | 505 | state->block_type++; 506 | state->block_type %= 6; 507 | state->uncomp_hwords_used += 2; 508 | //state->uncomp_hwords_used = (state->uncomp_hwords_used+0xF)&~0xF; 509 | return true; 510 | } 511 | 512 | bool init_mdec_encoder(mdec_encoder_t *encoder, bs_codec_t video_codec, int video_width, int video_height) { 513 | encoder->video_codec = video_codec; 514 | encoder->video_width = video_width; 515 | encoder->video_height = video_height; 516 | 517 | mdec_encoder_state_t *state = &(encoder->state); 518 | 519 | #if 0 520 | if (state->dct_context != NULL) 521 | return true; 522 | #endif 523 | 524 | state->dct_context = avcodec_dct_alloc(); 525 | state->ac_huffman_map = malloc(0x10000 * sizeof(uint32_t)); 526 | state->dc_huffman_map = malloc(0x200 * 3 * sizeof(uint32_t)); 527 | state->coeff_clamp_map = malloc(0x10000 * sizeof(int16_t)); 528 | 529 | if ( 530 | state->dct_context == NULL || 531 | state->ac_huffman_map == NULL || 532 | state->dc_huffman_map == NULL || 533 | state->coeff_clamp_map == NULL 534 | ) 535 | return false; 536 | 537 | int dct_block_count_x = (video_width + 15) / 16; 538 | int dct_block_count_y = (video_height + 15) / 16; 539 | int dct_block_size = dct_block_count_x * dct_block_count_y * sizeof(int16_t) * 8*8; 540 | 541 | for (int i = 0; i < 6; i++) { 542 | state->dct_block_lists[i] = malloc(dct_block_size); 543 | 544 | if (state->dct_block_lists[i] == NULL) 545 | return false; 546 | } 547 | 548 | avcodec_dct_init(state->dct_context); 549 | init_dct_data(state, video_codec); 550 | return true; 551 | } 552 | 553 | void destroy_mdec_encoder(mdec_encoder_t *encoder) { 554 | mdec_encoder_state_t *state = &(encoder->state); 555 | 556 | if (state->dct_context) { 557 | av_free(state->dct_context); 558 | state->dct_context = NULL; 559 | } 560 | if (state->ac_huffman_map) { 561 | free(state->ac_huffman_map); 562 | state->ac_huffman_map = NULL; 563 | } 564 | if (state->dc_huffman_map) { 565 | free(state->dc_huffman_map); 566 | state->dc_huffman_map = NULL; 567 | } 568 | if (state->coeff_clamp_map) { 569 | free(state->coeff_clamp_map); 570 | state->coeff_clamp_map = NULL; 571 | } 572 | for (int i = 0; i < 6; i++) { 573 | if (state->dct_block_lists[i] != NULL) { 574 | free(state->dct_block_lists[i]); 575 | state->dct_block_lists[i] = NULL; 576 | } 577 | } 578 | } 579 | 580 | void encode_frame_bs(mdec_encoder_t *encoder, const uint8_t *video_frame) { 581 | mdec_encoder_state_t *state = &(encoder->state); 582 | 583 | assert(state->dct_context); 584 | 585 | int pitch = encoder->video_width; 586 | #if 0 587 | int real_index = state->frame_index - 1; 588 | if (real_index > (video_frame_count - 1)) 589 | real_index = video_frame_count - 1; 590 | 591 | const uint8_t *y_plane = video_frames + encoder->video_width * encoder->video_height * 3/2 * real_index; 592 | #else 593 | const uint8_t *y_plane = video_frame; 594 | const uint8_t *c_plane = y_plane + (encoder->video_width * encoder->video_height); 595 | #endif 596 | 597 | int dct_block_count_x = (encoder->video_width + 15) / 16; 598 | int dct_block_count_y = (encoder->video_height + 15) / 16; 599 | 600 | // TODO: non-16x16-aligned videos 601 | assert((encoder->video_width % 16) == 0); 602 | assert((encoder->video_height % 16) == 0); 603 | 604 | // Rearrange the Y/C planes returned by libswscale into macroblocks. 605 | for (int fx = 0; fx < dct_block_count_x; fx++) { 606 | for (int fy = 0; fy < dct_block_count_y; fy++) { 607 | // Order: Cr Cb [Y1|Y2] 608 | // [Y3|Y4] 609 | int block_offs = 64 * (fy*dct_block_count_x + fx); 610 | int16_t *blocks[6] = { 611 | state->dct_block_lists[0] + block_offs, 612 | state->dct_block_lists[1] + block_offs, 613 | state->dct_block_lists[2] + block_offs, 614 | state->dct_block_lists[3] + block_offs, 615 | state->dct_block_lists[4] + block_offs, 616 | state->dct_block_lists[5] + block_offs 617 | }; 618 | 619 | for (int y = 0; y < 8; y++) { 620 | for (int x = 0; x < 8; x++) { 621 | int k = y*8 + x; 622 | int cx = fx*8 + x; 623 | int cy = fy*8 + y; 624 | int lx = fx*16 + x; 625 | int ly = fy*16 + y; 626 | 627 | blocks[0][k] = (int16_t)c_plane[pitch*cy + 2*cx + 0] - 128; 628 | blocks[1][k] = (int16_t)c_plane[pitch*cy + 2*cx + 1] - 128; 629 | blocks[2][k] = (int16_t)y_plane[pitch*(ly+0) + (lx+0)] - 128; 630 | blocks[3][k] = (int16_t)y_plane[pitch*(ly+0) + (lx+8)] - 128; 631 | blocks[4][k] = (int16_t)y_plane[pitch*(ly+8) + (lx+0)] - 128; 632 | blocks[5][k] = (int16_t)y_plane[pitch*(ly+8) + (lx+8)] - 128; 633 | } 634 | } 635 | 636 | for (int i = 0; i < 6; i++) 637 | #if 0 638 | transform_dct_block(blocks[i]); 639 | #else 640 | state->dct_context->fdct(blocks[i]); 641 | #endif 642 | } 643 | } 644 | 645 | uint32_t end_of_block; 646 | 647 | if (encoder->video_codec == BS_CODEC_V2) { 648 | end_of_block = 0x1FF; 649 | } else { 650 | end_of_block = 0x3FF; 651 | assert(state->dc_huffman_map); 652 | } 653 | 654 | assert(state->ac_huffman_map); 655 | assert(state->coeff_clamp_map); 656 | 657 | // Attempt encoding the frame at the maximum quality. If the result is too 658 | // large, increase the quantization scale and try again. 659 | // TODO: if a frame encoded at scale N is too large but the same frame 660 | // encoded at scale N+1 leaves a significant amount of free space, attempt 661 | // compressing at scale N but optimizing coefficients away until it fits 662 | // (like the old algorithm did) 663 | for ( 664 | state->quant_scale = 1; 665 | state->quant_scale < 64; 666 | state->quant_scale++ 667 | ) { 668 | int16_t quant_table[8*8]; 669 | 670 | // The DC coefficient's quantization scale is always 8. 671 | quant_table[0] = quant_dec[0] * 8; 672 | 673 | for (int i = 1; i < 64; i++) 674 | quant_table[i] = quant_dec[i] * state->quant_scale; 675 | 676 | memset(state->frame_output, 0, state->frame_max_size); 677 | 678 | state->block_type = 0; 679 | state->last_dc_values[INDEX_CR] = 0; 680 | state->last_dc_values[INDEX_CB] = 0; 681 | state->last_dc_values[INDEX_Y] = 0; 682 | 683 | state->bits_value = 0; 684 | state->bits_left = 16; 685 | state->uncomp_hwords_used = 0; 686 | state->bytes_used = 8; 687 | 688 | bool ok = true; 689 | for (int fx = 0; ok && (fx < dct_block_count_x); fx++) { 690 | for (int fy = 0; ok && (fy < dct_block_count_y); fy++) { 691 | // Order: Cr Cb [Y1|Y2] 692 | // [Y3|Y4] 693 | int block_offs = 64 * (fy*dct_block_count_x + fx); 694 | int16_t *blocks[6] = { 695 | state->dct_block_lists[0] + block_offs, 696 | state->dct_block_lists[1] + block_offs, 697 | state->dct_block_lists[2] + block_offs, 698 | state->dct_block_lists[3] + block_offs, 699 | state->dct_block_lists[4] + block_offs, 700 | state->dct_block_lists[5] + block_offs 701 | }; 702 | 703 | for(int i = 0; ok && (i < 6); i++) 704 | ok = encode_dct_block(state, encoder->video_codec, blocks[i], quant_table); 705 | } 706 | } 707 | 708 | if (!ok) 709 | continue; 710 | if (!encode_bits(state, 10, end_of_block)) 711 | continue; 712 | #if 0 713 | if (!encode_bits(state, 2, 0x2)) 714 | continue; 715 | #endif 716 | if (!flush_bits(state)) 717 | continue; 718 | 719 | state->uncomp_hwords_used += 2; 720 | state->quant_scale_sum += state->quant_scale; 721 | break; 722 | } 723 | assert(state->quant_scale < 64); 724 | 725 | // MDEC DMA is usually configured to transfer data in 32-word chunks. 726 | state->uncomp_hwords_used = (state->uncomp_hwords_used+0x3F)&~0x3F; 727 | 728 | // This is not the number of 32-byte blocks required for uncompressed data 729 | // as jPSXdec docs say, but rather the number of 32-*bit* words required. 730 | // The first 4 bytes of the frame header are in fact the MDEC command to 731 | // start decoding, which contains the data length in words in the lower 16 732 | // bits. 733 | state->blocks_used = (state->uncomp_hwords_used+1)>>1; 734 | 735 | // We need a multiple of 4 736 | state->bytes_used = (state->bytes_used+0x3)&~0x3; 737 | 738 | // MDEC command (size of decompressed MDEC data) 739 | state->frame_output[0x000] = (uint8_t)state->blocks_used; 740 | state->frame_output[0x001] = (uint8_t)(state->blocks_used>>8); 741 | state->frame_output[0x002] = (uint8_t)0x00; 742 | state->frame_output[0x003] = (uint8_t)0x38; 743 | 744 | // Quantization scale 745 | state->frame_output[0x004] = (uint8_t)state->quant_scale; 746 | state->frame_output[0x005] = (uint8_t)(state->quant_scale>>8); 747 | 748 | // BS version 749 | if (encoder->video_codec == BS_CODEC_V2) 750 | state->frame_output[0x006] = 0x02; 751 | else 752 | state->frame_output[0x006] = 0x03; 753 | 754 | state->frame_output[0x007] = 0x00; 755 | } 756 | 757 | int encode_sector_str( 758 | mdec_encoder_t *encoder, 759 | format_t format, 760 | uint16_t str_video_id, 761 | const uint8_t *video_frames, 762 | uint8_t *output 763 | ) { 764 | mdec_encoder_state_t *state = &(encoder->state); 765 | int frame_size = encoder->video_width * encoder->video_height * 2; 766 | int frames_used = 0; 767 | 768 | while (state->frame_data_offset >= state->frame_max_size) { 769 | state->frame_index++; 770 | // TODO: work out an optimal block count for this 771 | // TODO: calculate this all based on FPS 772 | state->frame_block_overflow_num += state->frame_block_base_overflow; 773 | state->frame_max_size = state->frame_block_overflow_num / state->frame_block_overflow_den * 2016; 774 | state->frame_block_overflow_num %= state->frame_block_overflow_den; 775 | state->frame_data_offset = 0; 776 | 777 | encode_frame_bs(encoder, video_frames); 778 | video_frames += frame_size; 779 | frames_used++; 780 | } 781 | 782 | uint8_t header[32]; 783 | memset(header, 0, sizeof(header)); 784 | 785 | // STR version 786 | header[0x000] = 0x60; 787 | header[0x001] = 0x01; 788 | 789 | // Chunk type 790 | header[0x002] = (uint8_t)str_video_id; 791 | header[0x003] = (uint8_t)(str_video_id >> 8); 792 | 793 | // Muxed chunk index/count 794 | int chunk_index = state->frame_data_offset / 2016; 795 | int chunk_count = state->frame_max_size / 2016; 796 | header[0x004] = (uint8_t)chunk_index; 797 | header[0x005] = (uint8_t)(chunk_index >> 8); 798 | header[0x006] = (uint8_t)chunk_count; 799 | header[0x007] = (uint8_t)(chunk_count >> 8); 800 | 801 | // Frame index 802 | header[0x008] = (uint8_t)state->frame_index; 803 | header[0x009] = (uint8_t)(state->frame_index >> 8); 804 | header[0x00A] = (uint8_t)(state->frame_index >> 16); 805 | header[0x00B] = (uint8_t)(state->frame_index >> 24); 806 | 807 | // Demuxed bytes used as a multiple of 4 808 | header[0x00C] = (uint8_t)state->bytes_used; 809 | header[0x00D] = (uint8_t)(state->bytes_used >> 8); 810 | header[0x00E] = (uint8_t)(state->bytes_used >> 16); 811 | header[0x00F] = (uint8_t)(state->bytes_used >> 24); 812 | 813 | // Video frame size 814 | header[0x010] = (uint8_t)encoder->video_width; 815 | header[0x011] = (uint8_t)(encoder->video_width >> 8); 816 | header[0x012] = (uint8_t)encoder->video_height; 817 | header[0x013] = (uint8_t)(encoder->video_height >> 8); 818 | 819 | // Copy of BS header 820 | memcpy(header + 0x014, state->frame_output, 8); 821 | 822 | int offset; 823 | 824 | if (format == FORMAT_STR) 825 | offset = 0x008; 826 | else if (format == FORMAT_STRCD) 827 | offset = 0x018; 828 | else 829 | offset = 0x000; 830 | 831 | memcpy(output + offset, header, sizeof(header)); 832 | memcpy(output + offset + 0x020, state->frame_output + state->frame_data_offset, 2016); 833 | 834 | state->frame_data_offset += 2016; 835 | return frames_used; 836 | } 837 | -------------------------------------------------------------------------------- /psxavenc/mdec.h: -------------------------------------------------------------------------------- 1 | /* 2 | psxavenc: MDEC video + SPU/XA-ADPCM audio encoder frontend 3 | 4 | Copyright (c) 2019, 2020 Adrian "asie" Siekierka 5 | Copyright (c) 2019 Ben "GreaseMonkey" Russell 6 | Copyright (c) 2023, 2025 spicyjpeg 7 | 8 | This software is provided 'as-is', without any express or implied 9 | warranty. In no event will the authors be held liable for any damages 10 | arising from the use of this software. 11 | 12 | Permission is granted to anyone to use this software for any purpose, 13 | including commercial applications, and to alter it and redistribute it 14 | freely, subject to the following restrictions: 15 | 16 | 1. The origin of this software must not be misrepresented; you must not 17 | claim that you wrote the original software. If you use this software 18 | in a product, an acknowledgment in the product documentation would be 19 | appreciated but is not required. 20 | 2. Altered source versions must be plainly marked as such, and must not be 21 | misrepresented as being the original software. 22 | 3. This notice may not be removed or altered from any source distribution. 23 | */ 24 | 25 | #pragma once 26 | 27 | #include 28 | #include 29 | #include 30 | #include "args.h" 31 | 32 | typedef struct { 33 | int frame_index; 34 | int frame_data_offset; 35 | int frame_max_size; 36 | int frame_block_base_overflow; 37 | int frame_block_overflow_num; 38 | int frame_block_overflow_den; 39 | int block_type; 40 | int16_t last_dc_values[3]; 41 | uint16_t bits_value; 42 | int bits_left; 43 | uint8_t *frame_output; 44 | int bytes_used; 45 | int blocks_used; 46 | int uncomp_hwords_used; 47 | int quant_scale; 48 | int quant_scale_sum; 49 | 50 | AVDCT *dct_context; 51 | uint32_t *ac_huffman_map; 52 | uint32_t *dc_huffman_map; 53 | int16_t *coeff_clamp_map; 54 | int16_t *dct_block_lists[6]; 55 | } mdec_encoder_state_t; 56 | 57 | typedef struct { 58 | bs_codec_t video_codec; 59 | int video_width; 60 | int video_height; 61 | 62 | mdec_encoder_state_t state; 63 | } mdec_encoder_t; 64 | 65 | bool init_mdec_encoder(mdec_encoder_t *encoder, bs_codec_t video_codec, int video_width, int video_height); 66 | void destroy_mdec_encoder(mdec_encoder_t *encoder); 67 | void encode_frame_bs(mdec_encoder_t *encoder, const uint8_t *video_frame); 68 | int encode_sector_str( 69 | mdec_encoder_t *encoder, 70 | format_t format, 71 | uint16_t str_video_id, 72 | const uint8_t *video_frames, 73 | uint8_t *output 74 | ); 75 | --------------------------------------------------------------------------------