diff --git a/dsp/audio/cosmoaudio/cosmoaudio.c b/dsp/audio/cosmoaudio/cosmoaudio.c index d53d38809..c587544d8 100644 --- a/dsp/audio/cosmoaudio/cosmoaudio.c +++ b/dsp/audio/cosmoaudio/cosmoaudio.c @@ -178,6 +178,7 @@ COSMOAUDIO_ABI int cosmoaudio_open(struct CosmoAudio** cap, int sampleRate, COSMOAUDIO_ABI int cosmoaudio_close(struct CosmoAudio* ca) { ma_device_uninit(&ca->device); ma_pcm_rb_uninit(&ca->output); + ma_pcm_rb_uninit(&ca->input); free(ca); return COSMOAUDIO_SUCCESS; } @@ -221,15 +222,7 @@ COSMOAUDIO_ABI int cosmoaudio_write(struct CosmoAudio* ca, const float* data, */ COSMOAUDIO_ABI int cosmoaudio_read(struct CosmoAudio* ca, float* data, int frames) { - int read; - for (int i = 0; i < frames; i += read) { - int remaining = frames - i; - read = read_ring_buffer(&ca->input, data + i * ca->channels, remaining, - ca->channels); - if (read < 0) - return read; - } - return frames; + return read_ring_buffer(&ca->input, data, frames, ca->channels); } #ifdef _MSC_VER diff --git a/dsp/audio/cosmoaudio/miniaudio.c b/dsp/audio/cosmoaudio/miniaudio.c deleted file mode 100644 index e69de29bb..000000000 diff --git a/dsp/mpeg/.clang-format b/dsp/mpeg/.clang-format new file mode 100644 index 000000000..47a38a93f --- /dev/null +++ b/dsp/mpeg/.clang-format @@ -0,0 +1,2 @@ +DisableFormat: true +SortIncludes: Never diff --git a/dsp/mpeg/BUILD.mk b/dsp/mpeg/BUILD.mk index a16089eaa..be219ca23 100644 --- a/dsp/mpeg/BUILD.mk +++ b/dsp/mpeg/BUILD.mk @@ -25,18 +25,13 @@ DSP_MPEG_A_CHECKS = \ DSP_MPEG_A_DIRECTDEPS = \ LIBC_CALLS \ - LIBC_FMT \ LIBC_INTRIN \ - LIBC_LOG \ - LIBC_LOG \ LIBC_MEM \ LIBC_NEXGEN32E \ - LIBC_RUNTIME \ LIBC_STDIO \ LIBC_STR \ - LIBC_SYSV \ LIBC_TINYMATH \ - THIRD_PARTY_COMPILER_RT + THIRD_PARTY_COMPILER_RT \ DSP_MPEG_A_DEPS := \ $(call uniq,$(foreach x,$(DSP_MPEG_A_DIRECTDEPS),$($(x)))) @@ -49,9 +44,10 @@ $(DSP_MPEG_A).pkg: \ $(DSP_MPEG_A_OBJS) \ $(foreach x,$(DSP_MPEG_A_DIRECTDEPS),$($(x)_A).pkg) -o/$(MODE)/dsp/mpeg/clamp4int256-k8.o: private \ +o/$(MODE)/dsp/mpeg/pl_mpeg.o: private \ CFLAGS += \ - -Os + -ffunction-sections \ + -fdata-sections DSP_MPEG_LIBS = $(foreach x,$(DSP_MPEG_ARTIFACTS),$($(x))) DSP_MPEG_SRCS = $(foreach x,$(DSP_MPEG_ARTIFACTS),$($(x)_SRCS)) diff --git a/dsp/mpeg/README.cosmo b/dsp/mpeg/README.cosmo new file mode 100644 index 000000000..1c1991af4 --- /dev/null +++ b/dsp/mpeg/README.cosmo @@ -0,0 +1,17 @@ +DESCRIPTION + + pl_mpeg lets you decode .mpg files + +ORIGIN + + https://github.com/phoboslab/pl_mpeg/ + 9e40dd6536269d788728e32c39bfacf2ab7a0866 + +LICENSE + + MIT + +LOCAL CHANGES + + - Added API for extracting pixel aspect ratio + https://github.com/phoboslab/pl_mpeg/pull/42 diff --git a/dsp/mpeg/README.md b/dsp/mpeg/README.md new file mode 100755 index 000000000..4892e7026 --- /dev/null +++ b/dsp/mpeg/README.md @@ -0,0 +1,68 @@ +# PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer + +Single-file MIT licensed library for C/C++ + +See [pl_mpeg.h](https://github.com/phoboslab/pl_mpeg/blob/master/pl_mpeg.h) for +the documentation. + + +## Why? + +This is meant as a simple way to get video playback into your app or game. Other +solutions, such as ffmpeg require huge libraries and a lot of glue code. + +MPEG1 is an old and inefficient codec, but it's still good enough for many use +cases. All patents related to MPEG1 and MP2 have expired, so it's completely +free now. + +This library does not make use of any SIMD instructions, but because of +the relative simplicity of the codec it still manages to decode 4k60fps video +on a single CPU core (on my i7-6700k at least). + +## Compilation on Linux + +Use a GCC invocation like the following to build the example `pl_mpeg_player` +program: + +```shell +gcc -o pl_mpeg_player pl_mpeg_player.c $(pkg-config --cflags --libs sdl2 glew) +``` + +## Example Usage + +- [pl_mpeg_extract_frames.c](https://github.com/phoboslab/pl_mpeg/blob/master/pl_mpeg_extract_frames.c) +extracts all frames from a video and saves them as PNG. + - [pl_mpeg_player.c](https://github.com/phoboslab/pl_mpeg/blob/master/pl_mpeg_player.c) +implements a video player using SDL2 and OpenGL for rendering. + + + +## Encoding for PL_MPEG + +Most [MPEG-PS](https://en.wikipedia.org/wiki/MPEG_program_stream) (`.mpg`) files +containing MPEG1 Video ("mpeg1") and MPEG1 Audio Layer II ("mp2") streams should +work with PL_MPEG. Note that `.mpg` files can also contain MPEG2 Video, which is +not supported by this library. + +You can encode video in a suitable format using ffmpeg: + +``` +ffmpeg -i input.mp4 -c:v mpeg1video -q:v 0 -c:a mp2 -format mpeg output.mpg +``` + +`-q:v` sets a fixed video quality with a variable bitrate, where `0` is the +highest. You may use `-b:v` to set a fixed bitrate instead; e.g. +`-b:v 2000k` for 2000 kbit/s. Please refer to the +[ffmpeg documentation](http://ffmpeg.org/ffmpeg.html#Options) for more details. + +If you just want to quickly test the library, try this file: + +https://phoboslab.org/files/bjork-all-is-full-of-love.mpg + + +## Limitations + +- no error reporting. PL_MPEG will silently ignore any invalid data. +- the pts (presentation time stamp) for packets in the MPEG-PS container is +ignored. This may cause sync issues with some files. +- bugs, probably. diff --git a/dsp/mpeg/README.txt b/dsp/mpeg/README.txt deleted file mode 100644 index b8263c03c..000000000 --- a/dsp/mpeg/README.txt +++ /dev/null @@ -1,92 +0,0 @@ -PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer -Dominic Szablewski - https://phoboslab.org - --- Synopsis - -// This function gets called for each decoded video frame -void my_video_callback(plm_t *plm, plm_frame_t *frame, void *user) { - // Do something with frame->y.data, frame->cr.data, frame->cb.data -} - -// This function gets called for each decoded audio frame -void my_audio_callback(plm_t *plm, plm_samples_t *frame, void *user) { - // Do something with samples->interleaved -} - -// Load a .mpg (MPEG Program Stream) file -plm_t *plm = plm_create_with_filename("some-file.mpg"); - -// Install the video & audio decode callbacks -plm_set_video_decode_callback(plm, my_video_callback, my_data); -plm_set_audio_decode_callback(plm, my_audio_callback, my_data); - - -// Decode -do { - plm_decode(plm, time_since_last_call); -} while (!plm_has_ended(plm)); - -// All done -plm_destroy(plm); - - - --- Documentation - -This library provides several interfaces to load, demux and decode MPEG video -and audio data. A high-level API combines the demuxer, video & audio decoders -in an easy to use wrapper. - -Lower-level APIs for accessing the demuxer, video decoder and audio decoder, -as well as providing different data sources are also available. - -Interfaces are written in an object orientet style, meaning you create object -instances via various different constructor functions (plm_*create()), -do some work on them and later dispose them via plm_*destroy(). - -plm_* -- the high-level interface, combining demuxer and decoders -plm_buffer_* -- the data source used by all interfaces -plm_demux_* -- the MPEG-PS demuxer -plm_video_* -- the MPEG1 Video ("mpeg1") decoder -plm_audio_* -- the MPEG1 Audio Layer II ("mp2") decoder - - -This library uses malloc(), realloc() and free() to manage memory. Typically -all allocation happens up-front when creating the interface. However, the -default buffer size may be too small for certain inputs. In these cases plmpeg -will realloc() the buffer with a larger size whenever needed. You can configure -the default buffer size by defining PLM_BUFFER_DEFAULT_SIZE *before* -including this library. - -With the high-level interface you have two options to decode video & audio: - -1) Use plm_decode() and just hand over the delta time since the last call. -It will decode everything needed and call your callbacks (specified through -plm_set_{video|audio}_decode_callback()) any number of times. - -2) Use plm_decode_video() and plm_decode_audio() to decode exactly one -frame of video or audio data at a time. How you handle the synchronization of -both streams is up to you. - -If you only want to decode video *or* audio through these functions, you should -disable the other stream (plm_set_{video|audio}_enabled(false)) - - -Video data is decoded into a struct with all 3 planes (Y, Cr, Cb) stored in -separate buffers. You can either convert this to RGB on the CPU (slow) via the -plm_frame_to_rgb() function or do it on the GPU with the following matrix: - -mat4 rec601 = mat4( - 1.16438, 0.00000, 1.59603, -0.87079, - 1.16438, -0.39176, -0.81297, 0.52959, - 1.16438, 2.01723, 0.00000, -1.08139, - 0, 0, 0, 1 -); -gl_FragColor = vec4(y, cb, cr, 1.0) * rec601; - -Audio data is decoded into a struct with either one single float array with the -samples for the left and right channel interleaved, or if the -PLM_AUDIO_SEPARATE_CHANNELS is defined *before* including this library, into -two separate float arrays - one for each channel. - -See below for detailed the API documentation. diff --git a/dsp/mpeg/blockset.h b/dsp/mpeg/blockset.h deleted file mode 100644 index 14d0f36b2..000000000 --- a/dsp/mpeg/blockset.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef COSMOPOLITAN_DSP_MPEG_BLOCKSET_H_ -#define COSMOPOLITAN_DSP_MPEG_BLOCKSET_H_ - -#define PLM_BLOCK_SET(DEST, DEST_INDEX, DEST_WIDTH, SOURCE_INDEX, \ - SOURCE_WIDTH, BLOCK_SIZE, OP) \ - do { \ - int dest_scan = DEST_WIDTH - BLOCK_SIZE; \ - int source_scan = SOURCE_WIDTH - BLOCK_SIZE; \ - for (int y = 0; y < BLOCK_SIZE; y++) { \ - for (int x = 0; x < BLOCK_SIZE; x++) { \ - DEST[DEST_INDEX] = OP; \ - SOURCE_INDEX++; \ - DEST_INDEX++; \ - } \ - SOURCE_INDEX += source_scan; \ - DEST_INDEX += dest_scan; \ - } \ - } while (false) - -#endif /* COSMOPOLITAN_DSP_MPEG_BLOCKSET_H_ */ diff --git a/dsp/mpeg/buffer.c b/dsp/mpeg/buffer.c deleted file mode 100644 index f0cb0de38..000000000 --- a/dsp/mpeg/buffer.c +++ /dev/null @@ -1,153 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:4;tab-width:4;coding:utf-8 -*-│ -│ vi: set noet ft=c ts=4 sw=4 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer │ -│ Dominic Szablewski - https://phoboslab.org │ -│ │ -│ The MIT License(MIT) │ -│ Copyright(c) 2019 Dominic Szablewski │ -│ │ -│ Permission is hereby granted, free of charge, to any person obtaining │ -│ a copy of this software and associated documentation files(the │ -│ "Software"), to deal in the Software without restriction, including │ -│ without limitation the rights to use, copy, modify, merge, publish, │ -│ distribute, sublicense, and / or sell copies of the Software, and to │ -│ permit persons to whom the Software is furnished to do so, subject to │ -│ the following conditions: │ -│ │ -│ The above copyright notice and this permission notice shall be │ -│ included in all copies or substantial portions of the Software. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, │ -│ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF │ -│ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND │ -│ NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE │ -│ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN │ -│ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN │ -│ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE │ -│ SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "dsp/mpeg/buffer.h" -#include "dsp/mpeg/mpeg.h" -#include "libc/calls/calls.h" -#include "libc/log/check.h" -#include "libc/mem/mem.h" -#include "libc/stdio/stdio.h" -#include "libc/str/str.h" -#include "libc/sysv/consts/madv.h" -__static_yoink("pl_mpeg_notice"); - -/* clang-format off */ -// ----------------------------------------------------------------------------- -// plm_buffer implementation - -plm_buffer_t *plm_buffer_create_with_filename(const char *filename) { - FILE *fh = fopen(filename, "rb"); - if (!fh) { - return NULL; - } - fadvise(fileno(fh), 0, 0, MADV_SEQUENTIAL); - return plm_buffer_create_with_file(fh, true); -} - -plm_buffer_t *plm_buffer_create_with_file(FILE *fh, int close_when_done) { - plm_buffer_t *b; - b = plm_buffer_create_with_capacity(PLM_BUFFER_DEFAULT_SIZE); - b->fh = fh; - b->close_when_done = close_when_done; - b->mode = PLM_BUFFER_MODE_FILE; - plm_buffer_set_load_callback(b, plm_buffer_load_file_callback, NULL); - return b; -} - -plm_buffer_t *plm_buffer_create_with_memory(unsigned char *bytes, size_t length, int free_when_done) { - plm_buffer_t *b; - b = memalign(_Alignof(plm_buffer_t), sizeof(plm_buffer_t)); - memset(b, 0, sizeof(plm_buffer_t)); - b->capacity = length; - b->length = length; - b->free_when_done = free_when_done; - b->bytes = bytes; - b->mode = PLM_BUFFER_MODE_FIXED_MEM; - return b; -} - -plm_buffer_t * plm_buffer_create_with_capacity(size_t capacity) { - plm_buffer_t *b; - b = memalign(_Alignof(plm_buffer_t), sizeof(plm_buffer_t)); - memset(b, 0, sizeof(plm_buffer_t)); - b->capacity = capacity; - b->free_when_done = true; - b->bytes = (unsigned char *)malloc(capacity); - b->mode = PLM_BUFFER_MODE_DYNAMIC_MEM; - return b; -} - -void plm_buffer_destroy(plm_buffer_t *self) { - if (self->fh && self->close_when_done) { - fclose(self->fh); - } - if (self->free_when_done) { - free(self->bytes); - } - free(self); -} - -size_t plm_buffer_write(plm_buffer_t *self, unsigned char *bytes, size_t length) { - if (self->mode == PLM_BUFFER_MODE_FIXED_MEM) { - return 0; - } - // This should be a ring buffer, but instead it just shifts all unread data - // to the beginning of the buffer and appends new data at the end. Seems - // to be good enough. - plm_buffer_discard_read_bytes(self); - // Do we have to resize to fit the new data? - size_t bytes_available = self->capacity - self->length; - if (bytes_available < length) { - size_t new_size = self->capacity; - do { - new_size *= 2; - } while (new_size - self->length < length); - self->bytes = (unsigned char *)realloc(self->bytes, new_size); - self->capacity = new_size; - } - memcpy(self->bytes + self->length, bytes, length); - self->length += length; - return length; -} - -void plm_buffer_set_load_callback(plm_buffer_t *self, plm_buffer_load_callback fp, void *user) { - self->load_callback = fp; - self->load_callback_user_data = user; -} - -void plm_buffer_rewind(plm_buffer_t *self) { - if (self->fh) { - fseek(self->fh, 0, SEEK_SET); - self->length = 0; - } - if (self->mode != PLM_BUFFER_MODE_FIXED_MEM) { - self->length = 0; - } - self->bit_index = 0; -} - -void plm_buffer_discard_read_bytes(plm_buffer_t *self) { - size_t byte_pos = self->bit_index >> 3; - if (byte_pos == self->length) { - self->bit_index = 0; - self->length = 0; - } - else if (byte_pos > 0) { - memmove(self->bytes, self->bytes + byte_pos, self->length - byte_pos); - self->bit_index -= byte_pos << 3; - self->length -= byte_pos; - } -} - -void plm_buffer_load_file_callback(plm_buffer_t *self, void *user) { - plm_buffer_discard_read_bytes(self); - unsigned bytes_available = self->capacity - self->length; - unsigned bytes_read = fread(self->bytes + self->length, 1, bytes_available, self->fh); - self->length += bytes_read; -} diff --git a/dsp/mpeg/buffer.h b/dsp/mpeg/buffer.h deleted file mode 100644 index e841535fb..000000000 --- a/dsp/mpeg/buffer.h +++ /dev/null @@ -1,160 +0,0 @@ -#ifndef COSMOPOLITAN_DSP_MPEG_BUFFER_H_ -#define COSMOPOLITAN_DSP_MPEG_BUFFER_H_ -#include "dsp/mpeg/mpeg.h" -COSMOPOLITAN_C_START_ - -enum plm_buffer_mode { - PLM_BUFFER_MODE_FILE, - PLM_BUFFER_MODE_FIXED_MEM, - PLM_BUFFER_MODE_DYNAMIC_MEM -}; - -typedef struct plm_buffer_t { - unsigned bit_index; - unsigned capacity; - unsigned length; - int free_when_done; - int close_when_done; - FILE *fh; - plm_buffer_load_callback load_callback; - void *load_callback_user_data; - unsigned char *bytes; - enum plm_buffer_mode mode; -} plm_buffer_t; - -typedef struct { - int16_t index; - int16_t value; -} plm_vlc_t; - -typedef struct { - int16_t index; - uint16_t value; -} plm_vlc_uint_t; - -/* bool plm_buffer_has(plm_buffer_t *, size_t); */ -/* int plm_buffer_read(plm_buffer_t *, int); */ -/* void plm_buffer_align(plm_buffer_t *); */ -/* void plm_buffer_skip(plm_buffer_t *, size_t); */ -/* int plm_buffer_skip_bytes(plm_buffer_t *, unsigned char); */ -/* int plm_buffer_next_start_code(plm_buffer_t *); */ -/* int plm_buffer_find_start_code(plm_buffer_t *, int); */ -/* int plm_buffer_no_start_code(plm_buffer_t *); */ -/* int16_t plm_buffer_read_vlc(plm_buffer_t *, const plm_vlc_t *); */ -/* uint16_t plm_buffer_read_vlc_uint(plm_buffer_t *, const plm_vlc_uint_t *); */ - -void plm_buffer_discard_read_bytes(plm_buffer_t *); -relegated void plm_buffer_load_file_callback(plm_buffer_t *, void *); - -forceinline bool plm_buffer_has(plm_buffer_t *b, size_t bits) { - unsigned have; - have = b->length; - have <<= 3; - have -= b->bit_index; - if (bits <= have) { - return true; - } else { - if (b->load_callback) { - b->load_callback(b, b->load_callback_user_data); - return ((b->length << 3) - b->bit_index) >= bits; - } else { - return false; - } - } -} - -forceinline int plm_buffer_read(plm_buffer_t *self, int count) { - if (!plm_buffer_has(self, count)) - return 0; - int value = 0; - while (count) { - int current_byte = self->bytes[self->bit_index >> 3]; - int remaining = 8 - (self->bit_index & 7); // Remaining bits in byte - int read = remaining < count ? remaining : count; // Bits in self run - int shift = remaining - read; - int mask = (0xff >> (8 - read)); - value = (value << read) | ((current_byte & (mask << shift)) >> shift); - self->bit_index += read; - count -= read; - } - return value; -} - -forceinline void plm_buffer_align(plm_buffer_t *self) { - self->bit_index = ((self->bit_index + 7) >> 3) << 3; -} - -forceinline void plm_buffer_skip(plm_buffer_t *self, size_t count) { - if (plm_buffer_has(self, count)) { - self->bit_index += count; - } -} - -forceinline int plm_buffer_skip_bytes(plm_buffer_t *self, unsigned char v) { - unsigned skipped; - plm_buffer_align(self); - skipped = 0; - while (plm_buffer_has(self, 8)) { - if (v == self->bytes[self->bit_index >> 3]) { - self->bit_index += 8; - ++skipped; - } else { - break; - } - } - return skipped; -} - -forceinline int plm_buffer_next_start_code(plm_buffer_t *self) { - plm_buffer_align(self); - while (plm_buffer_has(self, (5 << 3))) { - size_t byte_index = (self->bit_index) >> 3; - if (self->bytes[byte_index] == 0x00 && - self->bytes[byte_index + 1] == 0x00 && - self->bytes[byte_index + 2] == 0x01) { - self->bit_index = (byte_index + 4) << 3; - return self->bytes[byte_index + 3]; - } - self->bit_index += 8; - } - self->bit_index = (self->length << 3); - return -1; -} - -forceinline int plm_buffer_find_start_code(plm_buffer_t *self, int code) { - int current = 0; - while (true) { - current = plm_buffer_next_start_code(self); - if (current == code || current == -1) { - return current; - } - } - return -1; -} - -forceinline int plm_buffer_no_start_code(plm_buffer_t *self) { - if (!plm_buffer_has(self, (5 << 3))) { - return false; - } - size_t byte_index = ((self->bit_index + 7) >> 3); - return !(self->bytes[byte_index] == 0x00 && - self->bytes[byte_index + 1] == 0x00 && - self->bytes[byte_index + 2] == 0x01); -} - -forceinline int16_t plm_buffer_read_vlc(plm_buffer_t *self, - const plm_vlc_t *table) { - plm_vlc_t state = {0, 0}; - do { - state = table[state.index + plm_buffer_read(self, 1)]; - } while (state.index > 0); - return state.value; -} - -forceinline uint16_t plm_buffer_read_vlc_uint(plm_buffer_t *self, - const plm_vlc_uint_t *table) { - return (uint16_t)plm_buffer_read_vlc(self, (plm_vlc_t *)table); -} - -COSMOPOLITAN_C_END_ -#endif /* COSMOPOLITAN_DSP_MPEG_BUFFER_H_ */ diff --git a/dsp/mpeg/clamp4int256-core.S b/dsp/mpeg/clamp4int256-core.S deleted file mode 100644 index db3b97c2e..000000000 --- a/dsp/mpeg/clamp4int256-core.S +++ /dev/null @@ -1,30 +0,0 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│ vi: set noet ft=asm ts=8 sw=8 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/macros.h" - -clamp4int256$core: - .leafprologue - pxor %xmm1,%xmm1 - pmaxsd %xmm1,%xmm0 - pminsd 0f(%rip),%xmm0 - .leafepilogue - .endfn clamp4int256$core,globl - - .rodata.cst16 -0: .long 255,255,255,255 diff --git a/dsp/mpeg/demux.c b/dsp/mpeg/demux.c deleted file mode 100644 index 66eff844a..000000000 --- a/dsp/mpeg/demux.c +++ /dev/null @@ -1,203 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:4;tab-width:4;coding:utf-8 -*-│ -│ vi: set noet ft=c ts=4 sw=4 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer │ -│ Dominic Szablewski - https://phoboslab.org │ -│ │ -│ The MIT License(MIT) │ -│ Copyright(c) 2019 Dominic Szablewski │ -│ │ -│ Permission is hereby granted, free of charge, to any person obtaining │ -│ a copy of this software and associated documentation files(the │ -│ "Software"), to deal in the Software without restriction, including │ -│ without limitation the rights to use, copy, modify, merge, publish, │ -│ distribute, sublicense, and / or sell copies of the Software, and to │ -│ permit persons to whom the Software is furnished to do so, subject to │ -│ the following conditions: │ -│ │ -│ The above copyright notice and this permission notice shall be │ -│ included in all copies or substantial portions of the Software. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, │ -│ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF │ -│ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND │ -│ NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE │ -│ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN │ -│ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN │ -│ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE │ -│ SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "dsp/mpeg/demux.h" -#include "dsp/mpeg/buffer.h" -#include "dsp/mpeg/mpeg.h" -#include "libc/mem/mem.h" -#include "libc/str/str.h" -__static_yoink("pl_mpeg_notice"); - -/* clang-format off */ -// ---------------------------------------------------------------------------- -// plm_demux implementation - -plm_demux_t *plm_demux_create(plm_buffer_t *buffer, int destroy_when_done) { - plm_demux_t *self = (plm_demux_t *)malloc(sizeof(plm_demux_t)); - memset(self, 0, sizeof(plm_demux_t)); - - self->buffer = buffer; - self->destroy_buffer_when_done = destroy_when_done; - - if (plm_buffer_find_start_code(self->buffer, START_PACK) != -1) { - plm_demux_decode_pack_header(self); - } - if (plm_buffer_find_start_code(self->buffer, START_SYSTEM) != -1) { - plm_demux_decode_system_header(self); - } - return self; -} - -void plm_demux_destroy(plm_demux_t *self) { - if (self->destroy_buffer_when_done) { - plm_buffer_destroy(self->buffer); - } - free(self); -} - -int plm_demux_get_num_video_streams(plm_demux_t *self) { - return self->num_video_streams; -} - -int plm_demux_get_num_audio_streams(plm_demux_t *self) { - return self->num_audio_streams; -} - -void plm_demux_rewind(plm_demux_t *self) { - plm_buffer_rewind(self->buffer); -} - -plm_packet_t *plm_demux_decode(plm_demux_t *self) { - if (self->current_packet.length) { - size_t bits_till_next_packet = self->current_packet.length << 3; - if (!plm_buffer_has(self->buffer, bits_till_next_packet)) { - return NULL; - } - plm_buffer_skip(self->buffer, bits_till_next_packet); - self->current_packet.length = 0; - } - - if (!self->has_pack_header) { - if (plm_buffer_find_start_code(self->buffer, START_PACK) != -1) { - plm_demux_decode_pack_header(self); - } - else { - return NULL; - } - } - - if (!self->has_system_header) { - if (plm_buffer_find_start_code(self->buffer, START_SYSTEM) != -1) { - plm_demux_decode_system_header(self); - } - else { - return NULL; - } - } - - // pending packet just waiting for data? - if (self->next_packet.length) { - return plm_demux_get_packet(self); - } - - int code; - do { - code = plm_buffer_next_start_code(self->buffer); - if ( - code == PLM_DEMUX_PACKET_VIDEO_1 || - code == PLM_DEMUX_PACKET_PRIVATE || - (code >= PLM_DEMUX_PACKET_AUDIO_1 && code <= PLM_DEMUX_PACKET_AUDIO_4) - ) { - return plm_demux_decode_packet(self, code); - } - } while (code != -1); - - return NULL; -} - -double plm_demux_read_time(plm_demux_t *self) { - int64_t clock = plm_buffer_read(self->buffer, 3) << 30; - plm_buffer_skip(self->buffer, 1); - clock |= plm_buffer_read(self->buffer, 15) << 15; - plm_buffer_skip(self->buffer, 1); - clock |= plm_buffer_read(self->buffer, 15); - plm_buffer_skip(self->buffer, 1); - return (double)clock / 90000.0; -} - -void plm_demux_decode_pack_header(plm_demux_t *self) { - if (plm_buffer_read(self->buffer, 4) != 0x02) { - return; // invalid - } - self->system_clock_ref = plm_demux_read_time(self); - plm_buffer_skip(self->buffer, 1); - plm_buffer_skip(self->buffer, 22); // mux_rate * 50 - plm_buffer_skip(self->buffer, 1); - - self->has_pack_header = true; -} - -void plm_demux_decode_system_header(plm_demux_t *self) { - plm_buffer_skip(self->buffer, 16); // header_length - plm_buffer_skip(self->buffer, 24); // rate bound - self->num_audio_streams = plm_buffer_read(self->buffer, 6); - plm_buffer_skip(self->buffer, 5); // misc flags - self->num_video_streams = plm_buffer_read(self->buffer, 5); - - self->has_system_header = true; -} - -plm_packet_t *plm_demux_decode_packet(plm_demux_t *self, int start_code) { - if (!plm_buffer_has(self->buffer, 8 << 3)) { - return NULL; - } - - self->next_packet.type = start_code; - self->next_packet.length = plm_buffer_read(self->buffer, 16); - self->next_packet.length -= plm_buffer_skip_bytes(self->buffer, 0xff); // stuffing - - // skip P-STD - if (plm_buffer_read(self->buffer, 2) == 0x01) { - plm_buffer_skip(self->buffer, 16); - self->next_packet.length -= 2; - } - - int pts_dts_marker = plm_buffer_read(self->buffer, 2); - if (pts_dts_marker == 0x03) { - self->next_packet.pts = plm_demux_read_time(self); - plm_buffer_skip(self->buffer, 40); // skip dts - self->next_packet.length -= 10; - } - else if (pts_dts_marker == 0x02) { - self->next_packet.pts = plm_demux_read_time(self); - self->next_packet.length -= 5; - } - else if (pts_dts_marker == 0x00) { - self->next_packet.pts = 0; - plm_buffer_skip(self->buffer, 4); - self->next_packet.length -= 1; - } - else { - return NULL; // invalid - } - - return plm_demux_get_packet(self); -} - -plm_packet_t *plm_demux_get_packet(plm_demux_t *self) { - if (!plm_buffer_has(self->buffer, self->next_packet.length << 3)) { - return NULL; - } - self->current_packet.data = self->buffer->bytes + (self->buffer->bit_index >> 3); - self->current_packet.length = self->next_packet.length; - self->current_packet.type = self->next_packet.type; - self->current_packet.pts = self->next_packet.pts; - self->next_packet.length = 0; - return &self->current_packet; -} diff --git a/dsp/mpeg/demux.h b/dsp/mpeg/demux.h deleted file mode 100644 index f36de4d3a..000000000 --- a/dsp/mpeg/demux.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef COSMOPOLITAN_DSP_MPEG_DEMUX_H_ -#define COSMOPOLITAN_DSP_MPEG_DEMUX_H_ -#include "dsp/mpeg/mpeg.h" -COSMOPOLITAN_C_START_ - -#define START_PACK 0xBA -#define START_END 0xB9 -#define START_SYSTEM 0xBB - -typedef struct plm_demux_t { - plm_buffer_t *buffer; - int destroy_buffer_when_done; - double system_clock_ref; - int has_pack_header; - int has_system_header; - int num_audio_streams; - int num_video_streams; - plm_packet_t current_packet; - plm_packet_t next_packet; -} plm_demux_t; - -double plm_demux_read_time(plm_demux_t *self); -void plm_demux_decode_pack_header(plm_demux_t *self); -void plm_demux_decode_system_header(plm_demux_t *self); -plm_packet_t *plm_demux_decode_packet(plm_demux_t *self, int start_code); -plm_packet_t *plm_demux_get_packet(plm_demux_t *self); - -COSMOPOLITAN_C_END_ -#endif /* COSMOPOLITAN_DSP_MPEG_DEMUX_H_ */ diff --git a/dsp/mpeg/idct.c b/dsp/mpeg/idct.c deleted file mode 100644 index 11312607e..000000000 --- a/dsp/mpeg/idct.c +++ /dev/null @@ -1,101 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:4;tab-width:4;coding:utf-8 -*-│ -│ vi: set et ft=c ts=4 sw=4 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer │ -│ Dominic Szablewski - https://phoboslab.org │ -│ │ -│ The MIT License(MIT) │ -│ Copyright(c) 2019 Dominic Szablewski │ -│ │ -│ Permission is hereby granted, free of charge, to any person obtaining │ -│ a copy of this software and associated documentation files(the │ -│ "Software"), to deal in the Software without restriction, including │ -│ without limitation the rights to use, copy, modify, merge, publish, │ -│ distribute, sublicense, and / or sell copies of the Software, and to │ -│ permit persons to whom the Software is furnished to do so, subject to │ -│ the following conditions: │ -│ │ -│ The above copyright notice and this permission notice shall be │ -│ included in all copies or substantial portions of the Software. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, │ -│ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF │ -│ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND │ -│ NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE │ -│ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN │ -│ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN │ -│ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE │ -│ SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "dsp/core/half.h" -__static_yoink("pl_mpeg_notice"); - -/** - * Computes Fixed-Point 8x8 Inverse Discrete Cosine Transform. - * - * @note discovered by Nasir Ahmed - */ -void plm_video_idct(int block[8][8]) { - int i, t1, t2, m0; - int b1, b3, b4, b6, b7; - int y3, y4, y5, y6, y7; - int x0, x1, x2, x3, x4; - - for (i = 0; i < 8; ++i) { - b1 = block[4][i]; - b3 = block[2][i] + block[6][i]; - b4 = block[5][i] - block[3][i]; - t1 = block[1][i] + block[7][i]; - t2 = block[3][i] + block[5][i]; - b6 = block[1][i] - block[7][i]; - b7 = t1 + t2; - m0 = block[0][i]; - x4 = ((b6 * 473 - b4 * 196 + 128) >> 8) - b7; - x0 = x4 - (((t1 - t2) * 362 + 128) >> 8); - x1 = m0 - b1; - x2 = (((block[2][i] - block[6][i]) * 362 + 128) >> 8) - b3; - x3 = m0 + b1; - y3 = x1 + x2; - y4 = x3 + b3; - y5 = x1 - x2; - y6 = x3 - b3; - y7 = -x0 - ((b4 * 473 + b6 * 196 + 128) >> 8); - block[0][i] = b7 + y4; - block[1][i] = x4 + y3; - block[2][i] = y5 - x0; - block[3][i] = y6 - y7; - block[4][i] = y6 + y7; - block[5][i] = x0 + y5; - block[6][i] = y3 - x4; - block[7][i] = y4 - b7; - } - - for (i = 0; i < 8; ++i) { - b1 = block[i][4]; - b3 = block[i][2] + block[i][6]; - b4 = block[i][5] - block[i][3]; - t1 = block[i][1] + block[i][7]; - t2 = block[i][3] + block[i][5]; - b6 = block[i][1] - block[i][7]; - b7 = t1 + t2; - m0 = block[i][0]; - x4 = ((b6 * 473 - b4 * 196 + 128) >> 8) - b7; - x0 = x4 - (((t1 - t2) * 362 + 128) >> 8); - x1 = m0 - b1; - x2 = (((block[i][2] - block[i][6]) * 362 + 128) >> 8) - b3; - x3 = m0 + b1; - y3 = x1 + x2; - y4 = x3 + b3; - y5 = x1 - x2; - y6 = x3 - b3; - y7 = -x0 - ((b4 * 473 + b6 * 196 + 128) >> 8); - block[i][0] = (b7 + y4 + 128) >> 8; - block[i][1] = (x4 + y3 + 128) >> 8; - block[i][2] = (y5 - x0 + 128) >> 8; - block[i][3] = (y6 - y7 + 128) >> 8; - block[i][4] = (y6 + y7 + 128) >> 8; - block[i][5] = (x0 + y5 + 128) >> 8; - block[i][6] = (y3 - x4 + 128) >> 8; - block[i][7] = (y4 - b7 + 128) >> 8; - } -} diff --git a/dsp/mpeg/idct.h b/dsp/mpeg/idct.h deleted file mode 100644 index 1d16f8e38..000000000 --- a/dsp/mpeg/idct.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef COSMOPOLITAN_DSP_MPEG_IDCT_H_ -#define COSMOPOLITAN_DSP_MPEG_IDCT_H_ -COSMOPOLITAN_C_START_ - -void plm_video_idct(int *); - -COSMOPOLITAN_C_END_ -#endif /* COSMOPOLITAN_DSP_MPEG_IDCT_H_ */ diff --git a/dsp/mpeg/macroblock.c b/dsp/mpeg/macroblock.c deleted file mode 100644 index 783f963bc..000000000 --- a/dsp/mpeg/macroblock.c +++ /dev/null @@ -1,171 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:4;tab-width:4;coding:utf-8 -*-│ -│ vi: set et ft=c ts=4 sw=4 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer │ -│ Dominic Szablewski - https://phoboslab.org │ -│ │ -│ The MIT License(MIT) │ -│ Copyright(c) 2019 Dominic Szablewski │ -│ │ -│ Permission is hereby granted, free of charge, to any person obtaining │ -│ a copy of this software and associated documentation files(the │ -│ "Software"), to deal in the Software without restriction, including │ -│ without limitation the rights to use, copy, modify, merge, publish, │ -│ distribute, sublicense, and / or sell copies of the Software, and to │ -│ permit persons to whom the Software is furnished to do so, subject to │ -│ the following conditions: │ -│ │ -│ The above copyright notice and this permission notice shall be │ -│ included in all copies or substantial portions of the Software. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, │ -│ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF │ -│ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND │ -│ NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE │ -│ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN │ -│ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN │ -│ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE │ -│ SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "dsp/mpeg/mpeg.h" -#include "dsp/mpeg/video.h" -#include "libc/log/check.h" - -forceinline void plm_video_process_macroblock(plm_video_t *self, uint8_t *d, - uint8_t *s, int motion_h, - int motion_v, bool interpolate, - unsigned BW) { - unsigned si, di, max_address; - int y, x, dest_scan, source_scan, dw, hp, vp, odd_h, odd_v; - dw = self->mb_width * BW; - hp = motion_h >> 1; - vp = motion_v >> 1; - odd_h = (motion_h & 1) == 1; - odd_v = (motion_v & 1) == 1; - si = ((self->mb_row * BW) + vp) * dw + (self->mb_col * BW) + hp; - di = (self->mb_row * dw + self->mb_col) * BW; - max_address = (dw * (self->mb_height * BW - BW + 1) - BW); - if (si > max_address || di > max_address) - return; - d += di; - s += si; - switch (((interpolate << 2) | (odd_h << 1) | (odd_v)) & 7) { - case 0: - dest_scan = dw - BW; - source_scan = dw - BW; - for (y = 0; y < BW; y++) { - for (x = 0; x < BW; x++) { - *d++ = *s++; - } - s += source_scan; - d += dest_scan; - } - break; - case 1: - dest_scan = dw - BW; - source_scan = dw - BW; - for (y = 0; y < BW; y++) { - for (x = 0; x < BW; x++) { - *d++ = (s[0] + s[dw] + 1) >> 1; - s++; - } - s += source_scan; - d += dest_scan; - } - break; - case 2: - dest_scan = dw - BW; - source_scan = dw - BW; - for (y = 0; y < BW; y++) { - for (x = 0; x < BW; x++) { - *d++ = (s[0] + s[1] + 1) >> 1; - s++; - } - s += source_scan; - d += dest_scan; - } - break; - case 3: - dest_scan = dw - BW; - source_scan = dw - BW; - for (y = 0; y < BW; y++) { - for (x = 0; x < BW; x++) { - *d++ = (s[0] + s[1] + s[dw] + s[dw + 1] + 2) >> 2; - s++; - } - s += source_scan; - d += dest_scan; - } - break; - case 4: - dest_scan = dw - BW; - source_scan = dw - BW; - for (y = 0; y < BW; y++) { - for (x = 0; x < BW; x++) { - d[0] = (d[0] + (s[0]) + 1) >> 1; - d++; - s++; - } - s += source_scan; - d += dest_scan; - } - break; - case 5: - dest_scan = dw - BW; - source_scan = dw - BW; - for (y = 0; y < BW; y++) { - for (x = 0; x < BW; x++) { - d[0] = (d[0] + ((s[0] + s[dw] + 1) >> 1) + 1) >> 1; - d++; - s++; - } - s += source_scan; - d += dest_scan; - } - break; - case 6: - dest_scan = dw - BW; - source_scan = dw - BW; - for (y = 0; y < BW; y++) { - for (x = 0; x < BW; x++) { - d[0] = (d[0] + ((s[0] + s[1] + 1) >> 1) + 1) >> 1; - d++; - s++; - } - s += source_scan; - d += dest_scan; - } - break; - case 7: - dest_scan = dw - BW; - source_scan = dw - BW; - for (y = 0; y < BW; y++) { - for (x = 0; x < BW; x++) { - d[0] = (d[0] + ((s[0] + s[1] + s[dw] + s[dw + 1] + 2) >> 2) + 1) >> 1; - d++; - s++; - } - s += source_scan; - d += dest_scan; - } - break; - default: - break; - } -} - -void plm_video_process_macroblock_8(plm_video_t *self, uint8_t *d, uint8_t *s, - int motion_h, int motion_v, - bool interpolate) { - DCHECK_ALIGNED(8, d); - DCHECK_ALIGNED(8, s); - plm_video_process_macroblock(self, d, s, motion_h, motion_v, interpolate, 8); -} - -void plm_video_process_macroblock_16(plm_video_t *self, uint8_t *d, uint8_t *s, - int motion_h, int motion_v, - bool interpolate) { - DCHECK_ALIGNED(16, d); - DCHECK_ALIGNED(16, s); - plm_video_process_macroblock(self, d, s, motion_h, motion_v, interpolate, 16); -} diff --git a/dsp/mpeg/mp2.c b/dsp/mpeg/mp2.c deleted file mode 100644 index 53fc91a23..000000000 --- a/dsp/mpeg/mp2.c +++ /dev/null @@ -1,769 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:4;tab-width:4;coding:utf-8 -*-│ -│ vi: set noet ft=c ts=4 sw=4 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer │ -│ Dominic Szablewski - https://phoboslab.org │ -│ │ -│ The MIT License(MIT) │ -│ Copyright(c) 2019 Dominic Szablewski │ -│ │ -│ Permission is hereby granted, free of charge, to any person obtaining │ -│ a copy of this software and associated documentation files(the │ -│ "Software"), to deal in the Software without restriction, including │ -│ without limitation the rights to use, copy, modify, merge, publish, │ -│ distribute, sublicense, and / or sell copies of the Software, and to │ -│ permit persons to whom the Software is furnished to do so, subject to │ -│ the following conditions: │ -│ │ -│ The above copyright notice and this permission notice shall be │ -│ included in all copies or substantial portions of the Software. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, │ -│ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF │ -│ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND │ -│ NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE │ -│ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN │ -│ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN │ -│ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE │ -│ SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "dsp/mpeg/buffer.h" -#include "dsp/mpeg/mpeg.h" -#include "libc/log/log.h" -#include "libc/mem/mem.h" -#include "libc/str/str.h" - -/* clang-format off */ -// ----------------------------------------------------------------------------- -// plm_audio implementation - -// Based on kjmp2 by Martin J. Fiedler -// http://keyj.emphy.de/kjmp2/ - -#define PLM_AUDIO_FRAME_SYNC 0x7ff - -#define PLM_AUDIO_MPEG_2_5 0x0 -#define PLM_AUDIO_MPEG_2 0x2 -#define PLM_AUDIO_MPEG_1 0x3 - -#define PLM_AUDIO_LAYER_III 0x1 -#define PLM_AUDIO_LAYER_II 0x2 -#define PLM_AUDIO_LAYER_I 0x3 - -#define PLM_AUDIO_MODE_STEREO 0x0 -#define PLM_AUDIO_MODE_JOINT_STEREO 0x1 -#define PLM_AUDIO_MODE_DUAL_CHANNEL 0x2 -#define PLM_AUDIO_MODE_MONO 0x3 - -static const unsigned short PLM_AUDIO_SAMPLE_RATE[] = { - 44100, 48000, 32000, 0, // MPEG-1 - 22050, 24000, 16000, 0 // MPEG-2 -}; - -static const short PLM_AUDIO_BIT_RATE[] = { - 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, // MPEG-1 - 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 // MPEG-2 -}; - -static const int PLM_AUDIO_SCALEFACTOR_BASE[] = { - 0x02000000, 0x01965FEA, 0x01428A30 -}; - -static const float PLM_AUDIO_SYNTHESIS_WINDOW[] = { - 0.0, -0.5, -0.5, -0.5, -0.5, -0.5, - -0.5, -1.0, -1.0, -1.0, -1.0, -1.5, - -1.5, -2.0, -2.0, -2.5, -2.5, -3.0, - -3.5, -3.5, -4.0, -4.5, -5.0, -5.5, - -6.5, -7.0, -8.0, -8.5, -9.5, -10.5, - -12.0, -13.0, -14.5, -15.5, -17.5, -19.0, - -20.5, -22.5, -24.5, -26.5, -29.0, -31.5, - -34.0, -36.5, -39.5, -42.5, -45.5, -48.5, - -52.0, -55.5, -58.5, -62.5, -66.0, -69.5, - -73.5, -77.0, -80.5, -84.5, -88.0, -91.5, - -95.0, -98.0, -101.0, -104.0, 106.5, 109.0, - 111.0, 112.5, 113.5, 114.0, 114.0, 113.5, - 112.0, 110.5, 107.5, 104.0, 100.0, 94.5, - 88.5, 81.5, 73.0, 63.5, 53.0, 41.5, - 28.5, 14.5, -1.0, -18.0, -36.0, -55.5, - -76.5, -98.5, -122.0, -147.0, -173.5, -200.5, - -229.5, -259.5, -290.5, -322.5, -355.5, -389.5, - -424.0, -459.5, -495.5, -532.0, -568.5, -605.0, - -641.5, -678.0, -714.0, -749.0, -783.5, -817.0, - -849.0, -879.5, -908.5, -935.0, -959.5, -981.0, - -1000.5, -1016.0, -1028.5, -1037.5, -1042.5, -1043.5, - -1040.0, -1031.5, 1018.5, 1000.0, 976.0, 946.5, - 911.0, 869.5, 822.0, 767.5, 707.0, 640.0, - 565.5, 485.0, 397.0, 302.5, 201.0, 92.5, - -22.5, -144.0, -272.5, -407.0, -547.5, -694.0, - -846.0, -1003.0, -1165.0, -1331.5, -1502.0, -1675.5, - -1852.5, -2031.5, -2212.5, -2394.0, -2576.5, -2758.5, - -2939.5, -3118.5, -3294.5, -3467.5, -3635.5, -3798.5, - -3955.0, -4104.5, -4245.5, -4377.5, -4499.0, -4609.5, - -4708.0, -4792.5, -4863.5, -4919.0, -4958.0, -4979.5, - -4983.0, -4967.5, -4931.5, -4875.0, -4796.0, -4694.5, - -4569.5, -4420.0, -4246.0, -4046.0, -3820.0, -3567.0, - 3287.0, 2979.5, 2644.0, 2280.5, 1888.0, 1467.5, - 1018.5, 541.0, 35.0, -499.0, -1061.0, -1650.0, - -2266.5, -2909.0, -3577.0, -4270.0, -4987.5, -5727.5, - -6490.0, -7274.0, -8077.5, -8899.5, -9739.0, -10594.5, - -11464.5, -12347.0, -13241.0, -14144.5, -15056.0, -15973.5, - -16895.5, -17820.0, -18744.5, -19668.0, -20588.0, -21503.0, - -22410.5, -23308.5, -24195.0, -25068.5, -25926.5, -26767.0, - -27589.0, -28389.0, -29166.5, -29919.0, -30644.5, -31342.0, - -32009.5, -32645.0, -33247.0, -33814.5, -34346.0, -34839.5, - -35295.0, -35710.0, -36084.5, -36417.5, -36707.5, -36954.0, - -37156.5, -37315.0, -37428.0, -37496.0, 37519.0, 37496.0, - 37428.0, 37315.0, 37156.5, 36954.0, 36707.5, 36417.5, - 36084.5, 35710.0, 35295.0, 34839.5, 34346.0, 33814.5, - 33247.0, 32645.0, 32009.5, 31342.0, 30644.5, 29919.0, - 29166.5, 28389.0, 27589.0, 26767.0, 25926.5, 25068.5, - 24195.0, 23308.5, 22410.5, 21503.0, 20588.0, 19668.0, - 18744.5, 17820.0, 16895.5, 15973.5, 15056.0, 14144.5, - 13241.0, 12347.0, 11464.5, 10594.5, 9739.0, 8899.5, - 8077.5, 7274.0, 6490.0, 5727.5, 4987.5, 4270.0, - 3577.0, 2909.0, 2266.5, 1650.0, 1061.0, 499.0, - -35.0, -541.0, -1018.5, -1467.5, -1888.0, -2280.5, - -2644.0, -2979.5, 3287.0, 3567.0, 3820.0, 4046.0, - 4246.0, 4420.0, 4569.5, 4694.5, 4796.0, 4875.0, - 4931.5, 4967.5, 4983.0, 4979.5, 4958.0, 4919.0, - 4863.5, 4792.5, 4708.0, 4609.5, 4499.0, 4377.5, - 4245.5, 4104.5, 3955.0, 3798.5, 3635.5, 3467.5, - 3294.5, 3118.5, 2939.5, 2758.5, 2576.5, 2394.0, - 2212.5, 2031.5, 1852.5, 1675.5, 1502.0, 1331.5, - 1165.0, 1003.0, 846.0, 694.0, 547.5, 407.0, - 272.5, 144.0, 22.5, -92.5, -201.0, -302.5, - -397.0, -485.0, -565.5, -640.0, -707.0, -767.5, - -822.0, -869.5, -911.0, -946.5, -976.0, -1000.0, - 1018.5, 1031.5, 1040.0, 1043.5, 1042.5, 1037.5, - 1028.5, 1016.0, 1000.5, 981.0, 959.5, 935.0, - 908.5, 879.5, 849.0, 817.0, 783.5, 749.0, - 714.0, 678.0, 641.5, 605.0, 568.5, 532.0, - 495.5, 459.5, 424.0, 389.5, 355.5, 322.5, - 290.5, 259.5, 229.5, 200.5, 173.5, 147.0, - 122.0, 98.5, 76.5, 55.5, 36.0, 18.0, - 1.0, -14.5, -28.5, -41.5, -53.0, -63.5, - -73.0, -81.5, -88.5, -94.5, -100.0, -104.0, - -107.5, -110.5, -112.0, -113.5, -114.0, -114.0, - -113.5, -112.5, -111.0, -109.0, 106.5, 104.0, - 101.0, 98.0, 95.0, 91.5, 88.0, 84.5, - 80.5, 77.0, 73.5, 69.5, 66.0, 62.5, - 58.5, 55.5, 52.0, 48.5, 45.5, 42.5, - 39.5, 36.5, 34.0, 31.5, 29.0, 26.5, - 24.5, 22.5, 20.5, 19.0, 17.5, 15.5, - 14.5, 13.0, 12.0, 10.5, 9.5, 8.5, - 8.0, 7.0, 6.5, 5.5, 5.0, 4.5, - 4.0, 3.5, 3.5, 3.0, 2.5, 2.5, - 2.0, 2.0, 1.5, 1.5, 1.0, 1.0, - 1.0, 1.0, 0.5, 0.5, 0.5, 0.5, - 0.5, 0.5 -}; - -// Quantizer lookup, step 1: bitrate classes -static const uint8_t PLM_AUDIO_QUANT_LUT_STEP_1[2][16] = { - // 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384 <- bitrate - { 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2 }, // mono - // 16, 24, 28, 32, 40, 48, 56, 64, 80, 96,112,128,160,192 <- bitrate / chan - { 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2 } // stereo -}; - -// Quantizer lookup, step 2: bitrate class, sample rate -> B2 table idx, sblimit -static const uint8_t PLM_AUDIO_QUANT_TAB_A = (27 | 64); // Table 3-B.2a: high-rate, sblimit = 27 -static const uint8_t PLM_AUDIO_QUANT_TAB_B = (30 | 64); // Table 3-B.2b: high-rate, sblimit = 30 -static const uint8_t PLM_AUDIO_QUANT_TAB_C = 8; // Table 3-B.2c: low-rate, sblimit = 8 -static const uint8_t PLM_AUDIO_QUANT_TAB_D = 12; // Table 3-B.2d: low-rate, sblimit = 12 - -static const uint8_t QUANT_LUT_STEP_2[3][3] = { - // 44.1 kHz, 48 kHz, 32 kHz - { PLM_AUDIO_QUANT_TAB_C, PLM_AUDIO_QUANT_TAB_C, PLM_AUDIO_QUANT_TAB_D }, // 32 - 48 kbit/sec/ch - { PLM_AUDIO_QUANT_TAB_A, PLM_AUDIO_QUANT_TAB_A, PLM_AUDIO_QUANT_TAB_A }, // 56 - 80 kbit/sec/ch - { PLM_AUDIO_QUANT_TAB_B, PLM_AUDIO_QUANT_TAB_A, PLM_AUDIO_QUANT_TAB_B } // 96+ kbit/sec/ch -}; - -// Quantizer lookup, step 3: B2 table, subband -> nbal, row index -// (upper 4 bits: nbal, lower 4 bits: row index) -static const uint8_t PLM_AUDIO_QUANT_LUT_STEP_3[3][32] = { - // Low-rate table (3-B.2c and 3-B.2d) - { - 0x44,0x44, - 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34 - }, - // High-rate table (3-B.2a and 3-B.2b) - { - 0x43,0x43,0x43, - 0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42, - 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31, - 0x20,0x20,0x20,0x20,0x20,0x20,0x20 - }, - // MPEG-2 LSR table (B.2 in ISO 13818-3) - { - 0x45,0x45,0x45,0x45, - 0x34,0x34,0x34,0x34,0x34,0x34,0x34, - 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24, - 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24 - } -}; - -// Quantizer lookup, step 4: table row, allocation[] value -> quant table index -static const uint8_t PLM_AUDIO_QUANT_LUT_STEP4[6][16] = { - { 0, 1, 2, 17 }, - { 0, 1, 2, 3, 4, 5, 6, 17 }, - { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17 }, - { 0, 1, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }, - { 0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17 }, - { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } -}; - -typedef struct plm_quantizer_spec_t { - unsigned short levels; - unsigned char group; - unsigned char bits; -} plm_quantizer_spec_t; - -static const plm_quantizer_spec_t PLM_AUDIO_QUANT_TAB[] = { - { 3, 1, 5 }, // 1 - { 5, 1, 7 }, // 2 - { 7, 0, 3 }, // 3 - { 9, 1, 10 }, // 4 - { 15, 0, 4 }, // 5 - { 31, 0, 5 }, // 6 - { 63, 0, 6 }, // 7 - { 127, 0, 7 }, // 8 - { 255, 0, 8 }, // 9 - { 511, 0, 9 }, // 10 - { 1023, 0, 10 }, // 11 - { 2047, 0, 11 }, // 12 - { 4095, 0, 12 }, // 13 - { 8191, 0, 13 }, // 14 - { 16383, 0, 14 }, // 15 - { 32767, 0, 15 }, // 16 - { 65535, 0, 16 } // 17 -}; - -struct plm_audio_t { - double time; - int samples_decoded; - int samplerate_index; - int bitrate_index; - int version; - int layer; - int mode; - int bound; - int v_pos; - int next_frame_data_size; - plm_buffer_t *buffer; - int destroy_buffer_when_done; - const plm_quantizer_spec_t *allocation[2][32]; - uint8_t scale_factor_info[2][32]; - int scale_factor[2][32][3]; - int sample[2][32][3]; - plm_samples_t samples; - float D[1024]; - float V[1024]; - float U[32]; -} forcealign(64); - -typedef plm_audio_t plm_audio_t; - -int plm_audio_decode_header(plm_audio_t *self); -void plm_audio_decode_frame(plm_audio_t *self); -const plm_quantizer_spec_t *plm_audio_read_allocation(plm_audio_t *self, int sb, int tab3); -void plm_audio_read_samples(plm_audio_t *self, int ch, int sb, int part); -void plm_audio_matrix_transform(int s[32][3], int ss, float *d, int dp); - -plm_audio_t *plm_audio_create_with_buffer(plm_buffer_t *buffer, int destroy_when_done) { - plm_audio_t *self = (plm_audio_t *)memalign(_Alignof(plm_audio_t), sizeof(plm_audio_t)); - memset(self, 0, sizeof(plm_audio_t)); - - self->samples.count = PLM_AUDIO_SAMPLES_PER_FRAME; - self->buffer = buffer; - self->destroy_buffer_when_done = destroy_when_done; - self->samplerate_index = 3; // indicates 0 samplerate - - memcpy(self->D, PLM_AUDIO_SYNTHESIS_WINDOW, 512 * sizeof(float)); - memcpy(self->D + 512, PLM_AUDIO_SYNTHESIS_WINDOW, 512 * sizeof(float)); - - // Decode first header - if (plm_buffer_has(self->buffer, 48)) { - self->next_frame_data_size = plm_audio_decode_header(self); - } - - return self; -} - -void plm_audio_destroy(plm_audio_t *self) { - if (self->destroy_buffer_when_done) { - plm_buffer_destroy(self->buffer); - } - free(self); -} - -int plm_audio_get_samplerate(plm_audio_t *self) { - return PLM_AUDIO_SAMPLE_RATE[self->samplerate_index]; -} - -double plm_audio_get_time(plm_audio_t *self) { - return self->time; -} - -void plm_audio_rewind(plm_audio_t *self) { - plm_buffer_rewind(self->buffer); - self->time = 0; - self->samples_decoded = 0; - self->next_frame_data_size = 0; - - // TODO: needed? - memset(self->V, 0, sizeof(self->V)); - memset(self->U, 0, sizeof(self->U)); -} - -plm_samples_t *plm_audio_decode(plm_audio_t *self) { - DEBUGF("%s", "plm_audio_decode"); - // Do we have at least enough information to decode the frame header? - if (!self->next_frame_data_size) { - if (!plm_buffer_has(self->buffer, 48)) { - return NULL; - } - self->next_frame_data_size = plm_audio_decode_header(self); - } - - if ( - self->next_frame_data_size == 0 || - !plm_buffer_has(self->buffer, self->next_frame_data_size << 3) - ) { - return NULL; - } - - plm_audio_decode_frame(self); - self->next_frame_data_size = 0; - - self->samples.time = self->time; - - self->samples_decoded += PLM_AUDIO_SAMPLES_PER_FRAME; - self->time = (double)self->samples_decoded / - (double)PLM_AUDIO_SAMPLE_RATE[self->samplerate_index]; - - return &self->samples; -} - -int plm_audio_decode_header(plm_audio_t *self) { - // Check for valid header: syncword OK, MPEG-Audio Layer 2 - plm_buffer_skip_bytes(self->buffer, 0x00); - - int sync = plm_buffer_read(self->buffer, 11); - self->version = plm_buffer_read(self->buffer, 2); - self->layer = plm_buffer_read(self->buffer, 2); - int hasCRC = !plm_buffer_read(self->buffer, 1); - - if ( - sync != PLM_AUDIO_FRAME_SYNC || - self->version != PLM_AUDIO_MPEG_1 || - self->layer != PLM_AUDIO_LAYER_II - ) { - return false; // Invalid header or unsupported version - } - - self->bitrate_index = plm_buffer_read(self->buffer, 4) - 1; - if (self->bitrate_index > 13) { - return false; // Invalid bit rate or 'free format' - } - - self->samplerate_index = plm_buffer_read(self->buffer, 2); - if (self->samplerate_index == 3) { - return false; // Invalid sample rate - } - - if (self->version == PLM_AUDIO_MPEG_2) { - self->samplerate_index += 4; - self->bitrate_index += 14; - } - int padding = plm_buffer_read(self->buffer, 1); - plm_buffer_skip(self->buffer, 1); // f_private - self->mode = plm_buffer_read(self->buffer, 2); - - // Parse the mode_extension, set up the stereo bound - self->bound = 0; - if (self->mode == PLM_AUDIO_MODE_JOINT_STEREO) { - self->bound = (plm_buffer_read(self->buffer, 2) + 1) << 2; - } - else { - plm_buffer_skip(self->buffer, 2); - self->bound = (self->mode == PLM_AUDIO_MODE_MONO) ? 0 : 32; - } - - // Discard the last 4 bits of the header and the CRC value, if present - plm_buffer_skip(self->buffer, 4); - if (hasCRC) { - plm_buffer_skip(self->buffer, 16); - } - - // Compute frame size, check if we have enough data to decode the whole - // frame. - int bitrate = PLM_AUDIO_BIT_RATE[self->bitrate_index]; - int samplerate = PLM_AUDIO_SAMPLE_RATE[self->samplerate_index]; - int frame_size = (144000 * bitrate / samplerate) + padding; - return frame_size - (hasCRC ? 6 : 4); -} - -void plm_audio_decode_frame(plm_audio_t *self) { - // Prepare the quantizer table lookups - int tab3 = 0; - int sblimit = 0; - if (self->version == PLM_AUDIO_MPEG_2) { - // MPEG-2 (LSR) - tab3 = 2; - sblimit = 30; - } - else { - // MPEG-1 - int tab1 = (self->mode == PLM_AUDIO_MODE_MONO) ? 0 : 1; - int tab2 = PLM_AUDIO_QUANT_LUT_STEP_1[tab1][self->bitrate_index]; - tab3 = QUANT_LUT_STEP_2[tab2][self->samplerate_index]; - sblimit = tab3 & 63; - tab3 >>= 6; - } - - if (self->bound > sblimit) { - self->bound = sblimit; - } - - // Read the allocation information - for (int sb = 0; sb < self->bound; sb++) { - self->allocation[0][sb] = plm_audio_read_allocation(self, sb, tab3); - self->allocation[1][sb] = plm_audio_read_allocation(self, sb, tab3); - } - - for (int sb = self->bound; sb < sblimit; sb++) { - self->allocation[0][sb] = - self->allocation[1][sb] = - plm_audio_read_allocation(self, sb, tab3); - } - - // Read scale factor selector information - int channels = (self->mode == PLM_AUDIO_MODE_MONO) ? 1 : 2; - for (int sb = 0; sb < sblimit; sb++) { - for (int ch = 0; ch < channels; ch++) { - if (self->allocation[ch][sb]) { - self->scale_factor_info[ch][sb] = plm_buffer_read(self->buffer, 2); - } - } - if (self->mode == PLM_AUDIO_MODE_MONO) { - self->scale_factor_info[1][sb] = self->scale_factor_info[0][sb]; - } - } - - // Read scale factors - for (int sb = 0; sb < sblimit; sb++) { - for (int ch = 0; ch < channels; ch++) { - if (self->allocation[ch][sb]) { - int *sf = self->scale_factor[ch][sb]; - switch (self->scale_factor_info[ch][sb]) { - case 0: - sf[0] = plm_buffer_read(self->buffer, 6); - sf[1] = plm_buffer_read(self->buffer, 6); - sf[2] = plm_buffer_read(self->buffer, 6); - break; - case 1: - sf[0] = - sf[1] = plm_buffer_read(self->buffer, 6); - sf[2] = plm_buffer_read(self->buffer, 6); - break; - case 2: - sf[0] = - sf[1] = - sf[2] = plm_buffer_read(self->buffer, 6); - break; - case 3: - sf[0] = plm_buffer_read(self->buffer, 6); - sf[1] = - sf[2] = plm_buffer_read(self->buffer, 6); - break; - } - } - } - if (self->mode == PLM_AUDIO_MODE_MONO) { - self->scale_factor[1][sb][0] = self->scale_factor[0][sb][0]; - self->scale_factor[1][sb][1] = self->scale_factor[0][sb][1]; - self->scale_factor[1][sb][2] = self->scale_factor[0][sb][2]; - } - } - - // Coefficient input and reconstruction - int out_pos = 0; - for (int part = 0; part < 3; part++) { - for (int granule = 0; granule < 4; granule++) { - - // Read the samples - for (int sb = 0; sb < self->bound; sb++) { - plm_audio_read_samples(self, 0, sb, part); - plm_audio_read_samples(self, 1, sb, part); - } - for (int sb = self->bound; sb < sblimit; sb++) { - plm_audio_read_samples(self, 0, sb, part); - self->sample[1][sb][0] = self->sample[0][sb][0]; - self->sample[1][sb][1] = self->sample[0][sb][1]; - self->sample[1][sb][2] = self->sample[0][sb][2]; - } - for (int sb = sblimit; sb < 32; sb++) { - self->sample[0][sb][0] = 0; - self->sample[0][sb][1] = 0; - self->sample[0][sb][2] = 0; - self->sample[1][sb][0] = 0; - self->sample[1][sb][1] = 0; - self->sample[1][sb][2] = 0; - } - - // Synthesis loop - for (int p = 0; p < 3; p++) { - // Shifting step - self->v_pos = (self->v_pos - 64) & 1023; - - for (int ch = 0; ch < 2; ch++) { - plm_audio_matrix_transform(self->sample[ch], p, self->V, self->v_pos); - - // Build U, windowing, calculate output - memset(self->U, 0, sizeof(self->U)); - - int d_index = 512 - (self->v_pos >> 1); - int v_index = (self->v_pos % 128) >> 1; - while (v_index < 1024) { - for (int i = 0; i < 32; ++i) { - self->U[i] += self->D[d_index++] * self->V[v_index++]; - } - - v_index += 128 - 32; - d_index += 64 - 32; - } - - d_index -= (512 - 32); - v_index = (128 - 32 + 1024) - v_index; - while (v_index < 1024) { - for (int i = 0; i < 32; ++i) { - self->U[i] += self->D[d_index++] * self->V[v_index++]; - } - - v_index += 128 - 32; - d_index += 64 - 32; - } - - // Output samples - #ifdef PLM_AUDIO_SEPARATE_CHANNELS - float *out_channel = ch == 0 - ? self->samples.left - : self->samples.right; - for (int j = 0; j < 32; j++) { - out_channel[out_pos + j] = self->U[j] / 2147418112.0f; - } - #else - for (int j = 0; j < 32; j++) { - self->samples.interleaved[((out_pos + j) << 1) + ch] = - self->U[j] / 2147418112.0f; - } - #endif - } // End of synthesis channel loop - out_pos += 32; - } // End of synthesis sub-block loop - - } // Decoding of the granule finished - } - - plm_buffer_align(self->buffer); -} - -const plm_quantizer_spec_t *plm_audio_read_allocation(plm_audio_t *self, int sb, int tab3) { - int tab4 = PLM_AUDIO_QUANT_LUT_STEP_3[tab3][sb]; - int qtab = PLM_AUDIO_QUANT_LUT_STEP4[tab4 & 15][plm_buffer_read(self->buffer, tab4 >> 4)]; - return qtab ? (&PLM_AUDIO_QUANT_TAB[qtab - 1]) : 0; -} - -void plm_audio_read_samples(plm_audio_t *self, int ch, int sb, int part) { - const plm_quantizer_spec_t *q = self->allocation[ch][sb]; - int sf = self->scale_factor[ch][sb][part]; - int *sample = self->sample[ch][sb]; - int val = 0; - - if (!q) { - // No bits allocated for this subband - sample[0] = sample[1] = sample[2] = 0; - return; - } - - // Resolve scalefactor - if (sf == 63) { - sf = 0; - } - else { - int shift = (sf / 3) | 0; - sf = (PLM_AUDIO_SCALEFACTOR_BASE[sf % 3] + ((1u << shift) >> 1)) >> shift; - } - - // Decode samples - int adj = q->levels; - if (q->group) { - // Decode grouped samples - val = plm_buffer_read(self->buffer, q->bits); - sample[0] = val % adj; - val /= adj; - sample[1] = val % adj; - sample[2] = val / adj; - } - else { - // Decode direct samples - sample[0] = plm_buffer_read(self->buffer, q->bits); - sample[1] = plm_buffer_read(self->buffer, q->bits); - sample[2] = plm_buffer_read(self->buffer, q->bits); - } - - // Postmultiply samples - int scale = 65536 / (adj + 1); - adj = ((adj + 1) >> 1) - 1; - - val = (adj - sample[0]) * scale; - sample[0] = (val * (sf >> 12) + ((val * (sf & 4095) + 2048) >> 12)) >> 12; - - val = (adj - sample[1]) * scale; - sample[1] = (val * (sf >> 12) + ((val * (sf & 4095) + 2048) >> 12)) >> 12; - - val = (adj - sample[2]) * scale; - sample[2] = (val * (sf >> 12) + ((val * (sf & 4095) + 2048) >> 12)) >> 12; -} - -void plm_audio_matrix_transform(int s[32][3], int ss, float *d, int dp) { - float t01, t02, t03, t04, t05, t06, t07, t08, t09, t10, t11, t12, - t13, t14, t15, t16, t17, t18, t19, t20, t21, t22, t23, t24, - t25, t26, t27, t28, t29, t30, t31, t32, t33; - - t01 = (float)(s[0][ss] + s[31][ss]); t02 = (float)(s[0][ss] - s[31][ss]) * 0.500602998235f; - t03 = (float)(s[1][ss] + s[30][ss]); t04 = (float)(s[1][ss] - s[30][ss]) * 0.505470959898f; - t05 = (float)(s[2][ss] + s[29][ss]); t06 = (float)(s[2][ss] - s[29][ss]) * 0.515447309923f; - t07 = (float)(s[3][ss] + s[28][ss]); t08 = (float)(s[3][ss] - s[28][ss]) * 0.53104259109f; - t09 = (float)(s[4][ss] + s[27][ss]); t10 = (float)(s[4][ss] - s[27][ss]) * 0.553103896034f; - t11 = (float)(s[5][ss] + s[26][ss]); t12 = (float)(s[5][ss] - s[26][ss]) * 0.582934968206f; - t13 = (float)(s[6][ss] + s[25][ss]); t14 = (float)(s[6][ss] - s[25][ss]) * 0.622504123036f; - t15 = (float)(s[7][ss] + s[24][ss]); t16 = (float)(s[7][ss] - s[24][ss]) * 0.674808341455f; - t17 = (float)(s[8][ss] + s[23][ss]); t18 = (float)(s[8][ss] - s[23][ss]) * 0.744536271002f; - t19 = (float)(s[9][ss] + s[22][ss]); t20 = (float)(s[9][ss] - s[22][ss]) * 0.839349645416f; - t21 = (float)(s[10][ss] + s[21][ss]); t22 = (float)(s[10][ss] - s[21][ss]) * 0.972568237862f; - t23 = (float)(s[11][ss] + s[20][ss]); t24 = (float)(s[11][ss] - s[20][ss]) * 1.16943993343f; - t25 = (float)(s[12][ss] + s[19][ss]); t26 = (float)(s[12][ss] - s[19][ss]) * 1.48416461631f; - t27 = (float)(s[13][ss] + s[18][ss]); t28 = (float)(s[13][ss] - s[18][ss]) * 2.05778100995f; - t29 = (float)(s[14][ss] + s[17][ss]); t30 = (float)(s[14][ss] - s[17][ss]) * 3.40760841847f; - t31 = (float)(s[15][ss] + s[16][ss]); t32 = (float)(s[15][ss] - s[16][ss]) * 10.1900081235f; - - t33 = t01 + t31; t31 = (t01 - t31) * 0.502419286188f; - t01 = t03 + t29; t29 = (t03 - t29) * 0.52249861494f; - t03 = t05 + t27; t27 = (t05 - t27) * 0.566944034816f; - t05 = t07 + t25; t25 = (t07 - t25) * 0.64682178336f; - t07 = t09 + t23; t23 = (t09 - t23) * 0.788154623451f; - t09 = t11 + t21; t21 = (t11 - t21) * 1.06067768599f; - t11 = t13 + t19; t19 = (t13 - t19) * 1.72244709824f; - t13 = t15 + t17; t17 = (t15 - t17) * 5.10114861869f; - t15 = t33 + t13; t13 = (t33 - t13) * 0.509795579104f; - t33 = t01 + t11; t01 = (t01 - t11) * 0.601344886935f; - t11 = t03 + t09; t09 = (t03 - t09) * 0.899976223136f; - t03 = t05 + t07; t07 = (t05 - t07) * 2.56291544774f; - t05 = t15 + t03; t15 = (t15 - t03) * 0.541196100146f; - t03 = t33 + t11; t11 = (t33 - t11) * 1.30656296488f; - t33 = t05 + t03; t05 = (t05 - t03) * 0.707106781187f; - t03 = t15 + t11; t15 = (t15 - t11) * 0.707106781187f; - t03 += t15; - t11 = t13 + t07; t13 = (t13 - t07) * 0.541196100146f; - t07 = t01 + t09; t09 = (t01 - t09) * 1.30656296488f; - t01 = t11 + t07; t07 = (t11 - t07) * 0.707106781187f; - t11 = t13 + t09; t13 = (t13 - t09) * 0.707106781187f; - t11 += t13; t01 += t11; - t11 += t07; t07 += t13; - t09 = t31 + t17; t31 = (t31 - t17) * 0.509795579104f; - t17 = t29 + t19; t29 = (t29 - t19) * 0.601344886935f; - t19 = t27 + t21; t21 = (t27 - t21) * 0.899976223136f; - t27 = t25 + t23; t23 = (t25 - t23) * 2.56291544774f; - t25 = t09 + t27; t09 = (t09 - t27) * 0.541196100146f; - t27 = t17 + t19; t19 = (t17 - t19) * 1.30656296488f; - t17 = t25 + t27; t27 = (t25 - t27) * 0.707106781187f; - t25 = t09 + t19; t19 = (t09 - t19) * 0.707106781187f; - t25 += t19; - t09 = t31 + t23; t31 = (t31 - t23) * 0.541196100146f; - t23 = t29 + t21; t21 = (t29 - t21) * 1.30656296488f; - t29 = t09 + t23; t23 = (t09 - t23) * 0.707106781187f; - t09 = t31 + t21; t31 = (t31 - t21) * 0.707106781187f; - t09 += t31; t29 += t09; t09 += t23; t23 += t31; - t17 += t29; t29 += t25; t25 += t09; t09 += t27; - t27 += t23; t23 += t19; t19 += t31; - t21 = t02 + t32; t02 = (t02 - t32) * 0.502419286188f; - t32 = t04 + t30; t04 = (t04 - t30) * 0.52249861494f; - t30 = t06 + t28; t28 = (t06 - t28) * 0.566944034816f; - t06 = t08 + t26; t08 = (t08 - t26) * 0.64682178336f; - t26 = t10 + t24; t10 = (t10 - t24) * 0.788154623451f; - t24 = t12 + t22; t22 = (t12 - t22) * 1.06067768599f; - t12 = t14 + t20; t20 = (t14 - t20) * 1.72244709824f; - t14 = t16 + t18; t16 = (t16 - t18) * 5.10114861869f; - t18 = t21 + t14; t14 = (t21 - t14) * 0.509795579104f; - t21 = t32 + t12; t32 = (t32 - t12) * 0.601344886935f; - t12 = t30 + t24; t24 = (t30 - t24) * 0.899976223136f; - t30 = t06 + t26; t26 = (t06 - t26) * 2.56291544774f; - t06 = t18 + t30; t18 = (t18 - t30) * 0.541196100146f; - t30 = t21 + t12; t12 = (t21 - t12) * 1.30656296488f; - t21 = t06 + t30; t30 = (t06 - t30) * 0.707106781187f; - t06 = t18 + t12; t12 = (t18 - t12) * 0.707106781187f; - t06 += t12; - t18 = t14 + t26; t26 = (t14 - t26) * 0.541196100146f; - t14 = t32 + t24; t24 = (t32 - t24) * 1.30656296488f; - t32 = t18 + t14; t14 = (t18 - t14) * 0.707106781187f; - t18 = t26 + t24; t24 = (t26 - t24) * 0.707106781187f; - t18 += t24; t32 += t18; - t18 += t14; t26 = t14 + t24; - t14 = t02 + t16; t02 = (t02 - t16) * 0.509795579104f; - t16 = t04 + t20; t04 = (t04 - t20) * 0.601344886935f; - t20 = t28 + t22; t22 = (t28 - t22) * 0.899976223136f; - t28 = t08 + t10; t10 = (t08 - t10) * 2.56291544774f; - t08 = t14 + t28; t14 = (t14 - t28) * 0.541196100146f; - t28 = t16 + t20; t20 = (t16 - t20) * 1.30656296488f; - t16 = t08 + t28; t28 = (t08 - t28) * 0.707106781187f; - t08 = t14 + t20; t20 = (t14 - t20) * 0.707106781187f; - t08 += t20; - t14 = t02 + t10; t02 = (t02 - t10) * 0.541196100146f; - t10 = t04 + t22; t22 = (t04 - t22) * 1.30656296488f; - t04 = t14 + t10; t10 = (t14 - t10) * 0.707106781187f; - t14 = t02 + t22; t02 = (t02 - t22) * 0.707106781187f; - t14 += t02; t04 += t14; t14 += t10; t10 += t02; - t16 += t04; t04 += t08; t08 += t14; t14 += t28; - t28 += t10; t10 += t20; t20 += t02; t21 += t16; - t16 += t32; t32 += t04; t04 += t06; t06 += t08; - t08 += t18; t18 += t14; t14 += t30; t30 += t28; - t28 += t26; t26 += t10; t10 += t12; t12 += t20; - t20 += t24; t24 += t02; - - d[dp + 48] = -t33; - d[dp + 49] = d[dp + 47] = -t21; - d[dp + 50] = d[dp + 46] = -t17; - d[dp + 51] = d[dp + 45] = -t16; - d[dp + 52] = d[dp + 44] = -t01; - d[dp + 53] = d[dp + 43] = -t32; - d[dp + 54] = d[dp + 42] = -t29; - d[dp + 55] = d[dp + 41] = -t04; - d[dp + 56] = d[dp + 40] = -t03; - d[dp + 57] = d[dp + 39] = -t06; - d[dp + 58] = d[dp + 38] = -t25; - d[dp + 59] = d[dp + 37] = -t08; - d[dp + 60] = d[dp + 36] = -t11; - d[dp + 61] = d[dp + 35] = -t18; - d[dp + 62] = d[dp + 34] = -t09; - d[dp + 63] = d[dp + 33] = -t14; - d[dp + 32] = -t05; - d[dp + 0] = t05; d[dp + 31] = -t30; - d[dp + 1] = t30; d[dp + 30] = -t27; - d[dp + 2] = t27; d[dp + 29] = -t28; - d[dp + 3] = t28; d[dp + 28] = -t07; - d[dp + 4] = t07; d[dp + 27] = -t26; - d[dp + 5] = t26; d[dp + 26] = -t23; - d[dp + 6] = t23; d[dp + 25] = -t10; - d[dp + 7] = t10; d[dp + 24] = -t15; - d[dp + 8] = t15; d[dp + 23] = -t12; - d[dp + 9] = t12; d[dp + 22] = -t19; - d[dp + 10] = t19; d[dp + 21] = -t20; - d[dp + 11] = t20; d[dp + 20] = -t13; - d[dp + 12] = t13; d[dp + 19] = -t24; - d[dp + 13] = t24; d[dp + 18] = -t31; - d[dp + 14] = t31; d[dp + 17] = -t02; - d[dp + 15] = t02; d[dp + 16] = 0.0; -}; - diff --git a/dsp/mpeg/mpeg.h b/dsp/mpeg/mpeg.h deleted file mode 100644 index f49ed953b..000000000 --- a/dsp/mpeg/mpeg.h +++ /dev/null @@ -1,447 +0,0 @@ -#ifndef COSMOPOLITAN_DSP_MPEG_MPEG_H_ -#define COSMOPOLITAN_DSP_MPEG_MPEG_H_ -#include "libc/stdio/stdio.h" -COSMOPOLITAN_C_START_ - -typedef struct plm_t plm_t; -typedef struct plm_buffer_t plm_buffer_t; -typedef struct plm_demux_t plm_demux_t; -typedef struct plm_video_t plm_video_t; -typedef struct plm_audio_t plm_audio_t; - -/** - * Demuxed MPEG PS packet - * - * The type maps directly to the various MPEG-PES start codes. pts is - * the presentation time stamp of the packet in seconds. Not all packets - * have a pts value. - */ -typedef struct plm_packet_t { - int type; - double pts; - size_t length; - uint8_t *data; -} plm_packet_t; - -/** - * Decoded Video Plane - * - * The byte length of the data is width * height. Note that different - * planes have different sizes: the Luma plane (Y) is double the size of - * each of the two Chroma planes (Cr, Cb) - i.e. 4 times the byte - * length. Also note that the size of the plane does *not* denote the - * size of the displayed frame. The sizes of planes are always rounded - * up to the nearest macroblock (16px). - */ -typedef struct plm_plane_t { - unsigned int width; - unsigned int height; - uint8_t *data; -} plm_plane_t; - -/** - * Decoded Video Frame - * - * Width and height denote the desired display size of the frame. This - * may be different from the internal size of the 3 planes. - */ -typedef struct plm_frame_t { - double time; - unsigned int width; - unsigned int height; - plm_plane_t y; - plm_plane_t cr; - plm_plane_t cb; -} plm_frame_t; - -/** - * Callback function type for decoded video frames used by the high-level - * plm_* interface - */ -typedef void (*plm_video_decode_callback)(plm_t *self, plm_frame_t *frame, - void *user); - -/** - * Decoded Audio Samples - * - * Samples are stored as normalized (-1, 1) float either interleaved, or if - * PLM_AUDIO_SEPARATE_CHANNELS is defined, in two separate arrays. - * The `count` is always PLM_AUDIO_SAMPLES_PER_FRAME and just there for - * convenience. - */ -#define PLM_AUDIO_SAMPLES_PER_FRAME 1152 - -struct plm_samples_t { - double time; - unsigned int count; -#ifdef PLM_AUDIO_SEPARATE_CHANNELS - float left[PLM_AUDIO_SAMPLES_PER_FRAME] forcealign(32); - float right[PLM_AUDIO_SAMPLES_PER_FRAME] forcealign(32); -#else - float interleaved[PLM_AUDIO_SAMPLES_PER_FRAME * 2] forcealign(32); -#endif -} forcealign(32); - -typedef struct plm_samples_t plm_samples_t; - -/** - * Callback function type for decoded audio samples used by the high-level - * plm_* interface - */ -typedef void (*plm_audio_decode_callback)(plm_t *self, plm_samples_t *samples, - void *user); - -/** - * Callback function for plm_buffer when it needs more data - */ -typedef void (*plm_buffer_load_callback)(plm_buffer_t *self, void *user); - -/** - * ----------------------------------------------------------------------------- - * plm_* public API - * High-Level API for loading/demuxing/decoding MPEG-PS data - * - * Create a plmpeg instance with a filename. Returns NULL if the file could not - * be opened. - */ -plm_t *plm_create_with_filename(const char *filename); - -/** - * Create a plmpeg instance with file handle. Pass true to close_when_done - * to let plmpeg call fclose() on the handle when plm_destroy() is - * called. - */ -plm_t *plm_create_with_file(FILE *fh, int close_when_done); - -/** - * Create a plmpeg instance with pointer to memory as source. This assumes the - * whole file is in memory. Pass true to free_when_done to let plmpeg call - * free() on the pointer when plm_destroy() is called. - */ -plm_t *plm_create_with_memory(uint8_t *bytes, size_t length, - int free_when_done); - -/** - * Create a plmpeg instance with a plm_buffer as source. This is also - * called internally by all the above constructor functions. - */ -plm_t *plm_create_with_buffer(plm_buffer_t *buffer, int destroy_when_done); - -/** - * Destroy a plmpeg instance and free all data - */ -void plm_destroy(plm_t *self); - -/** - * Get or set whether video decoding is enabled. - */ -int plm_get_video_enabled(plm_t *self); -void plm_set_video_enabled(plm_t *self, int enabled); - -/** - * Get or set whether audio decoding is enabled. When enabling, you can set the - * desired audio stream (0-3) to decode. - */ -int plm_get_audio_enabled(plm_t *self); -void plm_set_audio_enabled(plm_t *self, int enabled, int stream_index); - -/** - * Get the display width/height of the video stream - */ -int plm_get_width(plm_t *self); -int plm_get_height(plm_t *self); - -double plm_get_pixel_aspect_ratio(plm_t *); - -/** - * Get the framerate of the video stream in frames per second - */ -double plm_get_framerate(plm_t *self); - -/** - * Get the number of available audio streams in the file - */ -int plm_get_num_audio_streams(plm_t *self); - -/** - * Get the samplerate of the audio stream in samples per second - */ -int plm_get_samplerate(plm_t *self); - -/** - * Get or set the audio lead time in seconds - the time in which audio samples - * are decoded in advance (or behind) the video decode time. Default 0. - */ -double plm_get_audio_lead_time(plm_t *self); -void plm_set_audio_lead_time(plm_t *self, double lead_time); - -/** - * Get the current internal time in seconds - */ -double plm_get_time(plm_t *self); - -/** - * Rewind all buffers back to the beginning. - */ -void plm_rewind(plm_t *self); - -/** - * Get or set looping. Default false. - */ -int plm_get_loop(plm_t *self); -void plm_set_loop(plm_t *self, int loop); - -/** - * Get whether the file has ended. If looping is enabled, this will always - * return false. - */ -int plm_has_ended(plm_t *self); - -/** - * Set the callback for decoded video frames used with plm_decode(). If no - * callback is set, video data will be ignored and not be decoded. - */ -void plm_set_video_decode_callback(plm_t *self, plm_video_decode_callback fp, - void *user); - -/** - * Set the callback for decoded audio samples used with plm_decode(). If no - * callback is set, audio data will be ignored and not be decoded. - */ -void plm_set_audio_decode_callback(plm_t *self, plm_audio_decode_callback fp, - void *user); - -/** - * Advance the internal timer by seconds and decode video/audio up to - * this time. Returns true/false whether anything was decoded. - */ -int plm_decode(plm_t *self, double seconds); - -/** - * Decode and return one video frame. Returns NULL if no frame could be decoded - * (either because the source ended or data is corrupt). If you only want to - * decode video, you should disable audio via plm_set_audio_enabled(). - * The returned plm_frame_t is valid until the next call to - * plm_decode_video call or until the plm_destroy is called. - */ -plm_frame_t *plm_decode_video(plm_t *self); - -/** - * Decode and return one audio frame. Returns NULL if no frame could be decoded - * (either because the source ended or data is corrupt). If you only want to - * decode audio, you should disable video via plm_set_video_enabled(). - * The returned plm_samples_t is valid until the next call to - * plm_decode_video or until the plm_destroy is called. - */ -plm_samples_t *plm_decode_audio(plm_t *self); - -/* ----------------------------------------------------------------------------- - * plm_buffer public API - * Provides the data source for all other plm_* interfaces - * - * The default size for buffers created from files or by the high-level API - */ -#ifndef PLM_BUFFER_DEFAULT_SIZE -#define PLM_BUFFER_DEFAULT_SIZE (128 * 1024) -#endif - -/** - * Create a buffer instance with a filename. Returns NULL if the file could not - * be opened. - */ -plm_buffer_t *plm_buffer_create_with_filename(const char *filename); - -/** - * Create a buffer instance with file handle. Pass true to close_when_done - * to let plmpeg call fclose() on the handle when plm_destroy() is - * called. - */ -plm_buffer_t *plm_buffer_create_with_file(FILE *fh, int close_when_done); - -/** - * Create a buffer instance with a pointer to memory as source. This assumes - * the whole file is in memory. Pass 1 to free_when_done to let plmpeg call - * free() on the pointer when plm_destroy() is called. - */ -plm_buffer_t *plm_buffer_create_with_memory(uint8_t *bytes, size_t length, - int free_when_done); - -/** - * Create an empty buffer with an initial capacity. The buffer will grow - * as needed. - */ -plm_buffer_t *plm_buffer_create_with_capacity(size_t capacity); - -/** - * Destroy a buffer instance and free all data - */ -void plm_buffer_destroy(plm_buffer_t *self); - -/** - * Copy data into the buffer. If the data to be written is larger than the - * available space, the buffer will realloc() with a larger capacity. - * Returns the number of bytes written. This will always be the same as the - * passed in length, except when the buffer was created _with_memory() for - * which _write() is forbidden. - */ -size_t plm_buffer_write(plm_buffer_t *self, uint8_t *bytes, size_t length); - -/** - * Set a callback that is called whenever the buffer needs more data - */ -void plm_buffer_set_load_callback(plm_buffer_t *self, - plm_buffer_load_callback fp, void *user); - -/** - * Rewind the buffer back to the beginning. When loading from a file handle, - * this also seeks to the beginning of the file. - */ -void plm_buffer_rewind(plm_buffer_t *self); - -/** - * ----------------------------------------------------------------------------- - * plm_demux public API - * Demux an MPEG Program Stream (PS) data into separate packages - * - * Various Packet Types - */ -#define PLM_DEMUX_PACKET_PRIVATE 0xBD -#define PLM_DEMUX_PACKET_AUDIO_1 0xC0 -#define PLM_DEMUX_PACKET_AUDIO_2 0xC1 -#define PLM_DEMUX_PACKET_AUDIO_3 0xC2 -#define PLM_DEMUX_PACKET_AUDIO_4 0xC2 -#define PLM_DEMUX_PACKET_VIDEO_1 0xE0 - -/** - * Create a demuxer with a plm_buffer as source - */ -plm_demux_t *plm_demux_create(plm_buffer_t *buffer, int destroy_when_done); - -/** - * Destroy a demuxer and free all data - */ -void plm_demux_destroy(plm_demux_t *self); - -/** - * Returns the number of video streams found in the system header. - */ -int plm_demux_get_num_video_streams(plm_demux_t *self); - -/** - * Returns the number of audio streams found in the system header. - */ -int plm_demux_get_num_audio_streams(plm_demux_t *self); - -/** - * Rewinds the internal buffer. See plm_buffer_rewind(). - */ -void plm_demux_rewind(plm_demux_t *self); - -/** - * Decode and return the next packet. The returned packet_t is valid until - * the next call to plm_demux_decode() or until the demuxer is destroyed. - */ -plm_packet_t *plm_demux_decode(plm_demux_t *self); - -/* ----------------------------------------------------------------------------- - * plm_video public API - * Decode MPEG1 Video ("mpeg1") data into raw YCrCb frames - */ - -/** - * Create a video decoder with a plm_buffer as source - */ -plm_video_t *plm_video_create_with_buffer(plm_buffer_t *buffer, - int destroy_when_done); - -/** - * Destroy a video decoder and free all data - */ -void plm_video_destroy(plm_video_t *self); - -/** - * Get the framerate in frames per second - */ -double plm_video_get_framerate(plm_video_t *); - -double plm_video_get_pixel_aspect_ratio(plm_video_t *); - -/** - * Get the display width/height - */ -int plm_video_get_width(plm_video_t *); -int plm_video_get_height(plm_video_t *); - -/** - * Set "no delay" mode. When enabled, the decoder assumes that the video does - * *not* contain any B-Frames. This is useful for reducing lag when streaming. - */ -void plm_video_set_no_delay(plm_video_t *self, int no_delay); - -/** - * Get the current internal time in seconds - */ -double plm_video_get_time(plm_video_t *self); - -/** - * Rewinds the internal buffer. See plm_buffer_rewind(). - */ -void plm_video_rewind(plm_video_t *self); - -/** - * Decode and return one frame of video and advance the internal time by - * 1/framerate seconds. The returned frame_t is valid until the next call of - * plm_video_decode() or until the video decoder is destroyed. - */ -plm_frame_t *plm_video_decode(plm_video_t *self); - -/** - * Convert the YCrCb data of a frame into an interleaved RGB buffer. The buffer - * pointed to by *rgb must have a size of (frame->width * frame->height * 3) - * bytes. - */ -void plm_frame_to_rgb(plm_frame_t *frame, uint8_t *rgb); - -/* ----------------------------------------------------------------------------- - * plm_audio public API - * Decode MPEG-1 Audio Layer II ("mp2") data into raw samples - */ - -/** - * Create an audio decoder with a plm_buffer as source. - */ -plm_audio_t *plm_audio_create_with_buffer(plm_buffer_t *buffer, - int destroy_when_done); - -/** - * Destroy an audio decoder and free all data - */ -void plm_audio_destroy(plm_audio_t *self); - -/** - * Get the samplerate in samples per second - */ -int plm_audio_get_samplerate(plm_audio_t *self); - -/** - * Get the current internal time in seconds - */ -double plm_audio_get_time(plm_audio_t *self); - -/** - * Rewinds the internal buffer. See plm_buffer_rewind(). - */ -void plm_audio_rewind(plm_audio_t *self); - -/** - * Decode and return one "frame" of audio and advance the internal time by - * (PLM_AUDIO_SAMPLES_PER_FRAME/samplerate) seconds. The returned samples_t - * is valid until the next call of plm_audio_decode() or until the audio - * decoder is destroyed. - */ -plm_samples_t *plm_audio_decode(plm_audio_t *self); - -extern long plmpegdecode_latency_; - -COSMOPOLITAN_C_END_ -#endif /* COSMOPOLITAN_DSP_MPEG_MPEG_H_ */ diff --git a/dsp/mpeg/mpeg1.c b/dsp/mpeg/mpeg1.c deleted file mode 100644 index 457dad4bc..000000000 --- a/dsp/mpeg/mpeg1.c +++ /dev/null @@ -1,1110 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:4;tab-width:4;coding:utf-8 -*-│ -│ vi: set et ft=c ts=4 sw=4 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer │ -│ Dominic Szablewski - https://phoboslab.org │ -│ │ -│ The MIT License(MIT) │ -│ Copyright(c) 2019 Dominic Szablewski │ -│ │ -│ Permission is hereby granted, free of charge, to any person obtaining │ -│ a copy of this software and associated documentation files(the │ -│ "Software"), to deal in the Software without restriction, including │ -│ without limitation the rights to use, copy, modify, merge, publish, │ -│ distribute, sublicense, and / or sell copies of the Software, and to │ -│ permit persons to whom the Software is furnished to do so, subject to │ -│ the following conditions: │ -│ │ -│ The above copyright notice and this permission notice shall be │ -│ included in all copies or substantial portions of the Software. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, │ -│ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF │ -│ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND │ -│ NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE │ -│ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN │ -│ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN │ -│ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE │ -│ SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "dsp/mpeg/blockset.h" -#include "dsp/mpeg/buffer.h" -#include "dsp/mpeg/idct.h" -#include "dsp/mpeg/mpeg.h" -#include "dsp/mpeg/video.h" -#include "libc/calls/struct/timespec.h" -#include "libc/fmt/conv.h" -#include "libc/log/log.h" -#include "libc/macros.h" -#include "libc/math.h" -#include "libc/mem/mem.h" -#include "libc/str/str.h" -#include "libc/time.h" -#include "libc/x/x.h" -__static_yoink("pl_mpeg_notice"); - -// ----------------------------------------------------------------------------- -// plm_video implementation - -// Inspired by Java MPEG-1 Video Decoder and Player by Zoltan Korandi -// https://sourceforge.net/projects/javampeg1video/ - -#define GETCONST(ARRAY, DEFAULT) - -static const int PLM_VIDEO_PICTURE_TYPE_INTRA = 1; -static const int PLM_VIDEO_PICTURE_TYPE_PREDICTIVE = 2; -static const int PLM_VIDEO_PICTURE_TYPE_B = 3; - -static const int PLM_START_SEQUENCE = 0xB3; -static const int PLM_START_SLICE_FIRST = 0x01; -static const int PLM_START_SLICE_LAST = 0xAF; -static const int PLM_START_PICTURE = 0x00; -static const int PLM_START_EXTENSION = 0xB5; -static const int PLM_START_USER_DATA = 0xB2; - -static const float PLM_VIDEO_PIXEL_ASPECT_RATIO[] = { - 1.0000, /* square pixels */ - 0.6735, /* 3:4? */ - 0.7031, /* MPEG-1 / MPEG-2 video encoding divergence? */ - 0.7615, 0.8055, 0.8437, 0.8935, 0.9157, 0.9815, - 1.0255, 1.0695, 1.0950, 1.1575, 1.2051, -}; - -static const float PLM_VIDEO_PICTURE_RATE[] = { - 23.976, /* NTSC-Film */ - 24.000, /* NTSC-Film (enriched for foreign nations) */ - 25.000, /* PAL (Britain, Africa, China, etc.) */ - 29.970, /* NTSC */ - 30.000, /* NTSC (enriched for foreign nations) */ - 50.000, /* PAL? */ - 59.940, /* NTSC-Wow */ - 60.000 /* NTSC-Wow (enriched for foreign nations) */ -}; - -static const uint8_t PLM_VIDEO_ZIG_ZAG[] = /* clang-format off */ { - 0, 1, 8, 16, 9, 2, 3, 10, - 17, 24, 32, 25, 18, 11, 4, 5, - 12, 19, 26, 33, 40, 48, 41, 34, - 27, 20, 13, 6, 7, 14, 21, 28, - 35, 42, 49, 56, 57, 50, 43, 36, - 29, 22, 15, 23, 30, 37, 44, 51, - 58, 59, 52, 45, 38, 31, 39, 46, - 53, 60, 61, 54, 47, 55, 62, 63, -} /* clang-format on */; - -static const uint8_t PLM_VIDEO_INTRAQUANT_MATRIX[] = /* clang-format off */ { - 8, 16, 19, 22, 26, 27, 29, 34, - 16, 16, 22, 24, 27, 29, 34, 37, - 19, 22, 26, 27, 29, 34, 34, 38, - 22, 22, 26, 27, 29, 34, 37, 40, - 22, 26, 27, 29, 32, 35, 40, 48, - 26, 27, 29, 32, 35, 40, 48, 58, - 26, 27, 29, 34, 38, 46, 56, 69, - 27, 29, 35, 38, 46, 56, 69, 83, -} /* clang-format on */; - -static const uint8_t PLM_VIDEO_NONINTRAQUANT_MATRIX[] = /* clang-format off */ { - 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, - 16, 16, 16, 16, 16, 16, 16, 16, -} /* clang-format on */; - -static const uint8_t PLM_VIDEO_PREMULTIPLIER_MATRIX[] = /* clang-format off */ { - 32, 44, 42, 38, 32, 25, 17, 9, - 44, 62, 58, 52, 44, 35, 24, 12, - 42, 58, 55, 49, 42, 33, 23, 12, - 38, 52, 49, 44, 38, 30, 20, 10, - 32, 44, 42, 38, 32, 25, 17, 9, - 25, 35, 33, 30, 25, 20, 14, 7, - 17, 24, 23, 20, 17, 14, 9, 5, - 9, 12, 12, 10, 9, 7, 5, 2, -} /* clang-format on */; - -static const plm_vlc_t PLM_VIDEO_MACROBLOCK_ADDRESS_INCREMENT[] = { - {1 << 1, 0}, {0, 1}, // 0: x - {2 << 1, 0}, {3 << 1, 0}, // 1: 0x - {4 << 1, 0}, {5 << 1, 0}, // 2: 00x - {0, 3}, {0, 2}, // 3: 01x - {6 << 1, 0}, {7 << 1, 0}, // 4: 000x - {0, 5}, {0, 4}, // 5: 001x - {8 << 1, 0}, {9 << 1, 0}, // 6: 0000x - {0, 7}, {0, 6}, // 7: 0001x - {10 << 1, 0}, {11 << 1, 0}, // 8: 0000 0x - {12 << 1, 0}, {13 << 1, 0}, // 9: 0000 1x - {14 << 1, 0}, {15 << 1, 0}, // 10: 0000 00x - {16 << 1, 0}, {17 << 1, 0}, // 11: 0000 01x - {18 << 1, 0}, {19 << 1, 0}, // 12: 0000 10x - {0, 9}, {0, 8}, // 13: 0000 11x - {-1, 0}, {20 << 1, 0}, // 14: 0000 000x - {-1, 0}, {21 << 1, 0}, // 15: 0000 001x - {22 << 1, 0}, {23 << 1, 0}, // 16: 0000 010x - {0, 15}, {0, 14}, // 17: 0000 011x - {0, 13}, {0, 12}, // 18: 0000 100x - {0, 11}, {0, 10}, // 19: 0000 101x - {24 << 1, 0}, {25 << 1, 0}, // 20: 0000 0001x - {26 << 1, 0}, {27 << 1, 0}, // 21: 0000 0011x - {28 << 1, 0}, {29 << 1, 0}, // 22: 0000 0100x - {30 << 1, 0}, {31 << 1, 0}, // 23: 0000 0101x - {32 << 1, 0}, {-1, 0}, // 24: 0000 0001 0x - {-1, 0}, {33 << 1, 0}, // 25: 0000 0001 1x - {34 << 1, 0}, {35 << 1, 0}, // 26: 0000 0011 0x - {36 << 1, 0}, {37 << 1, 0}, // 27: 0000 0011 1x - {38 << 1, 0}, {39 << 1, 0}, // 28: 0000 0100 0x - {0, 21}, {0, 20}, // 29: 0000 0100 1x - {0, 19}, {0, 18}, // 30: 0000 0101 0x - {0, 17}, {0, 16}, // 31: 0000 0101 1x - {0, 35}, {-1, 0}, // 32: 0000 0001 00x - {-1, 0}, {0, 34}, // 33: 0000 0001 11x - {0, 33}, {0, 32}, // 34: 0000 0011 00x - {0, 31}, {0, 30}, // 35: 0000 0011 01x - {0, 29}, {0, 28}, // 36: 0000 0011 10x - {0, 27}, {0, 26}, // 37: 0000 0011 11x - {0, 25}, {0, 24}, // 38: 0000 0100 00x - {0, 23}, {0, 22}, // 39: 0000 0100 01x -}; - -static const plm_vlc_t PLM_VIDEO_MACROBLOCK_TYPE_INTRA[] = { - {1 << 1, 0}, - {0, 0x01}, // 0: x - {-1, 0}, - {0, 0x11}, // 1: 0x -}; - -static const plm_vlc_t PLM_VIDEO_MACROBLOCK_TYPE_PREDICTIVE[] = { - {1 << 1, 0}, {0, 0x0a}, // 0: x - {2 << 1, 0}, {0, 0x02}, // 1: 0x - {3 << 1, 0}, {0, 0x08}, // 2: 00x - {4 << 1, 0}, {5 << 1, 0}, // 3: 000x - {6 << 1, 0}, {0, 0x12}, // 4: 0000x - {0, 0x1a}, {0, 0x01}, // 5: 0001x - {-1, 0}, {0, 0x11}, // 6: 0000 0x -}; - -static const plm_vlc_t PLM_VIDEO_MACROBLOCK_TYPE_B[] = { - {1 << 1, 0}, {2 << 1, 0}, // 0: x - {3 << 1, 0}, {4 << 1, 0}, // 1: 0x - {0, 0x0c}, {0, 0x0e}, // 2: 1x - {5 << 1, 0}, {6 << 1, 0}, // 3: 00x - {0, 0x04}, {0, 0x06}, // 4: 01x - {7 << 1, 0}, {8 << 1, 0}, // 5: 000x - {0, 0x08}, {0, 0x0a}, // 6: 001x - {9 << 1, 0}, {10 << 1, 0}, // 7: 0000x - {0, 0x1e}, {0, 0x01}, // 8: 0001x - {-1, 0}, {0, 0x11}, // 9: 0000 0x - {0, 0x16}, {0, 0x1a}, // 10: 0000 1x -}; - -static const plm_vlc_t PLM_VIDEO_CODE_BLOCK_PATTERN[] = { - {1 << 1, 0}, {2 << 1, 0}, // 0: x - {3 << 1, 0}, {4 << 1, 0}, // 1: 0x - {5 << 1, 0}, {6 << 1, 0}, // 2: 1x - {7 << 1, 0}, {8 << 1, 0}, // 3: 00x - {9 << 1, 0}, {10 << 1, 0}, // 4: 01x - {11 << 1, 0}, {12 << 1, 0}, // 5: 10x - {13 << 1, 0}, {0, 60}, // 6: 11x - {14 << 1, 0}, {15 << 1, 0}, // 7: 000x - {16 << 1, 0}, {17 << 1, 0}, // 8: 001x - {18 << 1, 0}, {19 << 1, 0}, // 9: 010x - {20 << 1, 0}, {21 << 1, 0}, // 10: 011x - {22 << 1, 0}, {23 << 1, 0}, // 11: 100x - {0, 32}, {0, 16}, // 12: 101x - {0, 8}, {0, 4}, // 13: 110x - {24 << 1, 0}, {25 << 1, 0}, // 14: 0000x - {26 << 1, 0}, {27 << 1, 0}, // 15: 0001x - {28 << 1, 0}, {29 << 1, 0}, // 16: 0010x - {30 << 1, 0}, {31 << 1, 0}, // 17: 0011x - {0, 62}, {0, 2}, // 18: 0100x - {0, 61}, {0, 1}, // 19: 0101x - {0, 56}, {0, 52}, // 20: 0110x - {0, 44}, {0, 28}, // 21: 0111x - {0, 40}, {0, 20}, // 22: 1000x - {0, 48}, {0, 12}, // 23: 1001x - {32 << 1, 0}, {33 << 1, 0}, // 24: 0000 0x - {34 << 1, 0}, {35 << 1, 0}, // 25: 0000 1x - {36 << 1, 0}, {37 << 1, 0}, // 26: 0001 0x - {38 << 1, 0}, {39 << 1, 0}, // 27: 0001 1x - {40 << 1, 0}, {41 << 1, 0}, // 28: 0010 0x - {42 << 1, 0}, {43 << 1, 0}, // 29: 0010 1x - {0, 63}, {0, 3}, // 30: 0011 0x - {0, 36}, {0, 24}, // 31: 0011 1x - {44 << 1, 0}, {45 << 1, 0}, // 32: 0000 00x - {46 << 1, 0}, {47 << 1, 0}, // 33: 0000 01x - {48 << 1, 0}, {49 << 1, 0}, // 34: 0000 10x - {50 << 1, 0}, {51 << 1, 0}, // 35: 0000 11x - {52 << 1, 0}, {53 << 1, 0}, // 36: 0001 00x - {54 << 1, 0}, {55 << 1, 0}, // 37: 0001 01x - {56 << 1, 0}, {57 << 1, 0}, // 38: 0001 10x - {58 << 1, 0}, {59 << 1, 0}, // 39: 0001 11x - {0, 34}, {0, 18}, // 40: 0010 00x - {0, 10}, {0, 6}, // 41: 0010 01x - {0, 33}, {0, 17}, // 42: 0010 10x - {0, 9}, {0, 5}, // 43: 0010 11x - {-1, 0}, {60 << 1, 0}, // 44: 0000 000x - {61 << 1, 0}, {62 << 1, 0}, // 45: 0000 001x - {0, 58}, {0, 54}, // 46: 0000 010x - {0, 46}, {0, 30}, // 47: 0000 011x - {0, 57}, {0, 53}, // 48: 0000 100x - {0, 45}, {0, 29}, // 49: 0000 101x - {0, 38}, {0, 26}, // 50: 0000 110x - {0, 37}, {0, 25}, // 51: 0000 111x - {0, 43}, {0, 23}, // 52: 0001 000x - {0, 51}, {0, 15}, // 53: 0001 001x - {0, 42}, {0, 22}, // 54: 0001 010x - {0, 50}, {0, 14}, // 55: 0001 011x - {0, 41}, {0, 21}, // 56: 0001 100x - {0, 49}, {0, 13}, // 57: 0001 101x - {0, 35}, {0, 19}, // 58: 0001 110x - {0, 11}, {0, 7}, // 59: 0001 111x - {0, 39}, {0, 27}, // 60: 0000 0001x - {0, 59}, {0, 55}, // 61: 0000 0010x - {0, 47}, {0, 31}, // 62: 0000 0011x -}; - -static const plm_vlc_t PLM_VIDEO_MOTION[] = { - {1 << 1, 0}, {0, 0}, // 0: x - {2 << 1, 0}, {3 << 1, 0}, // 1: 0x - {4 << 1, 0}, {5 << 1, 0}, // 2: 00x - {0, 1}, {0, -1}, // 3: 01x - {6 << 1, 0}, {7 << 1, 0}, // 4: 000x - {0, 2}, {0, -2}, // 5: 001x - {8 << 1, 0}, {9 << 1, 0}, // 6: 0000x - {0, 3}, {0, -3}, // 7: 0001x - {10 << 1, 0}, {11 << 1, 0}, // 8: 0000 0x - {12 << 1, 0}, {13 << 1, 0}, // 9: 0000 1x - {-1, 0}, {14 << 1, 0}, // 10: 0000 00x - {15 << 1, 0}, {16 << 1, 0}, // 11: 0000 01x - {17 << 1, 0}, {18 << 1, 0}, // 12: 0000 10x - {0, 4}, {0, -4}, // 13: 0000 11x - {-1, 0}, {19 << 1, 0}, // 14: 0000 001x - {20 << 1, 0}, {21 << 1, 0}, // 15: 0000 010x - {0, 7}, {0, -7}, // 16: 0000 011x - {0, 6}, {0, -6}, // 17: 0000 100x - {0, 5}, {0, -5}, // 18: 0000 101x - {22 << 1, 0}, {23 << 1, 0}, // 19: 0000 0011x - {24 << 1, 0}, {25 << 1, 0}, // 20: 0000 0100x - {26 << 1, 0}, {27 << 1, 0}, // 21: 0000 0101x - {28 << 1, 0}, {29 << 1, 0}, // 22: 0000 0011 0x - {30 << 1, 0}, {31 << 1, 0}, // 23: 0000 0011 1x - {32 << 1, 0}, {33 << 1, 0}, // 24: 0000 0100 0x - {0, 10}, {0, -10}, // 25: 0000 0100 1x - {0, 9}, {0, -9}, // 26: 0000 0101 0x - {0, 8}, {0, -8}, // 27: 0000 0101 1x - {0, 16}, {0, -16}, // 28: 0000 0011 00x - {0, 15}, {0, -15}, // 29: 0000 0011 01x - {0, 14}, {0, -14}, // 30: 0000 0011 10x - {0, 13}, {0, -13}, // 31: 0000 0011 11x - {0, 12}, {0, -12}, // 32: 0000 0100 00x - {0, 11}, {0, -11}, // 33: 0000 0100 01x -}; - -static const plm_vlc_t PLM_VIDEO_DCT_SIZE_LUMINANCE[] = { - {1 << 1, 0}, {2 << 1, 0}, // 0: x - {0, 1}, {0, 2}, // 1: 0x - {3 << 1, 0}, {4 << 1, 0}, // 2: 1x - {0, 0}, {0, 3}, // 3: 10x - {0, 4}, {5 << 1, 0}, // 4: 11x - {0, 5}, {6 << 1, 0}, // 5: 111x - {0, 6}, {7 << 1, 0}, // 6: 1111x - {0, 7}, {8 << 1, 0}, // 7: 1111 1x - {0, 8}, {-1, 0}, // 8: 1111 11x -}; - -static const plm_vlc_t PLM_VIDEO_DCT_SIZE_CHROMINANCE[] = { - {1 << 1, 0}, {2 << 1, 0}, // 0: x - {0, 0}, {0, 1}, // 1: 0x - {0, 2}, {3 << 1, 0}, // 2: 1x - {0, 3}, {4 << 1, 0}, // 3: 11x - {0, 4}, {5 << 1, 0}, // 4: 111x - {0, 5}, {6 << 1, 0}, // 5: 1111x - {0, 6}, {7 << 1, 0}, // 6: 1111 1x - {0, 7}, {8 << 1, 0}, // 7: 1111 11x - {0, 8}, {-1, 0}, // 8: 1111 111x -}; - -// dct_coeff bitmap: -// 0xff00 run -// 0x00ff level - -// Decoded values are unsigned. Sign bit follows in the stream. - -static const plm_vlc_uint_t PLM_VIDEO_DCT_COEFF[] = { - {1 << 1, 0}, {0, 0x0001}, // 0: x - {2 << 1, 0}, {3 << 1, 0}, // 1: 0x - {4 << 1, 0}, {5 << 1, 0}, // 2: 00x - {6 << 1, 0}, {0, 0x0101}, // 3: 01x - {7 << 1, 0}, {8 << 1, 0}, // 4: 000x - {9 << 1, 0}, {10 << 1, 0}, // 5: 001x - {0, 0x0002}, {0, 0x0201}, // 6: 010x - {11 << 1, 0}, {12 << 1, 0}, // 7: 0000x - {13 << 1, 0}, {14 << 1, 0}, // 8: 0001x - {15 << 1, 0}, {0, 0x0003}, // 9: 0010x - {0, 0x0401}, {0, 0x0301}, // 10: 0011x - {16 << 1, 0}, {0, 0xffff}, // 11: 0000 0x - {17 << 1, 0}, {18 << 1, 0}, // 12: 0000 1x - {0, 0x0701}, {0, 0x0601}, // 13: 0001 0x - {0, 0x0102}, {0, 0x0501}, // 14: 0001 1x - {19 << 1, 0}, {20 << 1, 0}, // 15: 0010 0x - {21 << 1, 0}, {22 << 1, 0}, // 16: 0000 00x - {0, 0x0202}, {0, 0x0901}, // 17: 0000 10x - {0, 0x0004}, {0, 0x0801}, // 18: 0000 11x - {23 << 1, 0}, {24 << 1, 0}, // 19: 0010 00x - {25 << 1, 0}, {26 << 1, 0}, // 20: 0010 01x - {27 << 1, 0}, {28 << 1, 0}, // 21: 0000 000x - {29 << 1, 0}, {30 << 1, 0}, // 22: 0000 001x - {0, 0x0d01}, {0, 0x0006}, // 23: 0010 000x - {0, 0x0c01}, {0, 0x0b01}, // 24: 0010 001x - {0, 0x0302}, {0, 0x0103}, // 25: 0010 010x - {0, 0x0005}, {0, 0x0a01}, // 26: 0010 011x - {31 << 1, 0}, {32 << 1, 0}, // 27: 0000 0000x - {33 << 1, 0}, {34 << 1, 0}, // 28: 0000 0001x - {35 << 1, 0}, {36 << 1, 0}, // 29: 0000 0010x - {37 << 1, 0}, {38 << 1, 0}, // 30: 0000 0011x - {39 << 1, 0}, {40 << 1, 0}, // 31: 0000 0000 0x - {41 << 1, 0}, {42 << 1, 0}, // 32: 0000 0000 1x - {43 << 1, 0}, {44 << 1, 0}, // 33: 0000 0001 0x - {45 << 1, 0}, {46 << 1, 0}, // 34: 0000 0001 1x - {0, 0x1001}, {0, 0x0502}, // 35: 0000 0010 0x - {0, 0x0007}, {0, 0x0203}, // 36: 0000 0010 1x - {0, 0x0104}, {0, 0x0f01}, // 37: 0000 0011 0x - {0, 0x0e01}, {0, 0x0402}, // 38: 0000 0011 1x - {47 << 1, 0}, {48 << 1, 0}, // 39: 0000 0000 00x - {49 << 1, 0}, {50 << 1, 0}, // 40: 0000 0000 01x - {51 << 1, 0}, {52 << 1, 0}, // 41: 0000 0000 10x - {53 << 1, 0}, {54 << 1, 0}, // 42: 0000 0000 11x - {55 << 1, 0}, {56 << 1, 0}, // 43: 0000 0001 00x - {57 << 1, 0}, {58 << 1, 0}, // 44: 0000 0001 01x - {59 << 1, 0}, {60 << 1, 0}, // 45: 0000 0001 10x - {61 << 1, 0}, {62 << 1, 0}, // 46: 0000 0001 11x - {-1, 0}, {63 << 1, 0}, // 47: 0000 0000 000x - {64 << 1, 0}, {65 << 1, 0}, // 48: 0000 0000 001x - {66 << 1, 0}, {67 << 1, 0}, // 49: 0000 0000 010x - {68 << 1, 0}, {69 << 1, 0}, // 50: 0000 0000 011x - {70 << 1, 0}, {71 << 1, 0}, // 51: 0000 0000 100x - {72 << 1, 0}, {73 << 1, 0}, // 52: 0000 0000 101x - {74 << 1, 0}, {75 << 1, 0}, // 53: 0000 0000 110x - {76 << 1, 0}, {77 << 1, 0}, // 54: 0000 0000 111x - {0, 0x000b}, {0, 0x0802}, // 55: 0000 0001 000x - {0, 0x0403}, {0, 0x000a}, // 56: 0000 0001 001x - {0, 0x0204}, {0, 0x0702}, // 57: 0000 0001 010x - {0, 0x1501}, {0, 0x1401}, // 58: 0000 0001 011x - {0, 0x0009}, {0, 0x1301}, // 59: 0000 0001 100x - {0, 0x1201}, {0, 0x0105}, // 60: 0000 0001 101x - {0, 0x0303}, {0, 0x0008}, // 61: 0000 0001 110x - {0, 0x0602}, {0, 0x1101}, // 62: 0000 0001 111x - {78 << 1, 0}, {79 << 1, 0}, // 63: 0000 0000 0001x - {80 << 1, 0}, {81 << 1, 0}, // 64: 0000 0000 0010x - {82 << 1, 0}, {83 << 1, 0}, // 65: 0000 0000 0011x - {84 << 1, 0}, {85 << 1, 0}, // 66: 0000 0000 0100x - {86 << 1, 0}, {87 << 1, 0}, // 67: 0000 0000 0101x - {88 << 1, 0}, {89 << 1, 0}, // 68: 0000 0000 0110x - {90 << 1, 0}, {91 << 1, 0}, // 69: 0000 0000 0111x - {0, 0x0a02}, {0, 0x0902}, // 70: 0000 0000 1000x - {0, 0x0503}, {0, 0x0304}, // 71: 0000 0000 1001x - {0, 0x0205}, {0, 0x0107}, // 72: 0000 0000 1010x - {0, 0x0106}, {0, 0x000f}, // 73: 0000 0000 1011x - {0, 0x000e}, {0, 0x000d}, // 74: 0000 0000 1100x - {0, 0x000c}, {0, 0x1a01}, // 75: 0000 0000 1101x - {0, 0x1901}, {0, 0x1801}, // 76: 0000 0000 1110x - {0, 0x1701}, {0, 0x1601}, // 77: 0000 0000 1111x - {92 << 1, 0}, {93 << 1, 0}, // 78: 0000 0000 0001 0x - {94 << 1, 0}, {95 << 1, 0}, // 79: 0000 0000 0001 1x - {96 << 1, 0}, {97 << 1, 0}, // 80: 0000 0000 0010 0x - {98 << 1, 0}, {99 << 1, 0}, // 81: 0000 0000 0010 1x - {100 << 1, 0}, {101 << 1, 0}, // 82: 0000 0000 0011 0x - {102 << 1, 0}, {103 << 1, 0}, // 83: 0000 0000 0011 1x - {0, 0x001f}, {0, 0x001e}, // 84: 0000 0000 0100 0x - {0, 0x001d}, {0, 0x001c}, // 85: 0000 0000 0100 1x - {0, 0x001b}, {0, 0x001a}, // 86: 0000 0000 0101 0x - {0, 0x0019}, {0, 0x0018}, // 87: 0000 0000 0101 1x - {0, 0x0017}, {0, 0x0016}, // 88: 0000 0000 0110 0x - {0, 0x0015}, {0, 0x0014}, // 89: 0000 0000 0110 1x - {0, 0x0013}, {0, 0x0012}, // 90: 0000 0000 0111 0x - {0, 0x0011}, {0, 0x0010}, // 91: 0000 0000 0111 1x - {104 << 1, 0}, {105 << 1, 0}, // 92: 0000 0000 0001 00x - {106 << 1, 0}, {107 << 1, 0}, // 93: 0000 0000 0001 01x - {108 << 1, 0}, {109 << 1, 0}, // 94: 0000 0000 0001 10x - {110 << 1, 0}, {111 << 1, 0}, // 95: 0000 0000 0001 11x - {0, 0x0028}, {0, 0x0027}, // 96: 0000 0000 0010 00x - {0, 0x0026}, {0, 0x0025}, // 97: 0000 0000 0010 01x - {0, 0x0024}, {0, 0x0023}, // 98: 0000 0000 0010 10x - {0, 0x0022}, {0, 0x0021}, // 99: 0000 0000 0010 11x - {0, 0x0020}, {0, 0x010e}, // 100: 0000 0000 0011 00x - {0, 0x010d}, {0, 0x010c}, // 101: 0000 0000 0011 01x - {0, 0x010b}, {0, 0x010a}, // 102: 0000 0000 0011 10x - {0, 0x0109}, {0, 0x0108}, // 103: 0000 0000 0011 11x - {0, 0x0112}, {0, 0x0111}, // 104: 0000 0000 0001 000x - {0, 0x0110}, {0, 0x010f}, // 105: 0000 0000 0001 001x - {0, 0x0603}, {0, 0x1002}, // 106: 0000 0000 0001 010x - {0, 0x0f02}, {0, 0x0e02}, // 107: 0000 0000 0001 011x - {0, 0x0d02}, {0, 0x0c02}, // 108: 0000 0000 0001 100x - {0, 0x0b02}, {0, 0x1f01}, // 109: 0000 0000 0001 101x - {0, 0x1e01}, {0, 0x1d01}, // 110: 0000 0000 0001 110x - {0, 0x1c01}, {0, 0x1b01}, // 111: 0000 0000 0001 111x -}; - -long plmpegdecode_latency_; - -static plm_vlc_t *PLM_VIDEO_MACROBLOCK_TYPE[4]; -static plm_vlc_t *PLM_VIDEO_DCT_SIZE[3]; - -#define plm_clamp(n) MIN(255, MAX(0, n)) - -void plm_video_destroy(plm_video_t *self) { - if (self->destroy_buffer_when_done) { - plm_buffer_destroy(self->buffer); - } - if (self->has_sequence_header) { - free(self->frames_data); - } - free(self); -} - -double plm_video_get_pixel_aspect_ratio(plm_video_t *self) { - return self->pixel_aspect_ratio; -} - -double plm_video_get_framerate(plm_video_t *self) { - return self->framerate; -} - -int plm_video_get_width(plm_video_t *self) { - return self->width; -} - -int plm_video_get_height(plm_video_t *self) { - return self->height; -} - -void plm_video_set_no_delay(plm_video_t *self, int no_delay) { - self->assume_no_b_frames = no_delay; -} - -double plm_video_get_time(plm_video_t *self) { - return self->time; -} - -void plm_video_rewind(plm_video_t *self) { - plm_buffer_rewind(self->buffer); - self->time = 0; - self->frames_decoded = 0; - self->has_reference_frame = false; -} - -void plm_video_init_frame(plm_video_t *self, plm_frame_t *frame, - uint8_t *base) { - size_t plane_size = self->luma_width * self->luma_height; - frame->width = self->width; - frame->height = self->height; - frame->y.width = self->luma_width; - frame->y.height = self->luma_height; - frame->y.data = base; - frame->cr.width = self->chroma_width; - frame->cr.height = self->chroma_height; - frame->cr.data = base + plane_size; - frame->cb.width = self->chroma_width; - frame->cb.height = self->chroma_height; - frame->cb.data = base + plane_size * 2; -} - -void plm_video_decode_sequence_header(plm_video_t *self) { - int previous_width = self->width; - int previous_height = self->height; - - self->width = plm_buffer_read(self->buffer, 12); - self->height = plm_buffer_read(self->buffer, 12); - - int pixel_aspect_ratio_code; - pixel_aspect_ratio_code = plm_buffer_read(self->buffer, 4); - pixel_aspect_ratio_code -= 1; - pixel_aspect_ratio_code = MAX(pixel_aspect_ratio_code, 0); - pixel_aspect_ratio_code = - MIN(pixel_aspect_ratio_code, ARRAYLEN(PLM_VIDEO_PIXEL_ASPECT_RATIO) - 1); - self->pixel_aspect_ratio = - PLM_VIDEO_PIXEL_ASPECT_RATIO[pixel_aspect_ratio_code]; - - int framerate_code; - framerate_code = plm_buffer_read(self->buffer, 4); - framerate_code -= 1; - framerate_code = MAX(framerate_code, 0); - framerate_code = MIN(framerate_code, ARRAYLEN(PLM_VIDEO_PICTURE_RATE) - 1); - self->framerate = PLM_VIDEO_PICTURE_RATE[framerate_code]; - - // skip bitRate, marker, bufferSize and constrained bit - plm_buffer_skip(self->buffer, 18 + 1 + 10 + 1); - - if (plm_buffer_read(self->buffer, 1)) { // load custom intra quant matrix? - for (int i = 0; i < 64; i++) { - int idx = PLM_VIDEO_ZIG_ZAG[i]; - self->intra_quant_matrix[idx] = plm_buffer_read(self->buffer, 8); - } - } else { - memcpy(self->intra_quant_matrix, PLM_VIDEO_INTRAQUANT_MATRIX, 64); - } - - if (plm_buffer_read(self->buffer, - 1)) { // load custom non intra quant matrix? - for (int i = 0; i < 64; i++) { - int idx = PLM_VIDEO_ZIG_ZAG[i]; - self->non_intra_quant_matrix[idx] = plm_buffer_read(self->buffer, 8); - } - } else { - memcpy(self->non_intra_quant_matrix, PLM_VIDEO_NONINTRAQUANT_MATRIX, 64); - } - - if (self->has_sequence_header) { - if (self->width == previous_width && self->height == previous_height) { - // We already had a sequence header with the same width/height; - // nothing else to do here. - return; - } - - // We had a sequence header but with different dimensions; - // delete the previous planes and allocate new. - free(self->frames_data); - } - - self->mb_width = (self->width + 15) >> 4; - self->mb_height = (self->height + 15) >> 4; - self->mb_size = self->mb_width * self->mb_height; - self->luma_width = self->mb_width << 4; - self->luma_height = self->mb_height << 4; - self->chroma_width = self->mb_width << 3; - self->chroma_height = self->mb_height << 3; - - size_t plane_size = self->luma_width * self->luma_height; - self->frames_data = memalign(64, plane_size * 9); - plm_video_init_frame(self, &self->frame_current, - self->frames_data + plane_size * 0); - plm_video_init_frame(self, &self->frame_forward, - self->frames_data + plane_size * 3); - plm_video_init_frame(self, &self->frame_backward, - self->frames_data + plane_size * 6); - - self->has_sequence_header = true; - - INFOF("%s:\n" - "\t%-20s = %15d;\n" - "\t%-20s = %15d;\n" - "\t%-20s = %15f;\n" - "\t%-20s = %15f;\n" - "\t%-20s = %15d;\n" - "\t%-20s = %15d;\n" - "\t%-20s = %15d;\n" - "\t%-20s = %15d;\n" - "\t%-20s = %15d;\n" - "\t%-20s = %15d;\n" - "\t%-20s = %15d;", - "New MPEG Sequence", "width", self->width, "height", self->height, - "framerate", self->framerate, "pixel_aspect_ratio", - self->pixel_aspect_ratio, "mb_size", self->mb_size, "mb_width", - self->mb_width, "mb_height", self->mb_height, "luma_width", - self->luma_width, "luma_height", self->luma_height, "chroma_width", - self->chroma_width, "chroma_height", self->chroma_height); -} - -static void plm_video_copy_macroblock(plm_video_t *self, int motion_h, - int motion_v, plm_frame_t *d) { - plm_frame_t *s = &self->frame_current; - plm_video_process_macroblock_16(self, s->y.data, d->y.data, motion_h, - motion_v, false); - plm_video_process_macroblock_8(self, s->cr.data, d->cr.data, motion_h / 2, - motion_v / 2, false); - plm_video_process_macroblock_8(self, s->cb.data, d->cb.data, motion_h / 2, - motion_v / 2, false); -} - -static void plm_video_interpolate_macroblock(plm_video_t *self, int motion_h, - int motion_v, plm_frame_t *d) { - plm_frame_t *s = &self->frame_current; - plm_video_process_macroblock_16(self, s->y.data, d->y.data, motion_h, - motion_v, true); - plm_video_process_macroblock_8(self, s->cr.data, d->cr.data, motion_h / 2, - motion_v / 2, true); - plm_video_process_macroblock_8(self, s->cb.data, d->cb.data, motion_h / 2, - motion_v / 2, true); -} - -static int plm_video_decode_motion_vector(plm_video_t *self, int r_size, - int motion) { - int fscale = 1u << r_size; - int m_code = plm_buffer_read_vlc(self->buffer, PLM_VIDEO_MOTION); - int r = 0; - int d; - if ((m_code != 0) && (fscale != 1)) { - r = plm_buffer_read(self->buffer, r_size); - d = ((abs(m_code) - 1) << r_size) + r + 1; - if (m_code < 0) { - d = -d; - } - } else { - d = m_code; - } - motion += d; - if (motion > (fscale << 4) - 1) { - motion -= fscale << 5; - } else if (motion < (int)(((unsigned)-fscale) << 4)) { - motion += fscale << 5; - } - return motion; -} - -static void plm_video_decode_motion_vectors(plm_video_t *self) { - // Forward - if (self->motion_forward.is_set) { - int r_size = self->motion_forward.r_size; - self->motion_forward.h = - plm_video_decode_motion_vector(self, r_size, self->motion_forward.h); - self->motion_forward.v = - plm_video_decode_motion_vector(self, r_size, self->motion_forward.v); - } else if (self->picture_type == PLM_VIDEO_PICTURE_TYPE_PREDICTIVE) { - // No motion information in P-picture, reset vectors - self->motion_forward.h = 0; - self->motion_forward.v = 0; - } - if (self->motion_backward.is_set) { - int r_size = self->motion_backward.r_size; - self->motion_backward.h = - plm_video_decode_motion_vector(self, r_size, self->motion_backward.h); - self->motion_backward.v = - plm_video_decode_motion_vector(self, r_size, self->motion_backward.v); - } -} - -static void plm_video_predict_macroblock(plm_video_t *self) { - int fw_h = self->motion_forward.h; - int fw_v = self->motion_forward.v; - if (self->motion_forward.full_px) { - fw_h <<= 1; - fw_v <<= 1; - } - if (self->picture_type == PLM_VIDEO_PICTURE_TYPE_B) { - int bw_h = self->motion_backward.h; - int bw_v = self->motion_backward.v; - if (self->motion_backward.full_px) { - bw_h <<= 1; - bw_v <<= 1; - } - if (self->motion_forward.is_set) { - plm_video_copy_macroblock(self, fw_h, fw_v, &self->frame_forward); - if (self->motion_backward.is_set) { - plm_video_interpolate_macroblock(self, bw_h, bw_v, - &self->frame_backward); - } - } else { - plm_video_copy_macroblock(self, bw_h, bw_v, &self->frame_backward); - } - } else { - plm_video_copy_macroblock(self, fw_h, fw_v, &self->frame_forward); - } -} - -static void plm_video_decode_block(plm_video_t *self, int block) { - int n = 0; - uint8_t *quant_matrix; - - // Decode DC coefficient of intra-coded blocks - if (self->macroblock_intra) { - int predictor; - int dct_size; - - // DC prediction - int plane_index = block > 3 ? block - 3 : 0; - predictor = self->dc_predictor[plane_index]; - dct_size = - plm_buffer_read_vlc(self->buffer, PLM_VIDEO_DCT_SIZE[plane_index]); - - // Read DC coeff - if (dct_size > 0) { - int differential = plm_buffer_read(self->buffer, dct_size); - if ((differential & (1 << (dct_size - 1))) != 0) { - self->block_data[0] = predictor + differential; - } else { - self->block_data[0] = - predictor + ((-1u << dct_size) | (differential + 1)); - } - } else { - self->block_data[0] = predictor; - } - - // Save predictor value - self->dc_predictor[plane_index] = self->block_data[0]; - - // Dequantize + premultiply - self->block_data[0] <<= (3 + 5); - - quant_matrix = self->intra_quant_matrix; - n = 1; - } else { - quant_matrix = self->non_intra_quant_matrix; - } - - // Decode AC coefficients (+DC for non-intra) - int level = 0; - while (true) { - int run = 0; - uint16_t coeff = - plm_buffer_read_vlc_uint(self->buffer, PLM_VIDEO_DCT_COEFF); - - if ((coeff == 0x0001) && (n > 0) && - (plm_buffer_read(self->buffer, 1) == 0)) { - // end_of_block - break; - } - if (coeff == 0xffff) { - // escape - run = plm_buffer_read(self->buffer, 6); - level = plm_buffer_read(self->buffer, 8); - if (level == 0) { - level = plm_buffer_read(self->buffer, 8); - } else if (level == 128) { - level = plm_buffer_read(self->buffer, 8) - 256; - } else if (level > 128) { - level = level - 256; - } - } else { - run = coeff >> 8; - level = coeff & 0xff; - if (plm_buffer_read(self->buffer, 1)) { - level = -level; - } - } - - n += run; - if (n < 0 || n >= 64) { - return; // invalid - } - - int de_zig_zagged = PLM_VIDEO_ZIG_ZAG[n]; - n++; - - // Dequantize, oddify, clip - level = (unsigned)level << 1; - if (!self->macroblock_intra) { - level += (level < 0 ? -1 : 1); - } - level = (level * self->quantizer_scale * quant_matrix[de_zig_zagged]) >> 4; - if ((level & 1) == 0) { - level -= level > 0 ? 1 : -1; - } - if (level > 2047) { - level = 2047; - } else if (level < -2048) { - level = -2048; - } - - // Save premultiplied coefficient - self->block_data[de_zig_zagged] = - level * PLM_VIDEO_PREMULTIPLIER_MATRIX[de_zig_zagged]; - } - - // Move block to its place - uint8_t *d; - int dw; - int di; - - if (block < 4) { - d = self->frame_current.y.data; - dw = self->luma_width; - di = (self->mb_row * self->luma_width + self->mb_col) << 4; - if ((block & 1) != 0) { - di += 8; - } - if ((block & 2) != 0) { - di += self->luma_width << 3; - } - } else { - d = (block == 4) ? self->frame_current.cb.data - : self->frame_current.cr.data; - dw = self->chroma_width; - di = ((self->mb_row * self->luma_width) << 2) + (self->mb_col << 3); - } - - int *s = self->block_data; - int si = 0; - if (self->macroblock_intra) { - // Overwrite (no prediction) - if (n == 1) { - int clamped = plm_clamp((s[0] + 128) >> 8); - PLM_BLOCK_SET(d, di, dw, si, 8, 8, clamped); - s[0] = 0; - } else { - plm_video_idct(s); - PLM_BLOCK_SET(d, di, dw, si, 8, 8, plm_clamp(s[si])); - memset(self->block_data, 0, sizeof(self->block_data)); - } - } else { - // Add data to the predicted macroblock - if (n == 1) { - int value = (s[0] + 128) >> 8; - PLM_BLOCK_SET(d, di, dw, si, 8, 8, plm_clamp(d[di] + value)); - s[0] = 0; - } else { - plm_video_idct(s); - PLM_BLOCK_SET(d, di, dw, si, 8, 8, plm_clamp(d[di] + s[si])); - memset(self->block_data, 0, sizeof(self->block_data)); - } - } -} - -static void plm_video_decode_macroblock(plm_video_t *self) { - // Decode self->macroblock_address_increment - int increment = 0; - int t = - plm_buffer_read_vlc(self->buffer, PLM_VIDEO_MACROBLOCK_ADDRESS_INCREMENT); - - while (t == 34) { - // macroblock_stuffing - t = plm_buffer_read_vlc(self->buffer, - PLM_VIDEO_MACROBLOCK_ADDRESS_INCREMENT); - } - while (t == 35) { - // macroblock_escape - increment += 33; - t = plm_buffer_read_vlc(self->buffer, - PLM_VIDEO_MACROBLOCK_ADDRESS_INCREMENT); - } - increment += t; - - // Process any skipped macroblocks - if (self->slice_begin) { - // The first self->macroblock_address_increment of each slice is relative - // to beginning of the preverious row, not the preverious macroblock - self->slice_begin = false; - self->macroblock_address += increment; - } else { - if (self->macroblock_address + increment >= self->mb_size) { - return; // invalid - } - if (increment > 1) { - // Skipped macroblocks reset DC predictors - self->dc_predictor[0] = 128; - self->dc_predictor[1] = 128; - self->dc_predictor[2] = 128; - - // Skipped macroblocks in P-pictures reset motion vectors - if (self->picture_type == PLM_VIDEO_PICTURE_TYPE_PREDICTIVE) { - self->motion_forward.h = 0; - self->motion_forward.v = 0; - } - } - - // Predict skipped macroblocks - while (increment > 1) { - self->macroblock_address++; - self->mb_row = self->macroblock_address / self->mb_width; - self->mb_col = self->macroblock_address % self->mb_width; - - plm_video_predict_macroblock(self); - increment--; - } - self->macroblock_address++; - } - - self->mb_row = self->macroblock_address / self->mb_width; - self->mb_col = self->macroblock_address % self->mb_width; - - if (self->mb_col >= self->mb_width || self->mb_row >= self->mb_height) { - return; // corrupt stream; - } - - // Process the current macroblock - // static const s16 *mbTable = MACROBLOCK_TYPE[self->picture_type]; - // macroblock_type = read_huffman(self->bits, mbTable); - - const plm_vlc_t *table = PLM_VIDEO_MACROBLOCK_TYPE[self->picture_type]; - self->macroblock_type = plm_buffer_read_vlc(self->buffer, table); - - self->macroblock_intra = (self->macroblock_type & 0x01); - self->motion_forward.is_set = (self->macroblock_type & 0x08); - self->motion_backward.is_set = (self->macroblock_type & 0x04); - - // Quantizer scale - if ((self->macroblock_type & 0x10) != 0) { - self->quantizer_scale = plm_buffer_read(self->buffer, 5); - } - - if (self->macroblock_intra) { - // Intra-coded macroblocks reset motion vectors - self->motion_backward.h = self->motion_forward.h = 0; - self->motion_backward.v = self->motion_forward.v = 0; - } else { - // Non-intra macroblocks reset DC predictors - self->dc_predictor[0] = 128; - self->dc_predictor[1] = 128; - self->dc_predictor[2] = 128; - - plm_video_decode_motion_vectors(self); - plm_video_predict_macroblock(self); - } - - // Decode blocks - int cbp = - ((self->macroblock_type & 0x02) != 0) - ? plm_buffer_read_vlc(self->buffer, PLM_VIDEO_CODE_BLOCK_PATTERN) - : (self->macroblock_intra ? 0x3f : 0); - - for (int block = 0, mask = 0x20; block < 6; block++) { - if ((cbp & mask) != 0) { - plm_video_decode_block(self, block); - } - mask >>= 1; - } -} - -static void plm_video_decode_slice(plm_video_t *self, int slice) { - self->slice_begin = true; - self->macroblock_address = (slice - 1) * self->mb_width - 1; - // Reset motion vectors and DC predictors - self->motion_backward.h = self->motion_forward.h = 0; - self->motion_backward.v = self->motion_forward.v = 0; - self->dc_predictor[0] = 128; - self->dc_predictor[1] = 128; - self->dc_predictor[2] = 128; - self->quantizer_scale = plm_buffer_read(self->buffer, 5); - // Skip extra - while (plm_buffer_read(self->buffer, 1)) { - plm_buffer_skip(self->buffer, 8); - } - do { - plm_video_decode_macroblock(self); - } while (self->macroblock_address < self->mb_size - 1 && - plm_buffer_no_start_code(self->buffer)); -} - -static void plm_video_decode_picture(plm_video_t *self) { - plm_buffer_skip(self->buffer, 10); // skip temporalReference - self->picture_type = plm_buffer_read(self->buffer, 3); - plm_buffer_skip(self->buffer, 16); // skip vbv_delay - - // D frames or unknown coding type - if (self->picture_type <= 0 || - self->picture_type > PLM_VIDEO_PICTURE_TYPE_B) { - return; - } - - // forward full_px, f_code - if (self->picture_type == PLM_VIDEO_PICTURE_TYPE_PREDICTIVE || - self->picture_type == PLM_VIDEO_PICTURE_TYPE_B) { - self->motion_forward.full_px = plm_buffer_read(self->buffer, 1); - int f_code = plm_buffer_read(self->buffer, 3); - if (f_code == 0) { - // Ignore picture with zero f_code - return; - } - self->motion_forward.r_size = f_code - 1; - } - - // backward full_px, f_code - if (self->picture_type == PLM_VIDEO_PICTURE_TYPE_B) { - self->motion_backward.full_px = plm_buffer_read(self->buffer, 1); - int f_code = plm_buffer_read(self->buffer, 3); - if (f_code == 0) { - // Ignore picture with zero f_code - return; - } - self->motion_backward.r_size = f_code - 1; - } - - plm_frame_t frame_temp = self->frame_forward; - - if (self->picture_type == PLM_VIDEO_PICTURE_TYPE_INTRA || - self->picture_type == PLM_VIDEO_PICTURE_TYPE_PREDICTIVE) { - self->frame_forward = self->frame_backward; - } - - // Skip extensions, user data - do { - self->start_code = plm_buffer_next_start_code(self->buffer); - } while (self->start_code == PLM_START_EXTENSION || - self->start_code == PLM_START_USER_DATA); - - while (self->start_code >= PLM_START_SLICE_FIRST && - self->start_code <= PLM_START_SLICE_LAST) { - plm_video_decode_slice(self, self->start_code & 0x000000FF); - if (self->macroblock_address == self->mb_size - 1) { - break; - } - self->start_code = plm_buffer_next_start_code(self->buffer); - } - - // If this is a reference picutre rotate the prediction pointers - if (self->picture_type == PLM_VIDEO_PICTURE_TYPE_INTRA || - self->picture_type == PLM_VIDEO_PICTURE_TYPE_PREDICTIVE) { - self->frame_backward = self->frame_current; - self->frame_current = frame_temp; - } -} - -static plm_frame_t *plm_video_decode_impl(plm_video_t *self) { - plm_frame_t *frame = NULL; - if (!self->has_sequence_header) { - self->start_code = - plm_buffer_find_start_code(self->buffer, PLM_START_SEQUENCE); - if (self->start_code == -1) { - return NULL; - } - plm_video_decode_sequence_header(self); - } - do { - if (self->start_code != PLM_START_PICTURE) { - self->start_code = - plm_buffer_find_start_code(self->buffer, PLM_START_PICTURE); - } - if (self->start_code == -1) { - return NULL; - } - plm_video_decode_picture(self); - if (self->assume_no_b_frames) { - frame = &self->frame_backward; - } else if (self->picture_type == PLM_VIDEO_PICTURE_TYPE_B) { - frame = &self->frame_current; - } else if (self->has_reference_frame) { - frame = &self->frame_forward; - } else { - self->has_reference_frame = true; - } - } while (!frame); - frame->time = self->time; - self->frames_decoded++; - self->time = (double)self->frames_decoded / self->framerate; - return frame; -} - -plm_frame_t *plm_video_decode(plm_video_t *self) { - plm_frame_t *res; - struct timespec tsc; - INFOF("plm_video_decode"); - tsc = timespec_real(); - res = plm_video_decode_impl(self); - plmpegdecode_latency_ = timespec_tomicros(timespec_sub(timespec_real(), tsc)); - return res; -} - -plm_video_t *plm_video_create_with_buffer(plm_buffer_t *buffer, - int destroy_when_done) { - plm_video_t *self = (plm_video_t *)memalign(64, sizeof(plm_video_t)); - memset(self, 0, sizeof(plm_video_t)); - self->buffer = buffer; - self->destroy_buffer_when_done = destroy_when_done; - self->start_code = - plm_buffer_find_start_code(self->buffer, PLM_START_SEQUENCE); - if (self->start_code != -1) { - plm_video_decode_sequence_header(self); - } - return self; -} - -__attribute__((__constructor__)) static textstartup void plm_video_init(void) { - PLM_VIDEO_MACROBLOCK_TYPE[0] = NULL; - PLM_VIDEO_MACROBLOCK_TYPE[1] = (void *)PLM_VIDEO_MACROBLOCK_TYPE_INTRA; - PLM_VIDEO_MACROBLOCK_TYPE[2] = (void *)PLM_VIDEO_MACROBLOCK_TYPE_PREDICTIVE; - PLM_VIDEO_MACROBLOCK_TYPE[3] = (void *)PLM_VIDEO_MACROBLOCK_TYPE_B; - PLM_VIDEO_DCT_SIZE[0] = (void *)PLM_VIDEO_DCT_SIZE_LUMINANCE; - PLM_VIDEO_DCT_SIZE[1] = (void *)PLM_VIDEO_DCT_SIZE_CHROMINANCE; - PLM_VIDEO_DCT_SIZE[2] = (void *)PLM_VIDEO_DCT_SIZE_CHROMINANCE; -} diff --git a/dsp/mpeg/notice.c b/dsp/mpeg/pl_mpeg.c similarity index 59% rename from dsp/mpeg/notice.c rename to dsp/mpeg/pl_mpeg.c index 264a7549b..e2f4a75d9 100644 --- a/dsp/mpeg/notice.c +++ b/dsp/mpeg/pl_mpeg.c @@ -2,3 +2,8 @@ __notice(pl_mpeg_notice, "\ PL_MPEG (MIT License)\n\ Copyright(c) 2019 Dominic Szablewski\n\ https://phoboslab.org"); + +long plmpegdecode_latency_; + +#define PL_MPEG_IMPLEMENTATION +#include "pl_mpeg.h" diff --git a/dsp/mpeg/pl_mpeg.h b/dsp/mpeg/pl_mpeg.h new file mode 100755 index 000000000..169967d20 --- /dev/null +++ b/dsp/mpeg/pl_mpeg.h @@ -0,0 +1,4379 @@ +/* +PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer + +Dominic Szablewski - https://phoboslab.org + + +-- LICENSE: The MIT License(MIT) + +Copyright(c) 2019 Dominic Szablewski + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files(the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions : +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + + + +-- Synopsis + +// Define `PL_MPEG_IMPLEMENTATION` in *one* C/C++ file before including this +// library to create the implementation. + +#define PL_MPEG_IMPLEMENTATION +#𝑖𝑛𝑐𝑙𝑢𝑑𝑒 "plmpeg.h" + +// This function gets called for each decoded video frame +void my_video_callback(plm_t *plm, plm_frame_t *frame, void *user) { + // Do something with frame->y.data, frame->cr.data, frame->cb.data +} + +// This function gets called for each decoded audio frame +void my_audio_callback(plm_t *plm, plm_samples_t *frame, void *user) { + // Do something with samples->interleaved +} + +// Load a .mpg (MPEG Program Stream) file +plm_t *plm = plm_create_with_filename("some-file.mpg"); + +// Install the video & audio decode callbacks +plm_set_video_decode_callback(plm, my_video_callback, my_data); +plm_set_audio_decode_callback(plm, my_audio_callback, my_data); + + +// Decode +do { + plm_decode(plm, time_since_last_call); +} while (!plm_has_ended(plm)); + +// All done +plm_destroy(plm); + + + +-- Documentation + +This library provides several interfaces to load, demux and decode MPEG video +and audio data. A high-level API combines the demuxer, video & audio decoders +in an easy to use wrapper. + +Lower-level APIs for accessing the demuxer, video decoder and audio decoder, +as well as providing different data sources are also available. + +Interfaces are written in an object oriented style, meaning you create object +instances via various different constructor functions (plm_*create()), +do some work on them and later dispose them via plm_*destroy(). + +plm_* ......... the high-level interface, combining demuxer and decoders +plm_buffer_* .. the data source used by all interfaces +plm_demux_* ... the MPEG-PS demuxer +plm_video_* ... the MPEG1 Video ("mpeg1") decoder +plm_audio_* ... the MPEG1 Audio Layer II ("mp2") decoder + + +With the high-level interface you have two options to decode video & audio: + + 1. Use plm_decode() and just hand over the delta time since the last call. + It will decode everything needed and call your callbacks (specified through + plm_set_{video|audio}_decode_callback()) any number of times. + + 2. Use plm_decode_video() and plm_decode_audio() to decode exactly one + frame of video or audio data at a time. How you handle the synchronization + of both streams is up to you. + +If you only want to decode video *or* audio through these functions, you should +disable the other stream (plm_set_{video|audio}_enabled(FALSE)) + +Video data is decoded into a struct with all 3 planes (Y, Cr, Cb) stored in +separate buffers. You can either convert this to RGB on the CPU (slow) via the +plm_frame_to_rgb() function or do it on the GPU with the following matrix: + +mat4 bt601 = mat4( + 1.16438, 0.00000, 1.59603, -0.87079, + 1.16438, -0.39176, -0.81297, 0.52959, + 1.16438, 2.01723, 0.00000, -1.08139, + 0, 0, 0, 1 +); +gl_FragColor = vec4(y, cb, cr, 1.0) * bt601; + +Audio data is decoded into a struct with either one single float array with the +samples for the left and right channel interleaved, or if the +PLM_AUDIO_SEPARATE_CHANNELS is defined *before* including this library, into +two separate float arrays - one for each channel. + + +Data can be supplied to the high level interface, the demuxer and the decoders +in three different ways: + + 1. Using plm_create_from_filename() or with a file handle with + plm_create_from_file(). + + 2. Using plm_create_with_memory() and supplying a pointer to memory that + contains the whole file. + + 3. Using plm_create_with_buffer(), supplying your own plm_buffer_t instance and + periodically writing to this buffer. + +When using your own plm_buffer_t instance, you can fill this buffer using +plm_buffer_write(). You can either monitor plm_buffer_get_remaining() and push +data when appropriate, or install a callback on the buffer with +plm_buffer_set_load_callback() that gets called whenever the buffer needs more +data. + +A buffer created with plm_buffer_create_with_capacity() is treated as a ring +buffer, meaning that data that has already been read, will be discarded. In +contrast, a buffer created with plm_buffer_create_for_appending() will keep all +data written to it in memory. This enables seeking in the already loaded data. + + +There should be no need to use the lower level plm_demux_*, plm_video_* and +plm_audio_* functions, if all you want to do is read/decode an MPEG-PS file. +However, if you get raw mpeg1video data or raw mp2 audio data from a different +source, these functions can be used to decode the raw data directly. Similarly, +if you only want to analyze an MPEG-PS file or extract raw video or audio +packets from it, you can use the plm_demux_* functions. + + +This library uses malloc(), realloc() and free() to manage memory. Typically +all allocation happens up-front when creating the interface. However, the +default buffer size may be too small for certain inputs. In these cases plmpeg +will realloc() the buffer with a larger size whenever needed. You can configure +the default buffer size by defining PLM_BUFFER_DEFAULT_SIZE *before* +including this library. + +You can also define PLM_MALLOC, PLM_REALLOC and PLM_FREE to provide your own +memory management functions. + + +See below for detailed the API documentation. + +*/ + + +#ifndef PL_MPEG_H +#define PL_MPEG_H + +#include +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + +extern long plmpegdecode_latency_; // [jart] + +// ----------------------------------------------------------------------------- +// Public Data Types + + +// Object types for the various interfaces + +typedef struct plm_t plm_t; +typedef struct plm_buffer_t plm_buffer_t; +typedef struct plm_demux_t plm_demux_t; +typedef struct plm_video_t plm_video_t; +typedef struct plm_audio_t plm_audio_t; + + +// Demuxed MPEG PS packet +// The type maps directly to the various MPEG-PES start codes. PTS is the +// presentation time stamp of the packet in seconds. Note that not all packets +// have a PTS value, indicated by PLM_PACKET_INVALID_TS. + +#define PLM_PACKET_INVALID_TS -1 + +typedef struct { + int type; + double pts; + size_t length; + uint8_t *data; +} plm_packet_t; + + +// Decoded Video Plane +// The byte length of the data is width * height. Note that different planes +// have different sizes: the Luma plane (Y) is double the size of each of +// the two Chroma planes (Cr, Cb) - i.e. 4 times the byte length. +// Also note that the size of the plane does *not* denote the size of the +// displayed frame. The sizes of planes are always rounded up to the nearest +// macroblock (16px). + +typedef struct { + unsigned int width; + unsigned int height; + uint8_t *data; +} plm_plane_t; + + +// Decoded Video Frame +// width and height denote the desired display size of the frame. This may be +// different from the internal size of the 3 planes. + +typedef struct { + double time; + unsigned int width; + unsigned int height; + plm_plane_t y; + plm_plane_t cr; + plm_plane_t cb; +} plm_frame_t; + + +// Callback function type for decoded video frames used by the high-level +// plm_* interface + +typedef void(*plm_video_decode_callback) + (plm_t *self, plm_frame_t *frame, void *user); + + +// Decoded Audio Samples +// Samples are stored as normalized (-1, 1) float either interleaved, or if +// PLM_AUDIO_SEPARATE_CHANNELS is defined, in two separate arrays. +// The `count` is always PLM_AUDIO_SAMPLES_PER_FRAME and just there for +// convenience. + +#define PLM_AUDIO_SAMPLES_PER_FRAME 1152 + +typedef struct { + double time; + unsigned int count; + #ifdef PLM_AUDIO_SEPARATE_CHANNELS + float left[PLM_AUDIO_SAMPLES_PER_FRAME]; + float right[PLM_AUDIO_SAMPLES_PER_FRAME]; + #else + float interleaved[PLM_AUDIO_SAMPLES_PER_FRAME * 2]; + #endif +} plm_samples_t; + + +// Callback function type for decoded audio samples used by the high-level +// plm_* interface + +typedef void(*plm_audio_decode_callback) + (plm_t *self, plm_samples_t *samples, void *user); + + +// Callback function for plm_buffer when it needs more data + +typedef void(*plm_buffer_load_callback)(plm_buffer_t *self, void *user); + + + +// ----------------------------------------------------------------------------- +// plm_* public API +// High-Level API for loading/demuxing/decoding MPEG-PS data + + +// Create a plmpeg instance with a filename. Returns NULL if the file could not +// be opened. + +plm_t *plm_create_with_filename(const char *filename); + + +// Create a plmpeg instance with a file handle. Pass TRUE to close_when_done to +// let plmpeg call fclose() on the handle when plm_destroy() is called. + +plm_t *plm_create_with_file(FILE *fh, int close_when_done); + + +// Create a plmpeg instance with a pointer to memory as source. This assumes the +// whole file is in memory. The memory is not copied. Pass TRUE to +// free_when_done to let plmpeg call free() on the pointer when plm_destroy() +// is called. + +plm_t *plm_create_with_memory(uint8_t *bytes, size_t length, int free_when_done); + + +// Create a plmpeg instance with a plm_buffer as source. Pass TRUE to +// destroy_when_done to let plmpeg call plm_buffer_destroy() on the buffer when +// plm_destroy() is called. + +plm_t *plm_create_with_buffer(plm_buffer_t *buffer, int destroy_when_done); + + +// Destroy a plmpeg instance and free all data. + +void plm_destroy(plm_t *self); + + +// Get whether we have headers on all available streams and we can report the +// number of video/audio streams, video dimensions, framerate and audio +// samplerate. +// This returns FALSE if the file is not an MPEG-PS file or - when not using a +// file as source - when not enough data is available yet. + +int plm_has_headers(plm_t *self); + + +// Probe the MPEG-PS data to find the actual number of video and audio streams +// within the buffer. For certain files (e.g. VideoCD) this can be more accurate +// than just reading the number of streams from the headers. +// This should only be used when the underlying plm_buffer is seekable, i.e. for +// files, fixed memory buffers or _for_appending buffers. If used with dynamic +// memory buffers it will skip decoding the probesize! +// The necessary probesize is dependent on the files you expect to read. Usually +// a few hundred KB should be enough to find all streams. +// Use plm_get_num_{audio|video}_streams() afterwards to get the number of +// streams in the file. +// Returns TRUE if any streams were found within the probesize. + +int plm_probe(plm_t *self, size_t probesize); + + +// Get or set whether video decoding is enabled. Default TRUE. + +int plm_get_video_enabled(plm_t *self); +void plm_set_video_enabled(plm_t *self, int enabled); + + +// Get the number of video streams (0--1) reported in the system header. + +int plm_get_num_video_streams(plm_t *self); + + +// Get the display width/height of the video stream. + +int plm_get_width(plm_t *self); +int plm_get_height(plm_t *self); +double plm_get_pixel_aspect_ratio(plm_t *self); // [jart] + + +// Get the framerate of the video stream in frames per second. + +double plm_get_framerate(plm_t *self); + + +// Get or set whether audio decoding is enabled. Default TRUE. + +int plm_get_audio_enabled(plm_t *self); +void plm_set_audio_enabled(plm_t *self, int enabled); + + +// Get the number of audio streams (0--4) reported in the system header. + +int plm_get_num_audio_streams(plm_t *self); + + +// Set the desired audio stream (0--3). Default 0. + +void plm_set_audio_stream(plm_t *self, int stream_index); + + +// Get the samplerate of the audio stream in samples per second. + +int plm_get_samplerate(plm_t *self); + + +// Get or set the audio lead time in seconds - the time in which audio samples +// are decoded in advance (or behind) the video decode time. Typically this +// should be set to the duration of the buffer of the audio API that you use +// for output. E.g. for SDL2: (SDL_AudioSpec.samples / samplerate) + +double plm_get_audio_lead_time(plm_t *self); +void plm_set_audio_lead_time(plm_t *self, double lead_time); + + +// Get the current internal time in seconds. + +double plm_get_time(plm_t *self); + + +// Get the video duration of the underlying source in seconds. + +double plm_get_duration(plm_t *self); + + +// Rewind all buffers back to the beginning. + +void plm_rewind(plm_t *self); + + +// Get or set looping. Default FALSE. + +int plm_get_loop(plm_t *self); +void plm_set_loop(plm_t *self, int loop); + + +// Get whether the file has ended. If looping is enabled, this will always +// return FALSE. + +int plm_has_ended(plm_t *self); + + +// Set the callback for decoded video frames used with plm_decode(). If no +// callback is set, video data will be ignored and not be decoded. The *user +// Parameter will be passed to your callback. + +void plm_set_video_decode_callback(plm_t *self, plm_video_decode_callback fp, void *user); + + +// Set the callback for decoded audio samples used with plm_decode(). If no +// callback is set, audio data will be ignored and not be decoded. The *user +// Parameter will be passed to your callback. + +void plm_set_audio_decode_callback(plm_t *self, plm_audio_decode_callback fp, void *user); + + +// Advance the internal timer by seconds and decode video/audio up to this time. +// This will call the video_decode_callback and audio_decode_callback any number +// of times. A frame-skip is not implemented, i.e. everything up to current time +// will be decoded. + +void plm_decode(plm_t *self, double seconds); + + +// Decode and return one video frame. Returns NULL if no frame could be decoded +// (either because the source ended or data is corrupt). If you only want to +// decode video, you should disable audio via plm_set_audio_enabled(). +// The returned plm_frame_t is valid until the next call to plm_decode_video() +// or until plm_destroy() is called. + +plm_frame_t *plm_decode_video(plm_t *self); + + +// Decode and return one audio frame. Returns NULL if no frame could be decoded +// (either because the source ended or data is corrupt). If you only want to +// decode audio, you should disable video via plm_set_video_enabled(). +// The returned plm_samples_t is valid until the next call to plm_decode_audio() +// or until plm_destroy() is called. + +plm_samples_t *plm_decode_audio(plm_t *self); + + +// Seek to the specified time, clamped between 0 -- duration. This can only be +// used when the underlying plm_buffer is seekable, i.e. for files, fixed +// memory buffers or _for_appending buffers. +// If seek_exact is TRUE this will seek to the exact time, otherwise it will +// seek to the last intra frame just before the desired time. Exact seeking can +// be slow, because all frames up to the seeked one have to be decoded on top of +// the previous intra frame. +// If seeking succeeds, this function will call the video_decode_callback +// exactly once with the target frame. If audio is enabled, it will also call +// the audio_decode_callback any number of times, until the audio_lead_time is +// satisfied. +// Returns TRUE if seeking succeeded or FALSE if no frame could be found. + +int plm_seek(plm_t *self, double time, int seek_exact); + + +// Similar to plm_seek(), but will not call the video_decode_callback, +// audio_decode_callback or make any attempts to sync audio. +// Returns the found frame or NULL if no frame could be found. + +plm_frame_t *plm_seek_frame(plm_t *self, double time, int seek_exact); + + + +// ----------------------------------------------------------------------------- +// plm_buffer public API +// Provides the data source for all other plm_* interfaces + + +// The default size for buffers created from files or by the high-level API + +#ifndef PLM_BUFFER_DEFAULT_SIZE +#define PLM_BUFFER_DEFAULT_SIZE (128 * 1024) +#endif + + +// Create a buffer instance with a filename. Returns NULL if the file could not +// be opened. + +plm_buffer_t *plm_buffer_create_with_filename(const char *filename); + + +// Create a buffer instance with a file handle. Pass TRUE to close_when_done +// to let plmpeg call fclose() on the handle when plm_destroy() is called. + +plm_buffer_t *plm_buffer_create_with_file(FILE *fh, int close_when_done); + + +// Create a buffer instance with a pointer to memory as source. This assumes +// the whole file is in memory. The bytes are not copied. Pass 1 to +// free_when_done to let plmpeg call free() on the pointer when plm_destroy() +// is called. + +plm_buffer_t *plm_buffer_create_with_memory(uint8_t *bytes, size_t length, int free_when_done); + + +// Create an empty buffer with an initial capacity. The buffer will grow +// as needed. Data that has already been read, will be discarded. + +plm_buffer_t *plm_buffer_create_with_capacity(size_t capacity); + + +// Create an empty buffer with an initial capacity. The buffer will grow +// as needed. Decoded data will *not* be discarded. This can be used when +// loading a file over the network, without needing to throttle the download. +// It also allows for seeking in the already loaded data. + +plm_buffer_t *plm_buffer_create_for_appending(size_t initial_capacity); + + +// Destroy a buffer instance and free all data + +void plm_buffer_destroy(plm_buffer_t *self); + + +// Copy data into the buffer. If the data to be written is larger than the +// available space, the buffer will realloc() with a larger capacity. +// Returns the number of bytes written. This will always be the same as the +// passed in length, except when the buffer was created _with_memory() for +// which _write() is forbidden. + +size_t plm_buffer_write(plm_buffer_t *self, uint8_t *bytes, size_t length); + + +// Mark the current byte length as the end of this buffer and signal that no +// more data is expected to be written to it. This function should be called +// just after the last plm_buffer_write(). +// For _with_capacity buffers, this is cleared on a plm_buffer_rewind(). + +void plm_buffer_signal_end(plm_buffer_t *self); + + +// Set a callback that is called whenever the buffer needs more data + +void plm_buffer_set_load_callback(plm_buffer_t *self, plm_buffer_load_callback fp, void *user); + + +// Rewind the buffer back to the beginning. When loading from a file handle, +// this also seeks to the beginning of the file. + +void plm_buffer_rewind(plm_buffer_t *self); + + +// Get the total size. For files, this returns the file size. For all other +// types it returns the number of bytes currently in the buffer. + +size_t plm_buffer_get_size(plm_buffer_t *self); + + +// Get the number of remaining (yet unread) bytes in the buffer. This can be +// useful to throttle writing. + +size_t plm_buffer_get_remaining(plm_buffer_t *self); + + +// Get whether the read position of the buffer is at the end and no more data +// is expected. + +int plm_buffer_has_ended(plm_buffer_t *self); + + + +// ----------------------------------------------------------------------------- +// plm_demux public API +// Demux an MPEG Program Stream (PS) data into separate packages + + +// Various Packet Types + +static const int PLM_DEMUX_PACKET_PRIVATE = 0xBD; +static const int PLM_DEMUX_PACKET_AUDIO_1 = 0xC0; +static const int PLM_DEMUX_PACKET_AUDIO_2 = 0xC1; +static const int PLM_DEMUX_PACKET_AUDIO_3 = 0xC2; +static const int PLM_DEMUX_PACKET_AUDIO_4 = 0xC3; +static const int PLM_DEMUX_PACKET_VIDEO_1 = 0xE0; + + +// Create a demuxer with a plm_buffer as source. This will also attempt to read +// the pack and system headers from the buffer. + +plm_demux_t *plm_demux_create(plm_buffer_t *buffer, int destroy_when_done); + + +// Destroy a demuxer and free all data. + +void plm_demux_destroy(plm_demux_t *self); + + +// Returns TRUE/FALSE whether pack and system headers have been found. This will +// attempt to read the headers if non are present yet. + +int plm_demux_has_headers(plm_demux_t *self); + + +// Probe the file for the actual number of video/audio streams. See +// plm_probe() for the details. + +int plm_demux_probe(plm_demux_t *self, size_t probesize); + + +// Returns the number of video streams found in the system header. This will +// attempt to read the system header if non is present yet. + +int plm_demux_get_num_video_streams(plm_demux_t *self); + + +// Returns the number of audio streams found in the system header. This will +// attempt to read the system header if non is present yet. + +int plm_demux_get_num_audio_streams(plm_demux_t *self); + + +// Rewind the internal buffer. See plm_buffer_rewind(). + +void plm_demux_rewind(plm_demux_t *self); + + +// Get whether the file has ended. This will be cleared on seeking or rewind. + +int plm_demux_has_ended(plm_demux_t *self); + + +// Seek to a packet of the specified type with a PTS just before specified time. +// If force_intra is TRUE, only packets containing an intra frame will be +// considered - this only makes sense when the type is PLM_DEMUX_PACKET_VIDEO_1. +// Note that the specified time is considered 0-based, regardless of the first +// PTS in the data source. + +plm_packet_t *plm_demux_seek(plm_demux_t *self, double time, int type, int force_intra); + + +// Get the PTS of the first packet of this type. Returns PLM_PACKET_INVALID_TS +// if not packet of this packet type can be found. + +double plm_demux_get_start_time(plm_demux_t *self, int type); + + +// Get the duration for the specified packet type - i.e. the span between the +// the first PTS and the last PTS in the data source. This only makes sense when +// the underlying data source is a file or fixed memory. + +double plm_demux_get_duration(plm_demux_t *self, int type); + + +// Decode and return the next packet. The returned packet_t is valid until +// the next call to plm_demux_decode() or until the demuxer is destroyed. + +plm_packet_t *plm_demux_decode(plm_demux_t *self); + + + +// ----------------------------------------------------------------------------- +// plm_video public API +// Decode MPEG1 Video ("mpeg1") data into raw YCrCb frames + + +// Create a video decoder with a plm_buffer as source. + +plm_video_t *plm_video_create_with_buffer(plm_buffer_t *buffer, int destroy_when_done); + + +// Destroy a video decoder and free all data. + +void plm_video_destroy(plm_video_t *self); + + +// Get whether a sequence header was found and we can accurately report on +// dimensions and framerate. + +int plm_video_has_header(plm_video_t *self); + + +// Get the framerate in frames per second. + +double plm_video_get_framerate(plm_video_t *self); +double plm_video_get_pixel_aspect_ratio(plm_video_t *self); // [jart] + + +// Get the display width/height. + +int plm_video_get_width(plm_video_t *self); +int plm_video_get_height(plm_video_t *self); + + +// Set "no delay" mode. When enabled, the decoder assumes that the video does +// *not* contain any B-Frames. This is useful for reducing lag when streaming. +// The default is FALSE. + +void plm_video_set_no_delay(plm_video_t *self, int no_delay); + + +// Get the current internal time in seconds. + +double plm_video_get_time(plm_video_t *self); + + +// Set the current internal time in seconds. This is only useful when you +// manipulate the underlying video buffer and want to enforce a correct +// timestamps. + +void plm_video_set_time(plm_video_t *self, double time); + + +// Rewind the internal buffer. See plm_buffer_rewind(). + +void plm_video_rewind(plm_video_t *self); + + +// Get whether the file has ended. This will be cleared on rewind. + +int plm_video_has_ended(plm_video_t *self); + + +// Decode and return one frame of video and advance the internal time by +// 1/framerate seconds. The returned frame_t is valid until the next call of +// plm_video_decode() or until the video decoder is destroyed. + +plm_frame_t *plm_video_decode(plm_video_t *self); + + +// Convert the YCrCb data of a frame into interleaved R G B data. The stride +// specifies the width in bytes of the destination buffer. I.e. the number of +// bytes from one line to the next. The stride must be at least +// (frame->width * bytes_per_pixel). The buffer pointed to by *dest must have a +// size of at least (stride * frame->height). +// Note that the alpha component of the dest buffer is always left untouched. + +void plm_frame_to_rgb(plm_frame_t *frame, uint8_t *dest, int stride); +void plm_frame_to_bgr(plm_frame_t *frame, uint8_t *dest, int stride); +void plm_frame_to_rgba(plm_frame_t *frame, uint8_t *dest, int stride); +void plm_frame_to_bgra(plm_frame_t *frame, uint8_t *dest, int stride); +void plm_frame_to_argb(plm_frame_t *frame, uint8_t *dest, int stride); +void plm_frame_to_abgr(plm_frame_t *frame, uint8_t *dest, int stride); + + +// ----------------------------------------------------------------------------- +// plm_audio public API +// Decode MPEG-1 Audio Layer II ("mp2") data into raw samples + + +// Create an audio decoder with a plm_buffer as source. + +plm_audio_t *plm_audio_create_with_buffer(plm_buffer_t *buffer, int destroy_when_done); + + +// Destroy an audio decoder and free all data. + +void plm_audio_destroy(plm_audio_t *self); + + +// Get whether a frame header was found and we can accurately report on +// samplerate. + +int plm_audio_has_header(plm_audio_t *self); + + +// Get the samplerate in samples per second. + +int plm_audio_get_samplerate(plm_audio_t *self); + + +// Get the current internal time in seconds. + +double plm_audio_get_time(plm_audio_t *self); + + +// Set the current internal time in seconds. This is only useful when you +// manipulate the underlying video buffer and want to enforce a correct +// timestamps. + +void plm_audio_set_time(plm_audio_t *self, double time); + + +// Rewind the internal buffer. See plm_buffer_rewind(). + +void plm_audio_rewind(plm_audio_t *self); + + +// Get whether the file has ended. This will be cleared on rewind. + +int plm_audio_has_ended(plm_audio_t *self); + + +// Decode and return one "frame" of audio and advance the internal time by +// (PLM_AUDIO_SAMPLES_PER_FRAME/samplerate) seconds. The returned samples_t +// is valid until the next call of plm_audio_decode() or until the audio +// decoder is destroyed. + +plm_samples_t *plm_audio_decode(plm_audio_t *self); + + + +#ifdef __cplusplus +} +#endif + +#endif // PL_MPEG_H + + + + + +// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- +// IMPLEMENTATION + +#ifdef PL_MPEG_IMPLEMENTATION + +#include +#include + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +#ifndef PLM_MALLOC + #define PLM_MALLOC(sz) malloc(sz) + #define PLM_FREE(p) free(p) + #define PLM_REALLOC(p, sz) realloc(p, sz) +#endif + +#define PLM_UNUSED(expr) (void)(expr) + + +// ----------------------------------------------------------------------------- +// plm (high-level interface) implementation + +struct plm_t { + plm_demux_t *demux; + double time; + int has_ended; + int loop; + int has_decoders; + + int video_enabled; + int video_packet_type; + plm_buffer_t *video_buffer; + plm_video_t *video_decoder; + + int audio_enabled; + int audio_stream_index; + int audio_packet_type; + double audio_lead_time; + plm_buffer_t *audio_buffer; + plm_audio_t *audio_decoder; + + plm_video_decode_callback video_decode_callback; + void *video_decode_callback_user_data; + + plm_audio_decode_callback audio_decode_callback; + void *audio_decode_callback_user_data; +}; + +int plm_init_decoders(plm_t *self); +void plm_handle_end(plm_t *self); +void plm_read_video_packet(plm_buffer_t *buffer, void *user); +void plm_read_audio_packet(plm_buffer_t *buffer, void *user); +void plm_read_packets(plm_t *self, int requested_type); + +plm_t *plm_create_with_filename(const char *filename) { + plm_buffer_t *buffer = plm_buffer_create_with_filename(filename); + if (!buffer) { + return NULL; + } + return plm_create_with_buffer(buffer, TRUE); +} + +plm_t *plm_create_with_file(FILE *fh, int close_when_done) { + plm_buffer_t *buffer = plm_buffer_create_with_file(fh, close_when_done); + return plm_create_with_buffer(buffer, TRUE); +} + +plm_t *plm_create_with_memory(uint8_t *bytes, size_t length, int free_when_done) { + plm_buffer_t *buffer = plm_buffer_create_with_memory(bytes, length, free_when_done); + return plm_create_with_buffer(buffer, TRUE); +} + +plm_t *plm_create_with_buffer(plm_buffer_t *buffer, int destroy_when_done) { + plm_t *self = (plm_t *)PLM_MALLOC(sizeof(plm_t)); + memset(self, 0, sizeof(plm_t)); + + self->demux = plm_demux_create(buffer, destroy_when_done); + self->video_enabled = TRUE; + self->audio_enabled = TRUE; + plm_init_decoders(self); + + return self; +} + +int plm_init_decoders(plm_t *self) { + if (self->has_decoders) { + return TRUE; + } + + if (!plm_demux_has_headers(self->demux)) { + return FALSE; + } + + if (plm_demux_get_num_video_streams(self->demux) > 0) { + if (self->video_enabled) { + self->video_packet_type = PLM_DEMUX_PACKET_VIDEO_1; + } + if (!self->video_decoder) { + self->video_buffer = plm_buffer_create_with_capacity(PLM_BUFFER_DEFAULT_SIZE); + plm_buffer_set_load_callback(self->video_buffer, plm_read_video_packet, self); + self->video_decoder = plm_video_create_with_buffer(self->video_buffer, TRUE); + } + } + + if (plm_demux_get_num_audio_streams(self->demux) > 0) { + if (self->audio_enabled) { + self->audio_packet_type = PLM_DEMUX_PACKET_AUDIO_1 + self->audio_stream_index; + } + if (!self->audio_decoder) { + self->audio_buffer = plm_buffer_create_with_capacity(PLM_BUFFER_DEFAULT_SIZE); + plm_buffer_set_load_callback(self->audio_buffer, plm_read_audio_packet, self); + self->audio_decoder = plm_audio_create_with_buffer(self->audio_buffer, TRUE); + } + } + + self->has_decoders = TRUE; + return TRUE; +} + +void plm_destroy(plm_t *self) { + if (self->video_decoder) { + plm_video_destroy(self->video_decoder); + } + if (self->audio_decoder) { + plm_audio_destroy(self->audio_decoder); + } + + plm_demux_destroy(self->demux); + PLM_FREE(self); +} + +int plm_get_audio_enabled(plm_t *self) { + return self->audio_enabled; +} + +int plm_has_headers(plm_t *self) { + if (!plm_demux_has_headers(self->demux)) { + return FALSE; + } + + if (!plm_init_decoders(self)) { + return FALSE; + } + + if ( + (self->video_decoder && !plm_video_has_header(self->video_decoder)) || + (self->audio_decoder && !plm_audio_has_header(self->audio_decoder)) + ) { + return FALSE; + } + + return TRUE; +} + +int plm_probe(plm_t *self, size_t probesize) { + int found_streams = plm_demux_probe(self->demux, probesize); + if (!found_streams) { + return FALSE; + } + + // Re-init decoders + self->has_decoders = FALSE; + self->video_packet_type = 0; + self->audio_packet_type = 0; + return plm_init_decoders(self); +} + +void plm_set_audio_enabled(plm_t *self, int enabled) { + self->audio_enabled = enabled; + + if (!enabled) { + self->audio_packet_type = 0; + return; + } + + self->audio_packet_type = (plm_init_decoders(self) && self->audio_decoder) + ? PLM_DEMUX_PACKET_AUDIO_1 + self->audio_stream_index + : 0; +} + +void plm_set_audio_stream(plm_t *self, int stream_index) { + if (stream_index < 0 || stream_index > 3) { + return; + } + self->audio_stream_index = stream_index; + + // Set the correct audio_packet_type + plm_set_audio_enabled(self, self->audio_enabled); +} + +int plm_get_video_enabled(plm_t *self) { + return self->video_enabled; +} + +void plm_set_video_enabled(plm_t *self, int enabled) { + self->video_enabled = enabled; + + if (!enabled) { + self->video_packet_type = 0; + return; + } + + self->video_packet_type = (plm_init_decoders(self) && self->video_decoder) + ? PLM_DEMUX_PACKET_VIDEO_1 + : 0; +} + +int plm_get_num_video_streams(plm_t *self) { + return plm_demux_get_num_video_streams(self->demux); +} + +int plm_get_width(plm_t *self) { + return (plm_init_decoders(self) && self->video_decoder) + ? plm_video_get_width(self->video_decoder) + : 0; +} + +int plm_get_height(plm_t *self) { + return (plm_init_decoders(self) && self->video_decoder) + ? plm_video_get_height(self->video_decoder) + : 0; +} + +double plm_get_framerate(plm_t *self) { + return (plm_init_decoders(self) && self->video_decoder) + ? plm_video_get_framerate(self->video_decoder) + : 0; +} + +double plm_get_pixel_aspect_ratio(plm_t *self) { // [jart] + return (plm_init_decoders(self) && self->video_decoder) + ? plm_video_get_pixel_aspect_ratio(self->video_decoder) + : 0; +} + +int plm_get_num_audio_streams(plm_t *self) { + return plm_demux_get_num_audio_streams(self->demux); +} + +int plm_get_samplerate(plm_t *self) { + return (plm_init_decoders(self) && self->audio_decoder) + ? plm_audio_get_samplerate(self->audio_decoder) + : 0; +} + +double plm_get_audio_lead_time(plm_t *self) { + return self->audio_lead_time; +} + +void plm_set_audio_lead_time(plm_t *self, double lead_time) { + self->audio_lead_time = lead_time; +} + +double plm_get_time(plm_t *self) { + return self->time; +} + +double plm_get_duration(plm_t *self) { + return plm_demux_get_duration(self->demux, PLM_DEMUX_PACKET_VIDEO_1); +} + +void plm_rewind(plm_t *self) { + if (self->video_decoder) { + plm_video_rewind(self->video_decoder); + } + + if (self->audio_decoder) { + plm_audio_rewind(self->audio_decoder); + } + + plm_demux_rewind(self->demux); + self->time = 0; +} + +int plm_get_loop(plm_t *self) { + return self->loop; +} + +void plm_set_loop(plm_t *self, int loop) { + self->loop = loop; +} + +int plm_has_ended(plm_t *self) { + return self->has_ended; +} + +void plm_set_video_decode_callback(plm_t *self, plm_video_decode_callback fp, void *user) { + self->video_decode_callback = fp; + self->video_decode_callback_user_data = user; +} + +void plm_set_audio_decode_callback(plm_t *self, plm_audio_decode_callback fp, void *user) { + self->audio_decode_callback = fp; + self->audio_decode_callback_user_data = user; +} + +void plm_decode(plm_t *self, double tick) { + if (!plm_init_decoders(self)) { + return; + } + + int decode_video = (self->video_decode_callback && self->video_packet_type); + int decode_audio = (self->audio_decode_callback && self->audio_packet_type); + + if (!decode_video && !decode_audio) { + // Nothing to do here + return; + } + + int did_decode = FALSE; + int decode_video_failed = FALSE; + int decode_audio_failed = FALSE; + + double video_target_time = self->time + tick; + double audio_target_time = self->time + tick + self->audio_lead_time; + + do { + did_decode = FALSE; + + if (decode_video && plm_video_get_time(self->video_decoder) < video_target_time) { + plm_frame_t *frame = plm_video_decode(self->video_decoder); + if (frame) { + self->video_decode_callback(self, frame, self->video_decode_callback_user_data); + did_decode = TRUE; + } + else { + decode_video_failed = TRUE; + } + } + + if (decode_audio && plm_audio_get_time(self->audio_decoder) < audio_target_time) { + plm_samples_t *samples = plm_audio_decode(self->audio_decoder); + if (samples) { + self->audio_decode_callback(self, samples, self->audio_decode_callback_user_data); + did_decode = TRUE; + } + else { + decode_audio_failed = TRUE; + } + } + } while (did_decode); + + // Did all sources we wanted to decode fail and the demuxer is at the end? + if ( + (!decode_video || decode_video_failed) && + (!decode_audio || decode_audio_failed) && + plm_demux_has_ended(self->demux) + ) { + plm_handle_end(self); + return; + } + + self->time += tick; +} + +plm_frame_t *plm_decode_video(plm_t *self) { + if (!plm_init_decoders(self)) { + return NULL; + } + + if (!self->video_packet_type) { + return NULL; + } + + plm_frame_t *frame = plm_video_decode(self->video_decoder); + if (frame) { + self->time = frame->time; + } + else if (plm_demux_has_ended(self->demux)) { + plm_handle_end(self); + } + return frame; +} + +plm_samples_t *plm_decode_audio(plm_t *self) { + if (!plm_init_decoders(self)) { + return NULL; + } + + if (!self->audio_packet_type) { + return NULL; + } + + plm_samples_t *samples = plm_audio_decode(self->audio_decoder); + if (samples) { + self->time = samples->time; + } + else if (plm_demux_has_ended(self->demux)) { + plm_handle_end(self); + } + return samples; +} + +void plm_handle_end(plm_t *self) { + if (self->loop) { + plm_rewind(self); + } + else { + self->has_ended = TRUE; + } +} + +void plm_read_video_packet(plm_buffer_t *buffer, void *user) { + PLM_UNUSED(buffer); + plm_t *self = (plm_t *)user; + plm_read_packets(self, self->video_packet_type); +} + +void plm_read_audio_packet(plm_buffer_t *buffer, void *user) { + PLM_UNUSED(buffer); + plm_t *self = (plm_t *)user; + plm_read_packets(self, self->audio_packet_type); +} + +void plm_read_packets(plm_t *self, int requested_type) { + plm_packet_t *packet; + while ((packet = plm_demux_decode(self->demux))) { + if (packet->type == self->video_packet_type) { + plm_buffer_write(self->video_buffer, packet->data, packet->length); + } + else if (packet->type == self->audio_packet_type) { + plm_buffer_write(self->audio_buffer, packet->data, packet->length); + } + + if (packet->type == requested_type) { + return; + } + } + + if (plm_demux_has_ended(self->demux)) { + if (self->video_buffer) { + plm_buffer_signal_end(self->video_buffer); + } + if (self->audio_buffer) { + plm_buffer_signal_end(self->audio_buffer); + } + } +} + +plm_frame_t *plm_seek_frame(plm_t *self, double time, int seek_exact) { + if (!plm_init_decoders(self)) { + return NULL; + } + + if (!self->video_packet_type) { + return NULL; + } + + int type = self->video_packet_type; + + double start_time = plm_demux_get_start_time(self->demux, type); + double duration = plm_demux_get_duration(self->demux, type); + + if (time < 0) { + time = 0; + } + else if (time > duration) { + time = duration; + } + + plm_packet_t *packet = plm_demux_seek(self->demux, time, type, TRUE); + if (!packet) { + return NULL; + } + + // Disable writing to the audio buffer while decoding video + int previous_audio_packet_type = self->audio_packet_type; + self->audio_packet_type = 0; + + // Clear video buffer and decode the found packet + plm_video_rewind(self->video_decoder); + plm_video_set_time(self->video_decoder, packet->pts - start_time); + plm_buffer_write(self->video_buffer, packet->data, packet->length); + plm_frame_t *frame = plm_video_decode(self->video_decoder); + + // If we want to seek to an exact frame, we have to decode all frames + // on top of the intra frame we just jumped to. + if (seek_exact) { + while (frame && frame->time < time) { + frame = plm_video_decode(self->video_decoder); + } + } + + // Enable writing to the audio buffer again? + self->audio_packet_type = previous_audio_packet_type; + + if (frame) { + self->time = frame->time; + } + + self->has_ended = FALSE; + return frame; +} + +int plm_seek(plm_t *self, double time, int seek_exact) { + plm_frame_t *frame = plm_seek_frame(self, time, seek_exact); + + if (!frame) { + return FALSE; + } + + if (self->video_decode_callback) { + self->video_decode_callback(self, frame, self->video_decode_callback_user_data); + } + + // If audio is not enabled we are done here. + if (!self->audio_packet_type) { + return TRUE; + } + + // Sync up Audio. This demuxes more packets until the first audio packet + // with a PTS greater than the current time is found. plm_decode() is then + // called to decode enough audio data to satisfy the audio_lead_time. + + double start_time = plm_demux_get_start_time(self->demux, self->video_packet_type); + plm_audio_rewind(self->audio_decoder); + + plm_packet_t *packet = NULL; + while ((packet = plm_demux_decode(self->demux))) { + if (packet->type == self->video_packet_type) { + plm_buffer_write(self->video_buffer, packet->data, packet->length); + } + else if ( + packet->type == self->audio_packet_type && + packet->pts - start_time > self->time + ) { + plm_audio_set_time(self->audio_decoder, packet->pts - start_time); + plm_buffer_write(self->audio_buffer, packet->data, packet->length); + plm_decode(self, 0); + break; + } + } + + return TRUE; +} + + + +// ----------------------------------------------------------------------------- +// plm_buffer implementation + +enum plm_buffer_mode { + PLM_BUFFER_MODE_FILE, + PLM_BUFFER_MODE_FIXED_MEM, + PLM_BUFFER_MODE_RING, + PLM_BUFFER_MODE_APPEND +}; + +struct plm_buffer_t { + size_t bit_index; + size_t capacity; + size_t length; + size_t total_size; + int discard_read_bytes; + int has_ended; + int free_when_done; + int close_when_done; + FILE *fh; + plm_buffer_load_callback load_callback; + void *load_callback_user_data; + uint8_t *bytes; + enum plm_buffer_mode mode; +}; + +typedef struct { + int16_t index; + int16_t value; +} plm_vlc_t; + +typedef struct { + int16_t index; + uint16_t value; +} plm_vlc_uint_t; + + +void plm_buffer_seek(plm_buffer_t *self, size_t pos); +size_t plm_buffer_tell(plm_buffer_t *self); +void plm_buffer_discard_read_bytes(plm_buffer_t *self); +void plm_buffer_load_file_callback(plm_buffer_t *self, void *user); + +int plm_buffer_has(plm_buffer_t *self, size_t count); +int plm_buffer_read(plm_buffer_t *self, int count); +void plm_buffer_align(plm_buffer_t *self); +void plm_buffer_skip(plm_buffer_t *self, size_t count); +int plm_buffer_skip_bytes(plm_buffer_t *self, uint8_t v); +int plm_buffer_next_start_code(plm_buffer_t *self); +int plm_buffer_find_start_code(plm_buffer_t *self, int code); +int plm_buffer_no_start_code(plm_buffer_t *self); +int16_t plm_buffer_read_vlc(plm_buffer_t *self, const plm_vlc_t *table); +uint16_t plm_buffer_read_vlc_uint(plm_buffer_t *self, const plm_vlc_uint_t *table); + +plm_buffer_t *plm_buffer_create_with_filename(const char *filename) { + FILE *fh = fopen(filename, "rb"); + if (!fh) { + return NULL; + } + return plm_buffer_create_with_file(fh, TRUE); +} + +plm_buffer_t *plm_buffer_create_with_file(FILE *fh, int close_when_done) { + plm_buffer_t *self = plm_buffer_create_with_capacity(PLM_BUFFER_DEFAULT_SIZE); + self->fh = fh; + self->close_when_done = close_when_done; + self->mode = PLM_BUFFER_MODE_FILE; + self->discard_read_bytes = TRUE; + + fseek(self->fh, 0, SEEK_END); + self->total_size = ftell(self->fh); + fseek(self->fh, 0, SEEK_SET); + + plm_buffer_set_load_callback(self, plm_buffer_load_file_callback, NULL); + return self; +} + +plm_buffer_t *plm_buffer_create_with_memory(uint8_t *bytes, size_t length, int free_when_done) { + plm_buffer_t *self = (plm_buffer_t *)PLM_MALLOC(sizeof(plm_buffer_t)); + memset(self, 0, sizeof(plm_buffer_t)); + self->capacity = length; + self->length = length; + self->total_size = length; + self->free_when_done = free_when_done; + self->bytes = bytes; + self->mode = PLM_BUFFER_MODE_FIXED_MEM; + self->discard_read_bytes = FALSE; + return self; +} + +plm_buffer_t *plm_buffer_create_with_capacity(size_t capacity) { + plm_buffer_t *self = (plm_buffer_t *)PLM_MALLOC(sizeof(plm_buffer_t)); + memset(self, 0, sizeof(plm_buffer_t)); + self->capacity = capacity; + self->free_when_done = TRUE; + self->bytes = (uint8_t *)PLM_MALLOC(capacity); + self->mode = PLM_BUFFER_MODE_RING; + self->discard_read_bytes = TRUE; + return self; +} + +plm_buffer_t *plm_buffer_create_for_appending(size_t initial_capacity) { + plm_buffer_t *self = plm_buffer_create_with_capacity(initial_capacity); + self->mode = PLM_BUFFER_MODE_APPEND; + self->discard_read_bytes = FALSE; + return self; +} + +void plm_buffer_destroy(plm_buffer_t *self) { + if (self->fh && self->close_when_done) { + fclose(self->fh); + } + if (self->free_when_done) { + PLM_FREE(self->bytes); + } + PLM_FREE(self); +} + +size_t plm_buffer_get_size(plm_buffer_t *self) { + return (self->mode == PLM_BUFFER_MODE_FILE) + ? self->total_size + : self->length; +} + +size_t plm_buffer_get_remaining(plm_buffer_t *self) { + return self->length - (self->bit_index >> 3); +} + +size_t plm_buffer_write(plm_buffer_t *self, uint8_t *bytes, size_t length) { + if (self->mode == PLM_BUFFER_MODE_FIXED_MEM) { + return 0; + } + + if (self->discard_read_bytes) { + // This should be a ring buffer, but instead it just shifts all unread + // data to the beginning of the buffer and appends new data at the end. + // Seems to be good enough. + + plm_buffer_discard_read_bytes(self); + if (self->mode == PLM_BUFFER_MODE_RING) { + self->total_size = 0; + } + } + + // Do we have to resize to fit the new data? + size_t bytes_available = self->capacity - self->length; + if (bytes_available < length) { + size_t new_size = self->capacity; + do { + new_size *= 2; + } while (new_size - self->length < length); + self->bytes = (uint8_t *)PLM_REALLOC(self->bytes, new_size); + self->capacity = new_size; + } + + memcpy(self->bytes + self->length, bytes, length); + self->length += length; + self->has_ended = FALSE; + return length; +} + +void plm_buffer_signal_end(plm_buffer_t *self) { + self->total_size = self->length; +} + +void plm_buffer_set_load_callback(plm_buffer_t *self, plm_buffer_load_callback fp, void *user) { + self->load_callback = fp; + self->load_callback_user_data = user; +} + +void plm_buffer_rewind(plm_buffer_t *self) { + plm_buffer_seek(self, 0); +} + +void plm_buffer_seek(plm_buffer_t *self, size_t pos) { + self->has_ended = FALSE; + + if (self->mode == PLM_BUFFER_MODE_FILE) { + fseek(self->fh, pos, SEEK_SET); + self->bit_index = 0; + self->length = 0; + } + else if (self->mode == PLM_BUFFER_MODE_RING) { + if (pos != 0) { + // Seeking to non-0 is forbidden for dynamic-mem buffers + return; + } + self->bit_index = 0; + self->length = 0; + self->total_size = 0; + } + else if (pos < self->length) { + self->bit_index = pos << 3; + } +} + +size_t plm_buffer_tell(plm_buffer_t *self) { + return self->mode == PLM_BUFFER_MODE_FILE + ? ftell(self->fh) + (self->bit_index >> 3) - self->length + : self->bit_index >> 3; +} + +void plm_buffer_discard_read_bytes(plm_buffer_t *self) { + size_t byte_pos = self->bit_index >> 3; + if (byte_pos == self->length) { + self->bit_index = 0; + self->length = 0; + } + else if (byte_pos > 0) { + memmove(self->bytes, self->bytes + byte_pos, self->length - byte_pos); + self->bit_index -= byte_pos << 3; + self->length -= byte_pos; + } +} + +void plm_buffer_load_file_callback(plm_buffer_t *self, void *user) { + PLM_UNUSED(user); + + if (self->discard_read_bytes) { + plm_buffer_discard_read_bytes(self); + } + + size_t bytes_available = self->capacity - self->length; + size_t bytes_read = fread(self->bytes + self->length, 1, bytes_available, self->fh); + self->length += bytes_read; + + if (bytes_read == 0) { + self->has_ended = TRUE; + } +} + +int plm_buffer_has_ended(plm_buffer_t *self) { + return self->has_ended; +} + +int plm_buffer_has(plm_buffer_t *self, size_t count) { + if (((self->length << 3) - self->bit_index) >= count) { + return TRUE; + } + + if (self->load_callback) { + self->load_callback(self, self->load_callback_user_data); + + if (((self->length << 3) - self->bit_index) >= count) { + return TRUE; + } + } + + if (self->total_size != 0 && self->length == self->total_size) { + self->has_ended = TRUE; + } + return FALSE; +} + +int plm_buffer_read(plm_buffer_t *self, int count) { + if (!plm_buffer_has(self, count)) { + return 0; + } + + int value = 0; + while (count) { + int current_byte = self->bytes[self->bit_index >> 3]; + + int remaining = 8 - (self->bit_index & 7); // Remaining bits in byte + int read = remaining < count ? remaining : count; // Bits in self run + int shift = remaining - read; + int mask = (0xff >> (8 - read)); + + value = (value << read) | ((current_byte & (mask << shift)) >> shift); + + self->bit_index += read; + count -= read; + } + + return value; +} + +void plm_buffer_align(plm_buffer_t *self) { + self->bit_index = ((self->bit_index + 7) >> 3) << 3; // Align to next byte +} + +void plm_buffer_skip(plm_buffer_t *self, size_t count) { + if (plm_buffer_has(self, count)) { + self->bit_index += count; + } +} + +int plm_buffer_skip_bytes(plm_buffer_t *self, uint8_t v) { + plm_buffer_align(self); + int skipped = 0; + while (plm_buffer_has(self, 8) && self->bytes[self->bit_index >> 3] == v) { + self->bit_index += 8; + skipped++; + } + return skipped; +} + +int plm_buffer_next_start_code(plm_buffer_t *self) { + plm_buffer_align(self); + + while (plm_buffer_has(self, (5 << 3))) { + size_t byte_index = (self->bit_index) >> 3; + if ( + self->bytes[byte_index] == 0x00 && + self->bytes[byte_index + 1] == 0x00 && + self->bytes[byte_index + 2] == 0x01 + ) { + self->bit_index = (byte_index + 4) << 3; + return self->bytes[byte_index + 3]; + } + self->bit_index += 8; + } + return -1; +} + +int plm_buffer_find_start_code(plm_buffer_t *self, int code) { + int current = 0; + while (TRUE) { + current = plm_buffer_next_start_code(self); + if (current == code || current == -1) { + return current; + } + } + return -1; +} + +int plm_buffer_has_start_code(plm_buffer_t *self, int code) { + size_t previous_bit_index = self->bit_index; + int previous_discard_read_bytes = self->discard_read_bytes; + + self->discard_read_bytes = FALSE; + int current = plm_buffer_find_start_code(self, code); + + self->bit_index = previous_bit_index; + self->discard_read_bytes = previous_discard_read_bytes; + return current; +} + +int plm_buffer_peek_non_zero(plm_buffer_t *self, int bit_count) { + if (!plm_buffer_has(self, bit_count)) { + return FALSE; + } + + int val = plm_buffer_read(self, bit_count); + self->bit_index -= bit_count; + return val != 0; +} + +int16_t plm_buffer_read_vlc(plm_buffer_t *self, const plm_vlc_t *table) { + plm_vlc_t state = {0, 0}; + do { + state = table[state.index + plm_buffer_read(self, 1)]; + } while (state.index > 0); + return state.value; +} + +uint16_t plm_buffer_read_vlc_uint(plm_buffer_t *self, const plm_vlc_uint_t *table) { + return (uint16_t)plm_buffer_read_vlc(self, (const plm_vlc_t *)table); +} + + + +// ---------------------------------------------------------------------------- +// plm_demux implementation + +static const int PLM_START_PACK = 0xBA; +static const int PLM_START_END = 0xB9; +static const int PLM_START_SYSTEM = 0xBB; + +struct plm_demux_t { + plm_buffer_t *buffer; + int destroy_buffer_when_done; + double system_clock_ref; + + size_t last_file_size; + double last_decoded_pts; + double start_time; + double duration; + + int start_code; + int has_pack_header; + int has_system_header; + int has_headers; + + int num_audio_streams; + int num_video_streams; + plm_packet_t current_packet; + plm_packet_t next_packet; +}; + + +void plm_demux_buffer_seek(plm_demux_t *self, size_t pos); +double plm_demux_decode_time(plm_demux_t *self); +plm_packet_t *plm_demux_decode_packet(plm_demux_t *self, int type); +plm_packet_t *plm_demux_get_packet(plm_demux_t *self); + +plm_demux_t *plm_demux_create(plm_buffer_t *buffer, int destroy_when_done) { + plm_demux_t *self = (plm_demux_t *)PLM_MALLOC(sizeof(plm_demux_t)); + memset(self, 0, sizeof(plm_demux_t)); + + self->buffer = buffer; + self->destroy_buffer_when_done = destroy_when_done; + + self->start_time = PLM_PACKET_INVALID_TS; + self->duration = PLM_PACKET_INVALID_TS; + self->start_code = -1; + + plm_demux_has_headers(self); + return self; +} + +void plm_demux_destroy(plm_demux_t *self) { + if (self->destroy_buffer_when_done) { + plm_buffer_destroy(self->buffer); + } + PLM_FREE(self); +} + +int plm_demux_has_headers(plm_demux_t *self) { + if (self->has_headers) { + return TRUE; + } + + // Decode pack header + if (!self->has_pack_header) { + if ( + self->start_code != PLM_START_PACK && + plm_buffer_find_start_code(self->buffer, PLM_START_PACK) == -1 + ) { + return FALSE; + } + + self->start_code = PLM_START_PACK; + if (!plm_buffer_has(self->buffer, 64)) { + return FALSE; + } + self->start_code = -1; + + if (plm_buffer_read(self->buffer, 4) != 0x02) { + return FALSE; + } + + self->system_clock_ref = plm_demux_decode_time(self); + plm_buffer_skip(self->buffer, 1); + plm_buffer_skip(self->buffer, 22); // mux_rate * 50 + plm_buffer_skip(self->buffer, 1); + + self->has_pack_header = TRUE; + } + + // Decode system header + if (!self->has_system_header) { + if ( + self->start_code != PLM_START_SYSTEM && + plm_buffer_find_start_code(self->buffer, PLM_START_SYSTEM) == -1 + ) { + return FALSE; + } + + self->start_code = PLM_START_SYSTEM; + if (!plm_buffer_has(self->buffer, 56)) { + return FALSE; + } + self->start_code = -1; + + plm_buffer_skip(self->buffer, 16); // header_length + plm_buffer_skip(self->buffer, 24); // rate bound + self->num_audio_streams = plm_buffer_read(self->buffer, 6); + plm_buffer_skip(self->buffer, 5); // misc flags + self->num_video_streams = plm_buffer_read(self->buffer, 5); + + self->has_system_header = TRUE; + } + + self->has_headers = TRUE; + return TRUE; +} + +int plm_demux_probe(plm_demux_t *self, size_t probesize) { + int previous_pos = plm_buffer_tell(self->buffer); + + int video_stream = FALSE; + int audio_streams[4] = {FALSE, FALSE, FALSE, FALSE}; + do { + self->start_code = plm_buffer_next_start_code(self->buffer); + if (self->start_code == PLM_DEMUX_PACKET_VIDEO_1) { + video_stream = TRUE; + } + else if ( + self->start_code >= PLM_DEMUX_PACKET_AUDIO_1 && + self->start_code <= PLM_DEMUX_PACKET_AUDIO_4 + ) { + audio_streams[self->start_code - PLM_DEMUX_PACKET_AUDIO_1] = TRUE; + } + } while ( + self->start_code != -1 && + plm_buffer_tell(self->buffer) - previous_pos < probesize + ); + + self->num_video_streams = video_stream ? 1 : 0; + self->num_audio_streams = 0; + for (int i = 0; i < 4; i++) { + if (audio_streams[i]) { + self->num_audio_streams++; + } + } + + plm_demux_buffer_seek(self, previous_pos); + return (self->num_video_streams || self->num_audio_streams); +} + +int plm_demux_get_num_video_streams(plm_demux_t *self) { + return plm_demux_has_headers(self) + ? self->num_video_streams + : 0; +} + +int plm_demux_get_num_audio_streams(plm_demux_t *self) { + return plm_demux_has_headers(self) + ? self->num_audio_streams + : 0; +} + +void plm_demux_rewind(plm_demux_t *self) { + plm_buffer_rewind(self->buffer); + self->current_packet.length = 0; + self->next_packet.length = 0; + self->start_code = -1; +} + +int plm_demux_has_ended(plm_demux_t *self) { + return plm_buffer_has_ended(self->buffer); +} + +void plm_demux_buffer_seek(plm_demux_t *self, size_t pos) { + plm_buffer_seek(self->buffer, pos); + self->current_packet.length = 0; + self->next_packet.length = 0; + self->start_code = -1; +} + +double plm_demux_get_start_time(plm_demux_t *self, int type) { + if (self->start_time != PLM_PACKET_INVALID_TS) { + return self->start_time; + } + + int previous_pos = plm_buffer_tell(self->buffer); + int previous_start_code = self->start_code; + + // Find first video PTS + plm_demux_rewind(self); + do { + plm_packet_t *packet = plm_demux_decode(self); + if (!packet) { + break; + } + if (packet->type == type) { + self->start_time = packet->pts; + } + } while (self->start_time == PLM_PACKET_INVALID_TS); + + plm_demux_buffer_seek(self, previous_pos); + self->start_code = previous_start_code; + return self->start_time; +} + +double plm_demux_get_duration(plm_demux_t *self, int type) { + size_t file_size = plm_buffer_get_size(self->buffer); + + if ( + self->duration != PLM_PACKET_INVALID_TS && + self->last_file_size == file_size + ) { + return self->duration; + } + + size_t previous_pos = plm_buffer_tell(self->buffer); + int previous_start_code = self->start_code; + + // Find last video PTS. Start searching 64kb from the end and go further + // back if needed. + long start_range = 64 * 1024; + long max_range = 4096 * 1024; + for (long range = start_range; range <= max_range; range *= 2) { + long seek_pos = file_size - range; + if (seek_pos < 0) { + seek_pos = 0; + range = max_range; // Make sure to bail after this round + } + plm_demux_buffer_seek(self, seek_pos); + self->current_packet.length = 0; + + double last_pts = PLM_PACKET_INVALID_TS; + plm_packet_t *packet = NULL; + while ((packet = plm_demux_decode(self))) { + if (packet->pts != PLM_PACKET_INVALID_TS && packet->type == type) { + last_pts = packet->pts; + } + } + if (last_pts != PLM_PACKET_INVALID_TS) { + self->duration = last_pts - plm_demux_get_start_time(self, type); + break; + } + } + + plm_demux_buffer_seek(self, previous_pos); + self->start_code = previous_start_code; + self->last_file_size = file_size; + return self->duration; +} + +plm_packet_t *plm_demux_seek(plm_demux_t *self, double seek_time, int type, int force_intra) { + if (!plm_demux_has_headers(self)) { + return NULL; + } + + // Using the current time, current byte position and the average bytes per + // second for this file, try to jump to a byte position that hopefully has + // packets containing timestamps within one second before to the desired + // seek_time. + + // If we hit close to the seek_time scan through all packets to find the + // last one (just before the seek_time) containing an intra frame. + // Otherwise we should at least be closer than before. Calculate the bytes + // per second for the jumped range and jump again. + + // The number of retries here is hard-limited to a generous amount. Usually + // the correct range is found after 1--5 jumps, even for files with very + // variable bitrates. If significantly more jumps are needed, there's + // probably something wrong with the file and we just avoid getting into an + // infinite loop. 32 retries should be enough for anybody. + + double duration = plm_demux_get_duration(self, type); + long file_size = plm_buffer_get_size(self->buffer); + long byterate = file_size / duration; + + double cur_time = self->last_decoded_pts; + double scan_span = 1; + + if (seek_time > duration) { + seek_time = duration; + } + else if (seek_time < 0) { + seek_time = 0; + } + seek_time += self->start_time; + + for (int retry = 0; retry < 32; retry++) { + int found_packet_with_pts = FALSE; + int found_packet_in_range = FALSE; + long last_valid_packet_start = -1; + double first_packet_time = PLM_PACKET_INVALID_TS; + + long cur_pos = plm_buffer_tell(self->buffer); + + // Estimate byte offset and jump to it. + long offset = (seek_time - cur_time - scan_span) * byterate; + long seek_pos = cur_pos + offset; + if (seek_pos < 0) { + seek_pos = 0; + } + else if (seek_pos > file_size - 256) { + seek_pos = file_size - 256; + } + + plm_demux_buffer_seek(self, seek_pos); + + // Scan through all packets up to the seek_time to find the last packet + // containing an intra frame. + while (plm_buffer_find_start_code(self->buffer, type) != -1) { + long packet_start = plm_buffer_tell(self->buffer); + plm_packet_t *packet = plm_demux_decode_packet(self, type); + + // Skip packet if it has no PTS + if (!packet || packet->pts == PLM_PACKET_INVALID_TS) { + continue; + } + + // Bail scanning through packets if we hit one that is outside + // seek_time - scan_span. + // We also adjust the cur_time and byterate values here so the next + // iteration can be a bit more precise. + if (packet->pts > seek_time || packet->pts < seek_time - scan_span) { + found_packet_with_pts = TRUE; + byterate = (seek_pos - cur_pos) / (packet->pts - cur_time); + cur_time = packet->pts; + break; + } + + // If we are still here, it means this packet is in close range to + // the seek_time. If this is the first packet for this jump position + // record the PTS. If we later have to back off, when there was no + // intra frame in this range, we can lower the seek_time to not scan + // this range again. + if (!found_packet_in_range) { + found_packet_in_range = TRUE; + first_packet_time = packet->pts; + } + + // Check if this is an intra frame packet. If so, record the buffer + // position of the start of this packet. We want to jump back to it + // later, when we know it's the last intra frame before desired + // seek time. + if (force_intra) { + for (size_t i = 0; i < packet->length - 6; i++) { + // Find the START_PICTURE code + if ( + packet->data[i] == 0x00 && + packet->data[i + 1] == 0x00 && + packet->data[i + 2] == 0x01 && + packet->data[i + 3] == 0x00 + ) { + // Bits 11--13 in the picture header contain the frame + // type, where 1=Intra + if ((packet->data[i + 5] & 0x38) == 8) { + last_valid_packet_start = packet_start; + } + break; + } + } + } + + // If we don't want intra frames, just use the last PTS found. + else { + last_valid_packet_start = packet_start; + } + } + + // If there was at least one intra frame in the range scanned above, + // our search is over. Jump back to the packet and decode it again. + if (last_valid_packet_start != -1) { + plm_demux_buffer_seek(self, last_valid_packet_start); + return plm_demux_decode_packet(self, type); + } + + // If we hit the right range, but still found no intra frame, we have + // to increases the scan_span. This is done exponentially to also handle + // video files with very few intra frames. + else if (found_packet_in_range) { + scan_span *= 2; + seek_time = first_packet_time; + } + + // If we didn't find any packet with a PTS, it probably means we reached + // the end of the file. Estimate byterate and cur_time accordingly. + else if (!found_packet_with_pts) { + byterate = (seek_pos - cur_pos) / (duration - cur_time); + cur_time = duration; + } + } + + return NULL; +} + +plm_packet_t *plm_demux_decode(plm_demux_t *self) { + if (!plm_demux_has_headers(self)) { + return NULL; + } + + if (self->current_packet.length) { + size_t bits_till_next_packet = self->current_packet.length << 3; + if (!plm_buffer_has(self->buffer, bits_till_next_packet)) { + return NULL; + } + plm_buffer_skip(self->buffer, bits_till_next_packet); + self->current_packet.length = 0; + } + + // Pending packet waiting for data? + if (self->next_packet.length) { + return plm_demux_get_packet(self); + } + + // Pending packet waiting for header? + if (self->start_code != -1) { + return plm_demux_decode_packet(self, self->start_code); + } + + do { + self->start_code = plm_buffer_next_start_code(self->buffer); + if ( + self->start_code == PLM_DEMUX_PACKET_VIDEO_1 || + self->start_code == PLM_DEMUX_PACKET_PRIVATE || ( + self->start_code >= PLM_DEMUX_PACKET_AUDIO_1 && + self->start_code <= PLM_DEMUX_PACKET_AUDIO_4 + ) + ) { + return plm_demux_decode_packet(self, self->start_code); + } + } while (self->start_code != -1); + + return NULL; +} + +double plm_demux_decode_time(plm_demux_t *self) { + int64_t clock = plm_buffer_read(self->buffer, 3) << 30; + plm_buffer_skip(self->buffer, 1); + clock |= plm_buffer_read(self->buffer, 15) << 15; + plm_buffer_skip(self->buffer, 1); + clock |= plm_buffer_read(self->buffer, 15); + plm_buffer_skip(self->buffer, 1); + return (double)clock / 90000.0; +} + +plm_packet_t *plm_demux_decode_packet(plm_demux_t *self, int type) { + if (!plm_buffer_has(self->buffer, 16 << 3)) { + return NULL; + } + + self->start_code = -1; + + self->next_packet.type = type; + self->next_packet.length = plm_buffer_read(self->buffer, 16); + self->next_packet.length -= plm_buffer_skip_bytes(self->buffer, 0xff); // stuffing + + // skip P-STD + if (plm_buffer_read(self->buffer, 2) == 0x01) { + plm_buffer_skip(self->buffer, 16); + self->next_packet.length -= 2; + } + + int pts_dts_marker = plm_buffer_read(self->buffer, 2); + if (pts_dts_marker == 0x03) { + self->next_packet.pts = plm_demux_decode_time(self); + self->last_decoded_pts = self->next_packet.pts; + plm_buffer_skip(self->buffer, 40); // skip dts + self->next_packet.length -= 10; + } + else if (pts_dts_marker == 0x02) { + self->next_packet.pts = plm_demux_decode_time(self); + self->last_decoded_pts = self->next_packet.pts; + self->next_packet.length -= 5; + } + else if (pts_dts_marker == 0x00) { + self->next_packet.pts = PLM_PACKET_INVALID_TS; + plm_buffer_skip(self->buffer, 4); + self->next_packet.length -= 1; + } + else { + return NULL; // invalid + } + + return plm_demux_get_packet(self); +} + +plm_packet_t *plm_demux_get_packet(plm_demux_t *self) { + if (!plm_buffer_has(self->buffer, self->next_packet.length << 3)) { + return NULL; + } + + self->current_packet.data = self->buffer->bytes + (self->buffer->bit_index >> 3); + self->current_packet.length = self->next_packet.length; + self->current_packet.type = self->next_packet.type; + self->current_packet.pts = self->next_packet.pts; + + self->next_packet.length = 0; + return &self->current_packet; +} + + + +// ----------------------------------------------------------------------------- +// plm_video implementation + +// Inspired by Java MPEG-1 Video Decoder and Player by Zoltan Korandi +// https://sourceforge.net/projects/javampeg1video/ + +static const int PLM_VIDEO_PICTURE_TYPE_INTRA = 1; +static const int PLM_VIDEO_PICTURE_TYPE_PREDICTIVE = 2; +static const int PLM_VIDEO_PICTURE_TYPE_B = 3; + +static const int PLM_START_SEQUENCE = 0xB3; +static const int PLM_START_SLICE_FIRST = 0x01; +static const int PLM_START_SLICE_LAST = 0xAF; +static const int PLM_START_PICTURE = 0x00; +static const int PLM_START_EXTENSION = 0xB5; +static const int PLM_START_USER_DATA = 0xB2; + +#define PLM_START_IS_SLICE(c) \ + (c >= PLM_START_SLICE_FIRST && c <= PLM_START_SLICE_LAST) + +static const float PLM_VIDEO_PIXEL_ASPECT_RATIO[] = { // [jart] + 1.0000, /* square pixels */ + 0.6735, /* 3:4? */ + 0.7031, /* MPEG-1 / MPEG-2 video encoding divergence? */ + 0.7615, 0.8055, 0.8437, 0.8935, 0.9157, 0.9815, + 1.0255, 1.0695, 1.0950, 1.1575, 1.2051, +}; + +static const double PLM_VIDEO_PICTURE_RATE[] = { + 0.000, 23.976, 24.000, 25.000, 29.970, 30.000, 50.000, 59.940, + 60.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000 +}; + +static const uint8_t PLM_VIDEO_ZIG_ZAG[] = { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63 +}; + +static const uint8_t PLM_VIDEO_INTRA_QUANT_MATRIX[] = { + 8, 16, 19, 22, 26, 27, 29, 34, + 16, 16, 22, 24, 27, 29, 34, 37, + 19, 22, 26, 27, 29, 34, 34, 38, + 22, 22, 26, 27, 29, 34, 37, 40, + 22, 26, 27, 29, 32, 35, 40, 48, + 26, 27, 29, 32, 35, 40, 48, 58, + 26, 27, 29, 34, 38, 46, 56, 69, + 27, 29, 35, 38, 46, 56, 69, 83 +}; + +static const uint8_t PLM_VIDEO_NON_INTRA_QUANT_MATRIX[] = { + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16 +}; + +static const uint8_t PLM_VIDEO_PREMULTIPLIER_MATRIX[] = { + 32, 44, 42, 38, 32, 25, 17, 9, + 44, 62, 58, 52, 44, 35, 24, 12, + 42, 58, 55, 49, 42, 33, 23, 12, + 38, 52, 49, 44, 38, 30, 20, 10, + 32, 44, 42, 38, 32, 25, 17, 9, + 25, 35, 33, 30, 25, 20, 14, 7, + 17, 24, 23, 20, 17, 14, 9, 5, + 9, 12, 12, 10, 9, 7, 5, 2 +}; + +static const plm_vlc_t PLM_VIDEO_MACROBLOCK_ADDRESS_INCREMENT[] = { + { 1 << 1, 0}, { 0, 1}, // 0: x + { 2 << 1, 0}, { 3 << 1, 0}, // 1: 0x + { 4 << 1, 0}, { 5 << 1, 0}, // 2: 00x + { 0, 3}, { 0, 2}, // 3: 01x + { 6 << 1, 0}, { 7 << 1, 0}, // 4: 000x + { 0, 5}, { 0, 4}, // 5: 001x + { 8 << 1, 0}, { 9 << 1, 0}, // 6: 0000x + { 0, 7}, { 0, 6}, // 7: 0001x + { 10 << 1, 0}, { 11 << 1, 0}, // 8: 0000 0x + { 12 << 1, 0}, { 13 << 1, 0}, // 9: 0000 1x + { 14 << 1, 0}, { 15 << 1, 0}, // 10: 0000 00x + { 16 << 1, 0}, { 17 << 1, 0}, // 11: 0000 01x + { 18 << 1, 0}, { 19 << 1, 0}, // 12: 0000 10x + { 0, 9}, { 0, 8}, // 13: 0000 11x + { -1, 0}, { 20 << 1, 0}, // 14: 0000 000x + { -1, 0}, { 21 << 1, 0}, // 15: 0000 001x + { 22 << 1, 0}, { 23 << 1, 0}, // 16: 0000 010x + { 0, 15}, { 0, 14}, // 17: 0000 011x + { 0, 13}, { 0, 12}, // 18: 0000 100x + { 0, 11}, { 0, 10}, // 19: 0000 101x + { 24 << 1, 0}, { 25 << 1, 0}, // 20: 0000 0001x + { 26 << 1, 0}, { 27 << 1, 0}, // 21: 0000 0011x + { 28 << 1, 0}, { 29 << 1, 0}, // 22: 0000 0100x + { 30 << 1, 0}, { 31 << 1, 0}, // 23: 0000 0101x + { 32 << 1, 0}, { -1, 0}, // 24: 0000 0001 0x + { -1, 0}, { 33 << 1, 0}, // 25: 0000 0001 1x + { 34 << 1, 0}, { 35 << 1, 0}, // 26: 0000 0011 0x + { 36 << 1, 0}, { 37 << 1, 0}, // 27: 0000 0011 1x + { 38 << 1, 0}, { 39 << 1, 0}, // 28: 0000 0100 0x + { 0, 21}, { 0, 20}, // 29: 0000 0100 1x + { 0, 19}, { 0, 18}, // 30: 0000 0101 0x + { 0, 17}, { 0, 16}, // 31: 0000 0101 1x + { 0, 35}, { -1, 0}, // 32: 0000 0001 00x + { -1, 0}, { 0, 34}, // 33: 0000 0001 11x + { 0, 33}, { 0, 32}, // 34: 0000 0011 00x + { 0, 31}, { 0, 30}, // 35: 0000 0011 01x + { 0, 29}, { 0, 28}, // 36: 0000 0011 10x + { 0, 27}, { 0, 26}, // 37: 0000 0011 11x + { 0, 25}, { 0, 24}, // 38: 0000 0100 00x + { 0, 23}, { 0, 22}, // 39: 0000 0100 01x +}; + +static const plm_vlc_t PLM_VIDEO_MACROBLOCK_TYPE_INTRA[] = { + { 1 << 1, 0}, { 0, 0x01}, // 0: x + { -1, 0}, { 0, 0x11}, // 1: 0x +}; + +static const plm_vlc_t PLM_VIDEO_MACROBLOCK_TYPE_PREDICTIVE[] = { + { 1 << 1, 0}, { 0, 0x0a}, // 0: x + { 2 << 1, 0}, { 0, 0x02}, // 1: 0x + { 3 << 1, 0}, { 0, 0x08}, // 2: 00x + { 4 << 1, 0}, { 5 << 1, 0}, // 3: 000x + { 6 << 1, 0}, { 0, 0x12}, // 4: 0000x + { 0, 0x1a}, { 0, 0x01}, // 5: 0001x + { -1, 0}, { 0, 0x11}, // 6: 0000 0x +}; + +static const plm_vlc_t PLM_VIDEO_MACROBLOCK_TYPE_B[] = { + { 1 << 1, 0}, { 2 << 1, 0}, // 0: x + { 3 << 1, 0}, { 4 << 1, 0}, // 1: 0x + { 0, 0x0c}, { 0, 0x0e}, // 2: 1x + { 5 << 1, 0}, { 6 << 1, 0}, // 3: 00x + { 0, 0x04}, { 0, 0x06}, // 4: 01x + { 7 << 1, 0}, { 8 << 1, 0}, // 5: 000x + { 0, 0x08}, { 0, 0x0a}, // 6: 001x + { 9 << 1, 0}, { 10 << 1, 0}, // 7: 0000x + { 0, 0x1e}, { 0, 0x01}, // 8: 0001x + { -1, 0}, { 0, 0x11}, // 9: 0000 0x + { 0, 0x16}, { 0, 0x1a}, // 10: 0000 1x +}; + +static const plm_vlc_t *PLM_VIDEO_MACROBLOCK_TYPE[] = { + NULL, + PLM_VIDEO_MACROBLOCK_TYPE_INTRA, + PLM_VIDEO_MACROBLOCK_TYPE_PREDICTIVE, + PLM_VIDEO_MACROBLOCK_TYPE_B +}; + +static const plm_vlc_t PLM_VIDEO_CODE_BLOCK_PATTERN[] = { + { 1 << 1, 0}, { 2 << 1, 0}, // 0: x + { 3 << 1, 0}, { 4 << 1, 0}, // 1: 0x + { 5 << 1, 0}, { 6 << 1, 0}, // 2: 1x + { 7 << 1, 0}, { 8 << 1, 0}, // 3: 00x + { 9 << 1, 0}, { 10 << 1, 0}, // 4: 01x + { 11 << 1, 0}, { 12 << 1, 0}, // 5: 10x + { 13 << 1, 0}, { 0, 60}, // 6: 11x + { 14 << 1, 0}, { 15 << 1, 0}, // 7: 000x + { 16 << 1, 0}, { 17 << 1, 0}, // 8: 001x + { 18 << 1, 0}, { 19 << 1, 0}, // 9: 010x + { 20 << 1, 0}, { 21 << 1, 0}, // 10: 011x + { 22 << 1, 0}, { 23 << 1, 0}, // 11: 100x + { 0, 32}, { 0, 16}, // 12: 101x + { 0, 8}, { 0, 4}, // 13: 110x + { 24 << 1, 0}, { 25 << 1, 0}, // 14: 0000x + { 26 << 1, 0}, { 27 << 1, 0}, // 15: 0001x + { 28 << 1, 0}, { 29 << 1, 0}, // 16: 0010x + { 30 << 1, 0}, { 31 << 1, 0}, // 17: 0011x + { 0, 62}, { 0, 2}, // 18: 0100x + { 0, 61}, { 0, 1}, // 19: 0101x + { 0, 56}, { 0, 52}, // 20: 0110x + { 0, 44}, { 0, 28}, // 21: 0111x + { 0, 40}, { 0, 20}, // 22: 1000x + { 0, 48}, { 0, 12}, // 23: 1001x + { 32 << 1, 0}, { 33 << 1, 0}, // 24: 0000 0x + { 34 << 1, 0}, { 35 << 1, 0}, // 25: 0000 1x + { 36 << 1, 0}, { 37 << 1, 0}, // 26: 0001 0x + { 38 << 1, 0}, { 39 << 1, 0}, // 27: 0001 1x + { 40 << 1, 0}, { 41 << 1, 0}, // 28: 0010 0x + { 42 << 1, 0}, { 43 << 1, 0}, // 29: 0010 1x + { 0, 63}, { 0, 3}, // 30: 0011 0x + { 0, 36}, { 0, 24}, // 31: 0011 1x + { 44 << 1, 0}, { 45 << 1, 0}, // 32: 0000 00x + { 46 << 1, 0}, { 47 << 1, 0}, // 33: 0000 01x + { 48 << 1, 0}, { 49 << 1, 0}, // 34: 0000 10x + { 50 << 1, 0}, { 51 << 1, 0}, // 35: 0000 11x + { 52 << 1, 0}, { 53 << 1, 0}, // 36: 0001 00x + { 54 << 1, 0}, { 55 << 1, 0}, // 37: 0001 01x + { 56 << 1, 0}, { 57 << 1, 0}, // 38: 0001 10x + { 58 << 1, 0}, { 59 << 1, 0}, // 39: 0001 11x + { 0, 34}, { 0, 18}, // 40: 0010 00x + { 0, 10}, { 0, 6}, // 41: 0010 01x + { 0, 33}, { 0, 17}, // 42: 0010 10x + { 0, 9}, { 0, 5}, // 43: 0010 11x + { -1, 0}, { 60 << 1, 0}, // 44: 0000 000x + { 61 << 1, 0}, { 62 << 1, 0}, // 45: 0000 001x + { 0, 58}, { 0, 54}, // 46: 0000 010x + { 0, 46}, { 0, 30}, // 47: 0000 011x + { 0, 57}, { 0, 53}, // 48: 0000 100x + { 0, 45}, { 0, 29}, // 49: 0000 101x + { 0, 38}, { 0, 26}, // 50: 0000 110x + { 0, 37}, { 0, 25}, // 51: 0000 111x + { 0, 43}, { 0, 23}, // 52: 0001 000x + { 0, 51}, { 0, 15}, // 53: 0001 001x + { 0, 42}, { 0, 22}, // 54: 0001 010x + { 0, 50}, { 0, 14}, // 55: 0001 011x + { 0, 41}, { 0, 21}, // 56: 0001 100x + { 0, 49}, { 0, 13}, // 57: 0001 101x + { 0, 35}, { 0, 19}, // 58: 0001 110x + { 0, 11}, { 0, 7}, // 59: 0001 111x + { 0, 39}, { 0, 27}, // 60: 0000 0001x + { 0, 59}, { 0, 55}, // 61: 0000 0010x + { 0, 47}, { 0, 31}, // 62: 0000 0011x +}; + +static const plm_vlc_t PLM_VIDEO_MOTION[] = { + { 1 << 1, 0}, { 0, 0}, // 0: x + { 2 << 1, 0}, { 3 << 1, 0}, // 1: 0x + { 4 << 1, 0}, { 5 << 1, 0}, // 2: 00x + { 0, 1}, { 0, -1}, // 3: 01x + { 6 << 1, 0}, { 7 << 1, 0}, // 4: 000x + { 0, 2}, { 0, -2}, // 5: 001x + { 8 << 1, 0}, { 9 << 1, 0}, // 6: 0000x + { 0, 3}, { 0, -3}, // 7: 0001x + { 10 << 1, 0}, { 11 << 1, 0}, // 8: 0000 0x + { 12 << 1, 0}, { 13 << 1, 0}, // 9: 0000 1x + { -1, 0}, { 14 << 1, 0}, // 10: 0000 00x + { 15 << 1, 0}, { 16 << 1, 0}, // 11: 0000 01x + { 17 << 1, 0}, { 18 << 1, 0}, // 12: 0000 10x + { 0, 4}, { 0, -4}, // 13: 0000 11x + { -1, 0}, { 19 << 1, 0}, // 14: 0000 001x + { 20 << 1, 0}, { 21 << 1, 0}, // 15: 0000 010x + { 0, 7}, { 0, -7}, // 16: 0000 011x + { 0, 6}, { 0, -6}, // 17: 0000 100x + { 0, 5}, { 0, -5}, // 18: 0000 101x + { 22 << 1, 0}, { 23 << 1, 0}, // 19: 0000 0011x + { 24 << 1, 0}, { 25 << 1, 0}, // 20: 0000 0100x + { 26 << 1, 0}, { 27 << 1, 0}, // 21: 0000 0101x + { 28 << 1, 0}, { 29 << 1, 0}, // 22: 0000 0011 0x + { 30 << 1, 0}, { 31 << 1, 0}, // 23: 0000 0011 1x + { 32 << 1, 0}, { 33 << 1, 0}, // 24: 0000 0100 0x + { 0, 10}, { 0, -10}, // 25: 0000 0100 1x + { 0, 9}, { 0, -9}, // 26: 0000 0101 0x + { 0, 8}, { 0, -8}, // 27: 0000 0101 1x + { 0, 16}, { 0, -16}, // 28: 0000 0011 00x + { 0, 15}, { 0, -15}, // 29: 0000 0011 01x + { 0, 14}, { 0, -14}, // 30: 0000 0011 10x + { 0, 13}, { 0, -13}, // 31: 0000 0011 11x + { 0, 12}, { 0, -12}, // 32: 0000 0100 00x + { 0, 11}, { 0, -11}, // 33: 0000 0100 01x +}; + +static const plm_vlc_t PLM_VIDEO_DCT_SIZE_LUMINANCE[] = { + { 1 << 1, 0}, { 2 << 1, 0}, // 0: x + { 0, 1}, { 0, 2}, // 1: 0x + { 3 << 1, 0}, { 4 << 1, 0}, // 2: 1x + { 0, 0}, { 0, 3}, // 3: 10x + { 0, 4}, { 5 << 1, 0}, // 4: 11x + { 0, 5}, { 6 << 1, 0}, // 5: 111x + { 0, 6}, { 7 << 1, 0}, // 6: 1111x + { 0, 7}, { 8 << 1, 0}, // 7: 1111 1x + { 0, 8}, { -1, 0}, // 8: 1111 11x +}; + +static const plm_vlc_t PLM_VIDEO_DCT_SIZE_CHROMINANCE[] = { + { 1 << 1, 0}, { 2 << 1, 0}, // 0: x + { 0, 0}, { 0, 1}, // 1: 0x + { 0, 2}, { 3 << 1, 0}, // 2: 1x + { 0, 3}, { 4 << 1, 0}, // 3: 11x + { 0, 4}, { 5 << 1, 0}, // 4: 111x + { 0, 5}, { 6 << 1, 0}, // 5: 1111x + { 0, 6}, { 7 << 1, 0}, // 6: 1111 1x + { 0, 7}, { 8 << 1, 0}, // 7: 1111 11x + { 0, 8}, { -1, 0}, // 8: 1111 111x +}; + +static const plm_vlc_t *PLM_VIDEO_DCT_SIZE[] = { + PLM_VIDEO_DCT_SIZE_LUMINANCE, + PLM_VIDEO_DCT_SIZE_CHROMINANCE, + PLM_VIDEO_DCT_SIZE_CHROMINANCE +}; + + +// dct_coeff bitmap: +// 0xff00 run +// 0x00ff level + +// Decoded values are unsigned. Sign bit follows in the stream. + +static const plm_vlc_uint_t PLM_VIDEO_DCT_COEFF[] = { + { 1 << 1, 0}, { 0, 0x0001}, // 0: x + { 2 << 1, 0}, { 3 << 1, 0}, // 1: 0x + { 4 << 1, 0}, { 5 << 1, 0}, // 2: 00x + { 6 << 1, 0}, { 0, 0x0101}, // 3: 01x + { 7 << 1, 0}, { 8 << 1, 0}, // 4: 000x + { 9 << 1, 0}, { 10 << 1, 0}, // 5: 001x + { 0, 0x0002}, { 0, 0x0201}, // 6: 010x + { 11 << 1, 0}, { 12 << 1, 0}, // 7: 0000x + { 13 << 1, 0}, { 14 << 1, 0}, // 8: 0001x + { 15 << 1, 0}, { 0, 0x0003}, // 9: 0010x + { 0, 0x0401}, { 0, 0x0301}, // 10: 0011x + { 16 << 1, 0}, { 0, 0xffff}, // 11: 0000 0x + { 17 << 1, 0}, { 18 << 1, 0}, // 12: 0000 1x + { 0, 0x0701}, { 0, 0x0601}, // 13: 0001 0x + { 0, 0x0102}, { 0, 0x0501}, // 14: 0001 1x + { 19 << 1, 0}, { 20 << 1, 0}, // 15: 0010 0x + { 21 << 1, 0}, { 22 << 1, 0}, // 16: 0000 00x + { 0, 0x0202}, { 0, 0x0901}, // 17: 0000 10x + { 0, 0x0004}, { 0, 0x0801}, // 18: 0000 11x + { 23 << 1, 0}, { 24 << 1, 0}, // 19: 0010 00x + { 25 << 1, 0}, { 26 << 1, 0}, // 20: 0010 01x + { 27 << 1, 0}, { 28 << 1, 0}, // 21: 0000 000x + { 29 << 1, 0}, { 30 << 1, 0}, // 22: 0000 001x + { 0, 0x0d01}, { 0, 0x0006}, // 23: 0010 000x + { 0, 0x0c01}, { 0, 0x0b01}, // 24: 0010 001x + { 0, 0x0302}, { 0, 0x0103}, // 25: 0010 010x + { 0, 0x0005}, { 0, 0x0a01}, // 26: 0010 011x + { 31 << 1, 0}, { 32 << 1, 0}, // 27: 0000 0000x + { 33 << 1, 0}, { 34 << 1, 0}, // 28: 0000 0001x + { 35 << 1, 0}, { 36 << 1, 0}, // 29: 0000 0010x + { 37 << 1, 0}, { 38 << 1, 0}, // 30: 0000 0011x + { 39 << 1, 0}, { 40 << 1, 0}, // 31: 0000 0000 0x + { 41 << 1, 0}, { 42 << 1, 0}, // 32: 0000 0000 1x + { 43 << 1, 0}, { 44 << 1, 0}, // 33: 0000 0001 0x + { 45 << 1, 0}, { 46 << 1, 0}, // 34: 0000 0001 1x + { 0, 0x1001}, { 0, 0x0502}, // 35: 0000 0010 0x + { 0, 0x0007}, { 0, 0x0203}, // 36: 0000 0010 1x + { 0, 0x0104}, { 0, 0x0f01}, // 37: 0000 0011 0x + { 0, 0x0e01}, { 0, 0x0402}, // 38: 0000 0011 1x + { 47 << 1, 0}, { 48 << 1, 0}, // 39: 0000 0000 00x + { 49 << 1, 0}, { 50 << 1, 0}, // 40: 0000 0000 01x + { 51 << 1, 0}, { 52 << 1, 0}, // 41: 0000 0000 10x + { 53 << 1, 0}, { 54 << 1, 0}, // 42: 0000 0000 11x + { 55 << 1, 0}, { 56 << 1, 0}, // 43: 0000 0001 00x + { 57 << 1, 0}, { 58 << 1, 0}, // 44: 0000 0001 01x + { 59 << 1, 0}, { 60 << 1, 0}, // 45: 0000 0001 10x + { 61 << 1, 0}, { 62 << 1, 0}, // 46: 0000 0001 11x + { -1, 0}, { 63 << 1, 0}, // 47: 0000 0000 000x + { 64 << 1, 0}, { 65 << 1, 0}, // 48: 0000 0000 001x + { 66 << 1, 0}, { 67 << 1, 0}, // 49: 0000 0000 010x + { 68 << 1, 0}, { 69 << 1, 0}, // 50: 0000 0000 011x + { 70 << 1, 0}, { 71 << 1, 0}, // 51: 0000 0000 100x + { 72 << 1, 0}, { 73 << 1, 0}, // 52: 0000 0000 101x + { 74 << 1, 0}, { 75 << 1, 0}, // 53: 0000 0000 110x + { 76 << 1, 0}, { 77 << 1, 0}, // 54: 0000 0000 111x + { 0, 0x000b}, { 0, 0x0802}, // 55: 0000 0001 000x + { 0, 0x0403}, { 0, 0x000a}, // 56: 0000 0001 001x + { 0, 0x0204}, { 0, 0x0702}, // 57: 0000 0001 010x + { 0, 0x1501}, { 0, 0x1401}, // 58: 0000 0001 011x + { 0, 0x0009}, { 0, 0x1301}, // 59: 0000 0001 100x + { 0, 0x1201}, { 0, 0x0105}, // 60: 0000 0001 101x + { 0, 0x0303}, { 0, 0x0008}, // 61: 0000 0001 110x + { 0, 0x0602}, { 0, 0x1101}, // 62: 0000 0001 111x + { 78 << 1, 0}, { 79 << 1, 0}, // 63: 0000 0000 0001x + { 80 << 1, 0}, { 81 << 1, 0}, // 64: 0000 0000 0010x + { 82 << 1, 0}, { 83 << 1, 0}, // 65: 0000 0000 0011x + { 84 << 1, 0}, { 85 << 1, 0}, // 66: 0000 0000 0100x + { 86 << 1, 0}, { 87 << 1, 0}, // 67: 0000 0000 0101x + { 88 << 1, 0}, { 89 << 1, 0}, // 68: 0000 0000 0110x + { 90 << 1, 0}, { 91 << 1, 0}, // 69: 0000 0000 0111x + { 0, 0x0a02}, { 0, 0x0902}, // 70: 0000 0000 1000x + { 0, 0x0503}, { 0, 0x0304}, // 71: 0000 0000 1001x + { 0, 0x0205}, { 0, 0x0107}, // 72: 0000 0000 1010x + { 0, 0x0106}, { 0, 0x000f}, // 73: 0000 0000 1011x + { 0, 0x000e}, { 0, 0x000d}, // 74: 0000 0000 1100x + { 0, 0x000c}, { 0, 0x1a01}, // 75: 0000 0000 1101x + { 0, 0x1901}, { 0, 0x1801}, // 76: 0000 0000 1110x + { 0, 0x1701}, { 0, 0x1601}, // 77: 0000 0000 1111x + { 92 << 1, 0}, { 93 << 1, 0}, // 78: 0000 0000 0001 0x + { 94 << 1, 0}, { 95 << 1, 0}, // 79: 0000 0000 0001 1x + { 96 << 1, 0}, { 97 << 1, 0}, // 80: 0000 0000 0010 0x + { 98 << 1, 0}, { 99 << 1, 0}, // 81: 0000 0000 0010 1x + {100 << 1, 0}, {101 << 1, 0}, // 82: 0000 0000 0011 0x + {102 << 1, 0}, {103 << 1, 0}, // 83: 0000 0000 0011 1x + { 0, 0x001f}, { 0, 0x001e}, // 84: 0000 0000 0100 0x + { 0, 0x001d}, { 0, 0x001c}, // 85: 0000 0000 0100 1x + { 0, 0x001b}, { 0, 0x001a}, // 86: 0000 0000 0101 0x + { 0, 0x0019}, { 0, 0x0018}, // 87: 0000 0000 0101 1x + { 0, 0x0017}, { 0, 0x0016}, // 88: 0000 0000 0110 0x + { 0, 0x0015}, { 0, 0x0014}, // 89: 0000 0000 0110 1x + { 0, 0x0013}, { 0, 0x0012}, // 90: 0000 0000 0111 0x + { 0, 0x0011}, { 0, 0x0010}, // 91: 0000 0000 0111 1x + {104 << 1, 0}, {105 << 1, 0}, // 92: 0000 0000 0001 00x + {106 << 1, 0}, {107 << 1, 0}, // 93: 0000 0000 0001 01x + {108 << 1, 0}, {109 << 1, 0}, // 94: 0000 0000 0001 10x + {110 << 1, 0}, {111 << 1, 0}, // 95: 0000 0000 0001 11x + { 0, 0x0028}, { 0, 0x0027}, // 96: 0000 0000 0010 00x + { 0, 0x0026}, { 0, 0x0025}, // 97: 0000 0000 0010 01x + { 0, 0x0024}, { 0, 0x0023}, // 98: 0000 0000 0010 10x + { 0, 0x0022}, { 0, 0x0021}, // 99: 0000 0000 0010 11x + { 0, 0x0020}, { 0, 0x010e}, // 100: 0000 0000 0011 00x + { 0, 0x010d}, { 0, 0x010c}, // 101: 0000 0000 0011 01x + { 0, 0x010b}, { 0, 0x010a}, // 102: 0000 0000 0011 10x + { 0, 0x0109}, { 0, 0x0108}, // 103: 0000 0000 0011 11x + { 0, 0x0112}, { 0, 0x0111}, // 104: 0000 0000 0001 000x + { 0, 0x0110}, { 0, 0x010f}, // 105: 0000 0000 0001 001x + { 0, 0x0603}, { 0, 0x1002}, // 106: 0000 0000 0001 010x + { 0, 0x0f02}, { 0, 0x0e02}, // 107: 0000 0000 0001 011x + { 0, 0x0d02}, { 0, 0x0c02}, // 108: 0000 0000 0001 100x + { 0, 0x0b02}, { 0, 0x1f01}, // 109: 0000 0000 0001 101x + { 0, 0x1e01}, { 0, 0x1d01}, // 110: 0000 0000 0001 110x + { 0, 0x1c01}, { 0, 0x1b01}, // 111: 0000 0000 0001 111x +}; + +typedef struct { + int full_px; + int is_set; + int r_size; + int h; + int v; +} plm_video_motion_t; + +struct plm_video_t { + double framerate; + double pixel_aspect_ratio; // [jart] + double time; + int frames_decoded; + int width; + int height; + int mb_width; + int mb_height; + int mb_size; + + int luma_width; + int luma_height; + + int chroma_width; + int chroma_height; + + int start_code; + int picture_type; + + plm_video_motion_t motion_forward; + plm_video_motion_t motion_backward; + + int has_sequence_header; + + int quantizer_scale; + int slice_begin; + int macroblock_address; + + int mb_row; + int mb_col; + + int macroblock_type; + int macroblock_intra; + + int dc_predictor[3]; + + plm_buffer_t *buffer; + int destroy_buffer_when_done; + + plm_frame_t frame_current; + plm_frame_t frame_forward; + plm_frame_t frame_backward; + + uint8_t *frames_data; + + int block_data[64]; + uint8_t intra_quant_matrix[64]; + uint8_t non_intra_quant_matrix[64]; + + int has_reference_frame; + int assume_no_b_frames; +}; + +static inline uint8_t plm_clamp(int n) { + if (n > 255) { + n = 255; + } + else if (n < 0) { + n = 0; + } + return n; +} + +int plm_video_decode_sequence_header(plm_video_t *self); +void plm_video_init_frame(plm_video_t *self, plm_frame_t *frame, uint8_t *base); +void plm_video_decode_picture(plm_video_t *self); +void plm_video_decode_slice(plm_video_t *self, int slice); +void plm_video_decode_macroblock(plm_video_t *self); +void plm_video_decode_motion_vectors(plm_video_t *self); +int plm_video_decode_motion_vector(plm_video_t *self, int r_size, int motion); +void plm_video_predict_macroblock(plm_video_t *self); +void plm_video_copy_macroblock(plm_video_t *self, plm_frame_t *s, int motion_h, int motion_v); +void plm_video_interpolate_macroblock(plm_video_t *self, plm_frame_t *s, int motion_h, int motion_v); +void plm_video_process_macroblock(plm_video_t *self, uint8_t *s, uint8_t *d, int mh, int mb, int bs, int interp); +void plm_video_decode_block(plm_video_t *self, int block); +void plm_video_idct(int *block); + +plm_video_t * plm_video_create_with_buffer(plm_buffer_t *buffer, int destroy_when_done) { + plm_video_t *self = (plm_video_t *)PLM_MALLOC(sizeof(plm_video_t)); + memset(self, 0, sizeof(plm_video_t)); + + self->buffer = buffer; + self->destroy_buffer_when_done = destroy_when_done; + + // Attempt to decode the sequence header + self->start_code = plm_buffer_find_start_code(self->buffer, PLM_START_SEQUENCE); + if (self->start_code != -1) { + plm_video_decode_sequence_header(self); + } + return self; +} + +void plm_video_destroy(plm_video_t *self) { + if (self->destroy_buffer_when_done) { + plm_buffer_destroy(self->buffer); + } + + if (self->has_sequence_header) { + PLM_FREE(self->frames_data); + } + + PLM_FREE(self); +} + +double plm_video_get_framerate(plm_video_t *self) { + return plm_video_has_header(self) + ? self->framerate + : 0; +} + +double plm_video_get_pixel_aspect_ratio(plm_video_t *self) { // [jart] + return plm_video_has_header(self) + ? self->pixel_aspect_ratio + : 0; +} + +int plm_video_get_width(plm_video_t *self) { + return plm_video_has_header(self) + ? self->width + : 0; +} + +int plm_video_get_height(plm_video_t *self) { + return plm_video_has_header(self) + ? self->height + : 0; +} + +void plm_video_set_no_delay(plm_video_t *self, int no_delay) { + self->assume_no_b_frames = no_delay; +} + +double plm_video_get_time(plm_video_t *self) { + return self->time; +} + +void plm_video_set_time(plm_video_t *self, double time) { + self->frames_decoded = self->framerate * time; + self->time = time; +} + +void plm_video_rewind(plm_video_t *self) { + plm_buffer_rewind(self->buffer); + self->time = 0; + self->frames_decoded = 0; + self->has_reference_frame = FALSE; + self->start_code = -1; +} + +int plm_video_has_ended(plm_video_t *self) { + return plm_buffer_has_ended(self->buffer); +} + +plm_frame_t *plm_video_decode(plm_video_t *self) { + if (!plm_video_has_header(self)) { + return NULL; + } + + struct timespec tsc; // [jart] + tsc = timespec_real(); // [jart] + + plm_frame_t *frame = NULL; + do { + if (self->start_code != PLM_START_PICTURE) { + self->start_code = plm_buffer_find_start_code(self->buffer, PLM_START_PICTURE); + + if (self->start_code == -1) { + // If we reached the end of the file and the previously decoded + // frame was a reference frame, we still have to return it. + if ( + self->has_reference_frame && + !self->assume_no_b_frames && + plm_buffer_has_ended(self->buffer) && ( + self->picture_type == PLM_VIDEO_PICTURE_TYPE_INTRA || + self->picture_type == PLM_VIDEO_PICTURE_TYPE_PREDICTIVE + ) + ) { + self->has_reference_frame = FALSE; + frame = &self->frame_backward; + break; + } + + return NULL; + } + } + + // Make sure we have a full picture in the buffer before attempting to + // decode it. Sadly, this can only be done by seeking for the start code + // of the next picture. Also, if we didn't find the start code for the + // next picture, but the source has ended, we assume that this last + // picture is in the buffer. + if ( + plm_buffer_has_start_code(self->buffer, PLM_START_PICTURE) == -1 && + !plm_buffer_has_ended(self->buffer) + ) { + return NULL; + } + plm_buffer_discard_read_bytes(self->buffer); + + plm_video_decode_picture(self); + + if (self->assume_no_b_frames) { + frame = &self->frame_backward; + } + else if (self->picture_type == PLM_VIDEO_PICTURE_TYPE_B) { + frame = &self->frame_current; + } + else if (self->has_reference_frame) { + frame = &self->frame_forward; + } + else { + self->has_reference_frame = TRUE; + } + } while (!frame); + + frame->time = self->time; + self->frames_decoded++; + self->time = (double)self->frames_decoded / self->framerate; + + plmpegdecode_latency_ = timespec_tomicros(timespec_sub(timespec_real(), tsc)); + return frame; +} + +int plm_video_has_header(plm_video_t *self) { + if (self->has_sequence_header) { + return TRUE; + } + + if (self->start_code != PLM_START_SEQUENCE) { + self->start_code = plm_buffer_find_start_code(self->buffer, PLM_START_SEQUENCE); + } + if (self->start_code == -1) { + return FALSE; + } + + if (!plm_video_decode_sequence_header(self)) { + return FALSE; + } + + return TRUE; +} + +int plm_video_decode_sequence_header(plm_video_t *self) { + int max_header_size = 64 + 2 * 64 * 8; // 64 bit header + 2x 64 byte matrix + if (!plm_buffer_has(self->buffer, max_header_size)) { + return FALSE; + } + + self->width = plm_buffer_read(self->buffer, 12); + self->height = plm_buffer_read(self->buffer, 12); + + if (self->width <= 0 || self->height <= 0) { + return FALSE; + } + + // [jart] get pixel aspect ratio + int pixel_aspect_ratio_code; + pixel_aspect_ratio_code = plm_buffer_read(self->buffer, 4); + pixel_aspect_ratio_code -= 1; + if (pixel_aspect_ratio_code < 0) + pixel_aspect_ratio_code = 0; + int par_last = (sizeof(PLM_VIDEO_PIXEL_ASPECT_RATIO) / + sizeof(PLM_VIDEO_PIXEL_ASPECT_RATIO[0]) - 1); + if (pixel_aspect_ratio_code > par_last) + pixel_aspect_ratio_code = par_last; + self->pixel_aspect_ratio = + PLM_VIDEO_PIXEL_ASPECT_RATIO[pixel_aspect_ratio_code]; + + self->framerate = PLM_VIDEO_PICTURE_RATE[plm_buffer_read(self->buffer, 4)]; + + // Skip bit_rate, marker, buffer_size and constrained bit + plm_buffer_skip(self->buffer, 18 + 1 + 10 + 1); + + // Load custom intra quant matrix? + if (plm_buffer_read(self->buffer, 1)) { + for (int i = 0; i < 64; i++) { + int idx = PLM_VIDEO_ZIG_ZAG[i]; + self->intra_quant_matrix[idx] = plm_buffer_read(self->buffer, 8); + } + } + else { + memcpy(self->intra_quant_matrix, PLM_VIDEO_INTRA_QUANT_MATRIX, 64); + } + + // Load custom non intra quant matrix? + if (plm_buffer_read(self->buffer, 1)) { + for (int i = 0; i < 64; i++) { + int idx = PLM_VIDEO_ZIG_ZAG[i]; + self->non_intra_quant_matrix[idx] = plm_buffer_read(self->buffer, 8); + } + } + else { + memcpy(self->non_intra_quant_matrix, PLM_VIDEO_NON_INTRA_QUANT_MATRIX, 64); + } + + self->mb_width = (self->width + 15) >> 4; + self->mb_height = (self->height + 15) >> 4; + self->mb_size = self->mb_width * self->mb_height; + + self->luma_width = self->mb_width << 4; + self->luma_height = self->mb_height << 4; + + self->chroma_width = self->mb_width << 3; + self->chroma_height = self->mb_height << 3; + + + // Allocate one big chunk of data for all 3 frames = 9 planes + size_t luma_plane_size = self->luma_width * self->luma_height; + size_t chroma_plane_size = self->chroma_width * self->chroma_height; + size_t frame_data_size = (luma_plane_size + 2 * chroma_plane_size); + + self->frames_data = (uint8_t*)PLM_MALLOC(frame_data_size * 3); + plm_video_init_frame(self, &self->frame_current, self->frames_data + frame_data_size * 0); + plm_video_init_frame(self, &self->frame_forward, self->frames_data + frame_data_size * 1); + plm_video_init_frame(self, &self->frame_backward, self->frames_data + frame_data_size * 2); + + self->has_sequence_header = TRUE; + return TRUE; +} + +void plm_video_init_frame(plm_video_t *self, plm_frame_t *frame, uint8_t *base) { + size_t luma_plane_size = self->luma_width * self->luma_height; + size_t chroma_plane_size = self->chroma_width * self->chroma_height; + + frame->width = self->width; + frame->height = self->height; + frame->y.width = self->luma_width; + frame->y.height = self->luma_height; + frame->y.data = base; + + frame->cr.width = self->chroma_width; + frame->cr.height = self->chroma_height; + frame->cr.data = base + luma_plane_size; + + frame->cb.width = self->chroma_width; + frame->cb.height = self->chroma_height; + frame->cb.data = base + luma_plane_size + chroma_plane_size; +} + +void plm_video_decode_picture(plm_video_t *self) { + plm_buffer_skip(self->buffer, 10); // skip temporalReference + self->picture_type = plm_buffer_read(self->buffer, 3); + plm_buffer_skip(self->buffer, 16); // skip vbv_delay + + // D frames or unknown coding type + if (self->picture_type <= 0 || self->picture_type > PLM_VIDEO_PICTURE_TYPE_B) { + return; + } + + // Forward full_px, f_code + if ( + self->picture_type == PLM_VIDEO_PICTURE_TYPE_PREDICTIVE || + self->picture_type == PLM_VIDEO_PICTURE_TYPE_B + ) { + self->motion_forward.full_px = plm_buffer_read(self->buffer, 1); + int f_code = plm_buffer_read(self->buffer, 3); + if (f_code == 0) { + // Ignore picture with zero f_code + return; + } + self->motion_forward.r_size = f_code - 1; + } + + // Backward full_px, f_code + if (self->picture_type == PLM_VIDEO_PICTURE_TYPE_B) { + self->motion_backward.full_px = plm_buffer_read(self->buffer, 1); + int f_code = plm_buffer_read(self->buffer, 3); + if (f_code == 0) { + // Ignore picture with zero f_code + return; + } + self->motion_backward.r_size = f_code - 1; + } + + plm_frame_t frame_temp = self->frame_forward; + if ( + self->picture_type == PLM_VIDEO_PICTURE_TYPE_INTRA || + self->picture_type == PLM_VIDEO_PICTURE_TYPE_PREDICTIVE + ) { + self->frame_forward = self->frame_backward; + } + + + // Find first slice start code; skip extension and user data + do { + self->start_code = plm_buffer_next_start_code(self->buffer); + } while ( + self->start_code == PLM_START_EXTENSION || + self->start_code == PLM_START_USER_DATA + ); + + // Decode all slices + while (PLM_START_IS_SLICE(self->start_code)) { + plm_video_decode_slice(self, self->start_code & 0x000000FF); + if (self->macroblock_address >= self->mb_size - 2) { + break; + } + self->start_code = plm_buffer_next_start_code(self->buffer); + } + + // If this is a reference picture rotate the prediction pointers + if ( + self->picture_type == PLM_VIDEO_PICTURE_TYPE_INTRA || + self->picture_type == PLM_VIDEO_PICTURE_TYPE_PREDICTIVE + ) { + self->frame_backward = self->frame_current; + self->frame_current = frame_temp; + } +} + +void plm_video_decode_slice(plm_video_t *self, int slice) { + self->slice_begin = TRUE; + self->macroblock_address = (slice - 1) * self->mb_width - 1; + + // Reset motion vectors and DC predictors + self->motion_backward.h = self->motion_forward.h = 0; + self->motion_backward.v = self->motion_forward.v = 0; + self->dc_predictor[0] = 128; + self->dc_predictor[1] = 128; + self->dc_predictor[2] = 128; + + self->quantizer_scale = plm_buffer_read(self->buffer, 5); + + // Skip extra + while (plm_buffer_read(self->buffer, 1)) { + plm_buffer_skip(self->buffer, 8); + } + + do { + plm_video_decode_macroblock(self); + } while ( + self->macroblock_address < self->mb_size - 1 && + plm_buffer_peek_non_zero(self->buffer, 23) + ); +} + +void plm_video_decode_macroblock(plm_video_t *self) { + // Decode increment + int increment = 0; + int t = plm_buffer_read_vlc(self->buffer, PLM_VIDEO_MACROBLOCK_ADDRESS_INCREMENT); + + while (t == 34) { + // macroblock_stuffing + t = plm_buffer_read_vlc(self->buffer, PLM_VIDEO_MACROBLOCK_ADDRESS_INCREMENT); + } + while (t == 35) { + // macroblock_escape + increment += 33; + t = plm_buffer_read_vlc(self->buffer, PLM_VIDEO_MACROBLOCK_ADDRESS_INCREMENT); + } + increment += t; + + // Process any skipped macroblocks + if (self->slice_begin) { + // The first increment of each slice is relative to beginning of the + // previous row, not the previous macroblock + self->slice_begin = FALSE; + self->macroblock_address += increment; + } + else { + if (self->macroblock_address + increment >= self->mb_size) { + return; // invalid + } + if (increment > 1) { + // Skipped macroblocks reset DC predictors + self->dc_predictor[0] = 128; + self->dc_predictor[1] = 128; + self->dc_predictor[2] = 128; + + // Skipped macroblocks in P-pictures reset motion vectors + if (self->picture_type == PLM_VIDEO_PICTURE_TYPE_PREDICTIVE) { + self->motion_forward.h = 0; + self->motion_forward.v = 0; + } + } + + // Predict skipped macroblocks + while (increment > 1) { + self->macroblock_address++; + self->mb_row = self->macroblock_address / self->mb_width; + self->mb_col = self->macroblock_address % self->mb_width; + + plm_video_predict_macroblock(self); + increment--; + } + self->macroblock_address++; + } + + self->mb_row = self->macroblock_address / self->mb_width; + self->mb_col = self->macroblock_address % self->mb_width; + + if (self->mb_col >= self->mb_width || self->mb_row >= self->mb_height) { + return; // corrupt stream; + } + + // Process the current macroblock + const plm_vlc_t *table = PLM_VIDEO_MACROBLOCK_TYPE[self->picture_type]; + self->macroblock_type = plm_buffer_read_vlc(self->buffer, table); + + self->macroblock_intra = (self->macroblock_type & 0x01); + self->motion_forward.is_set = (self->macroblock_type & 0x08); + self->motion_backward.is_set = (self->macroblock_type & 0x04); + + // Quantizer scale + if ((self->macroblock_type & 0x10) != 0) { + self->quantizer_scale = plm_buffer_read(self->buffer, 5); + } + + if (self->macroblock_intra) { + // Intra-coded macroblocks reset motion vectors + self->motion_backward.h = self->motion_forward.h = 0; + self->motion_backward.v = self->motion_forward.v = 0; + } + else { + // Non-intra macroblocks reset DC predictors + self->dc_predictor[0] = 128; + self->dc_predictor[1] = 128; + self->dc_predictor[2] = 128; + + plm_video_decode_motion_vectors(self); + plm_video_predict_macroblock(self); + } + + // Decode blocks + int cbp = ((self->macroblock_type & 0x02) != 0) + ? plm_buffer_read_vlc(self->buffer, PLM_VIDEO_CODE_BLOCK_PATTERN) + : (self->macroblock_intra ? 0x3f : 0); + + for (int block = 0, mask = 0x20; block < 6; block++) { + if ((cbp & mask) != 0) { + plm_video_decode_block(self, block); + } + mask >>= 1; + } +} + +void plm_video_decode_motion_vectors(plm_video_t *self) { + + // Forward + if (self->motion_forward.is_set) { + int r_size = self->motion_forward.r_size; + self->motion_forward.h = plm_video_decode_motion_vector(self, r_size, self->motion_forward.h); + self->motion_forward.v = plm_video_decode_motion_vector(self, r_size, self->motion_forward.v); + } + else if (self->picture_type == PLM_VIDEO_PICTURE_TYPE_PREDICTIVE) { + // No motion information in P-picture, reset vectors + self->motion_forward.h = 0; + self->motion_forward.v = 0; + } + + if (self->motion_backward.is_set) { + int r_size = self->motion_backward.r_size; + self->motion_backward.h = plm_video_decode_motion_vector(self, r_size, self->motion_backward.h); + self->motion_backward.v = plm_video_decode_motion_vector(self, r_size, self->motion_backward.v); + } +} + +int plm_video_decode_motion_vector(plm_video_t *self, int r_size, int motion) { + int fscale = 1 << r_size; + int m_code = plm_buffer_read_vlc(self->buffer, PLM_VIDEO_MOTION); + int r = 0; + int d; + + if ((m_code != 0) && (fscale != 1)) { + r = plm_buffer_read(self->buffer, r_size); + d = ((abs(m_code) - 1) << r_size) + r + 1; + if (m_code < 0) { + d = -d; + } + } + else { + d = m_code; + } + + motion += d; + if (motion > (fscale << 4) - 1) { + motion -= fscale << 5; + } + else if (motion < ((-fscale) << 4)) { + motion += fscale << 5; + } + + return motion; +} + +void plm_video_predict_macroblock(plm_video_t *self) { + int fw_h = self->motion_forward.h; + int fw_v = self->motion_forward.v; + + if (self->motion_forward.full_px) { + fw_h <<= 1; + fw_v <<= 1; + } + + if (self->picture_type == PLM_VIDEO_PICTURE_TYPE_B) { + int bw_h = self->motion_backward.h; + int bw_v = self->motion_backward.v; + + if (self->motion_backward.full_px) { + bw_h <<= 1; + bw_v <<= 1; + } + + if (self->motion_forward.is_set) { + plm_video_copy_macroblock(self, &self->frame_forward, fw_h, fw_v); + if (self->motion_backward.is_set) { + plm_video_interpolate_macroblock(self, &self->frame_backward, bw_h, bw_v); + } + } + else { + plm_video_copy_macroblock(self, &self->frame_backward, bw_h, bw_v); + } + } + else { + plm_video_copy_macroblock(self, &self->frame_forward, fw_h, fw_v); + } +} + +void plm_video_copy_macroblock(plm_video_t *self, plm_frame_t *s, int motion_h, int motion_v) { + plm_frame_t *d = &self->frame_current; + plm_video_process_macroblock(self, s->y.data, d->y.data, motion_h, motion_v, 16, FALSE); + plm_video_process_macroblock(self, s->cr.data, d->cr.data, motion_h / 2, motion_v / 2, 8, FALSE); + plm_video_process_macroblock(self, s->cb.data, d->cb.data, motion_h / 2, motion_v / 2, 8, FALSE); +} + +void plm_video_interpolate_macroblock(plm_video_t *self, plm_frame_t *s, int motion_h, int motion_v) { + plm_frame_t *d = &self->frame_current; + plm_video_process_macroblock(self, s->y.data, d->y.data, motion_h, motion_v, 16, TRUE); + plm_video_process_macroblock(self, s->cr.data, d->cr.data, motion_h / 2, motion_v / 2, 8, TRUE); + plm_video_process_macroblock(self, s->cb.data, d->cb.data, motion_h / 2, motion_v / 2, 8, TRUE); +} + +#define PLM_BLOCK_SET(DEST, DEST_INDEX, DEST_WIDTH, SOURCE_INDEX, SOURCE_WIDTH, BLOCK_SIZE, OP) do { \ + int dest_scan = DEST_WIDTH - BLOCK_SIZE; \ + int source_scan = SOURCE_WIDTH - BLOCK_SIZE; \ + for (int y = 0; y < BLOCK_SIZE; y++) { \ + for (int x = 0; x < BLOCK_SIZE; x++) { \ + DEST[DEST_INDEX] = OP; \ + SOURCE_INDEX++; DEST_INDEX++; \ + } \ + SOURCE_INDEX += source_scan; \ + DEST_INDEX += dest_scan; \ + }} while(FALSE) + +void plm_video_process_macroblock( + plm_video_t *self, uint8_t *s, uint8_t *d, + int motion_h, int motion_v, int block_size, int interpolate +) { + int dw = self->mb_width * block_size; + + int hp = motion_h >> 1; + int vp = motion_v >> 1; + int odd_h = (motion_h & 1) == 1; + int odd_v = (motion_v & 1) == 1; + + unsigned int si = ((self->mb_row * block_size) + vp) * dw + (self->mb_col * block_size) + hp; + unsigned int di = (self->mb_row * dw + self->mb_col) * block_size; + + unsigned int max_address = (dw * (self->mb_height * block_size - block_size + 1) - block_size); + if (si > max_address || di > max_address) { + return; // corrupt video + } + + #define PLM_MB_CASE(INTERPOLATE, ODD_H, ODD_V, OP) \ + case ((INTERPOLATE << 2) | (ODD_H << 1) | (ODD_V)): \ + PLM_BLOCK_SET(d, di, dw, si, dw, block_size, OP); \ + break + + switch ((interpolate << 2) | (odd_h << 1) | (odd_v)) { + PLM_MB_CASE(0, 0, 0, (s[si])); + PLM_MB_CASE(0, 0, 1, (s[si] + s[si + dw] + 1) >> 1); + PLM_MB_CASE(0, 1, 0, (s[si] + s[si + 1] + 1) >> 1); + PLM_MB_CASE(0, 1, 1, (s[si] + s[si + 1] + s[si + dw] + s[si + dw + 1] + 2) >> 2); + + PLM_MB_CASE(1, 0, 0, (d[di] + (s[si]) + 1) >> 1); + PLM_MB_CASE(1, 0, 1, (d[di] + ((s[si] + s[si + dw] + 1) >> 1) + 1) >> 1); + PLM_MB_CASE(1, 1, 0, (d[di] + ((s[si] + s[si + 1] + 1) >> 1) + 1) >> 1); + PLM_MB_CASE(1, 1, 1, (d[di] + ((s[si] + s[si + 1] + s[si + dw] + s[si + dw + 1] + 2) >> 2) + 1) >> 1); + } + + #undef PLM_MB_CASE +} + +void plm_video_decode_block(plm_video_t *self, int block) { + + int n = 0; + uint8_t *quant_matrix; + + // Decode DC coefficient of intra-coded blocks + if (self->macroblock_intra) { + int predictor; + int dct_size; + + // DC prediction + int plane_index = block > 3 ? block - 3 : 0; + predictor = self->dc_predictor[plane_index]; + dct_size = plm_buffer_read_vlc(self->buffer, PLM_VIDEO_DCT_SIZE[plane_index]); + + // Read DC coeff + if (dct_size > 0) { + int differential = plm_buffer_read(self->buffer, dct_size); + if ((differential & (1 << (dct_size - 1))) != 0) { + self->block_data[0] = predictor + differential; + } + else { + self->block_data[0] = predictor + (-(1 << dct_size) | (differential + 1)); + } + } + else { + self->block_data[0] = predictor; + } + + // Save predictor value + self->dc_predictor[plane_index] = self->block_data[0]; + + // Dequantize + premultiply + self->block_data[0] <<= (3 + 5); + + quant_matrix = self->intra_quant_matrix; + n = 1; + } + else { + quant_matrix = self->non_intra_quant_matrix; + } + + // Decode AC coefficients (+DC for non-intra) + int level = 0; + while (TRUE) { + int run = 0; + uint16_t coeff = plm_buffer_read_vlc_uint(self->buffer, PLM_VIDEO_DCT_COEFF); + + if ((coeff == 0x0001) && (n > 0) && (plm_buffer_read(self->buffer, 1) == 0)) { + // end_of_block + break; + } + if (coeff == 0xffff) { + // escape + run = plm_buffer_read(self->buffer, 6); + level = plm_buffer_read(self->buffer, 8); + if (level == 0) { + level = plm_buffer_read(self->buffer, 8); + } + else if (level == 128) { + level = plm_buffer_read(self->buffer, 8) - 256; + } + else if (level > 128) { + level = level - 256; + } + } + else { + run = coeff >> 8; + level = coeff & 0xff; + if (plm_buffer_read(self->buffer, 1)) { + level = -level; + } + } + + n += run; + if (n < 0 || n >= 64) { + return; // invalid + } + + int de_zig_zagged = PLM_VIDEO_ZIG_ZAG[n]; + n++; + + // Dequantize, oddify, clip + level <<= 1; + if (!self->macroblock_intra) { + level += (level < 0 ? -1 : 1); + } + level = (level * self->quantizer_scale * quant_matrix[de_zig_zagged]) >> 4; + if ((level & 1) == 0) { + level -= level > 0 ? 1 : -1; + } + if (level > 2047) { + level = 2047; + } + else if (level < -2048) { + level = -2048; + } + + // Save premultiplied coefficient + self->block_data[de_zig_zagged] = level * PLM_VIDEO_PREMULTIPLIER_MATRIX[de_zig_zagged]; + } + + // Move block to its place + uint8_t *d; + int dw; + int di; + + if (block < 4) { + d = self->frame_current.y.data; + dw = self->luma_width; + di = (self->mb_row * self->luma_width + self->mb_col) << 4; + if ((block & 1) != 0) { + di += 8; + } + if ((block & 2) != 0) { + di += self->luma_width << 3; + } + } + else { + d = (block == 4) ? self->frame_current.cb.data : self->frame_current.cr.data; + dw = self->chroma_width; + di = ((self->mb_row * self->luma_width) << 2) + (self->mb_col << 3); + } + + int *s = self->block_data; + int si = 0; + if (self->macroblock_intra) { + // Overwrite (no prediction) + if (n == 1) { + int clamped = plm_clamp((s[0] + 128) >> 8); + PLM_BLOCK_SET(d, di, dw, si, 8, 8, clamped); + s[0] = 0; + } + else { + plm_video_idct(s); + PLM_BLOCK_SET(d, di, dw, si, 8, 8, plm_clamp(s[si])); + memset(self->block_data, 0, sizeof(self->block_data)); + } + } + else { + // Add data to the predicted macroblock + if (n == 1) { + int value = (s[0] + 128) >> 8; + PLM_BLOCK_SET(d, di, dw, si, 8, 8, plm_clamp(d[di] + value)); + s[0] = 0; + } + else { + plm_video_idct(s); + PLM_BLOCK_SET(d, di, dw, si, 8, 8, plm_clamp(d[di] + s[si])); + memset(self->block_data, 0, sizeof(self->block_data)); + } + } +} + +void plm_video_idct(int *block) { + int + b1, b3, b4, b6, b7, tmp1, tmp2, m0, + x0, x1, x2, x3, x4, y3, y4, y5, y6, y7; + + // Transform columns + for (int i = 0; i < 8; ++i) { + b1 = block[4 * 8 + i]; + b3 = block[2 * 8 + i] + block[6 * 8 + i]; + b4 = block[5 * 8 + i] - block[3 * 8 + i]; + tmp1 = block[1 * 8 + i] + block[7 * 8 + i]; + tmp2 = block[3 * 8 + i] + block[5 * 8 + i]; + b6 = block[1 * 8 + i] - block[7 * 8 + i]; + b7 = tmp1 + tmp2; + m0 = block[0 * 8 + i]; + x4 = ((b6 * 473 - b4 * 196 + 128) >> 8) - b7; + x0 = x4 - (((tmp1 - tmp2) * 362 + 128) >> 8); + x1 = m0 - b1; + x2 = (((block[2 * 8 + i] - block[6 * 8 + i]) * 362 + 128) >> 8) - b3; + x3 = m0 + b1; + y3 = x1 + x2; + y4 = x3 + b3; + y5 = x1 - x2; + y6 = x3 - b3; + y7 = -x0 - ((b4 * 473 + b6 * 196 + 128) >> 8); + block[0 * 8 + i] = b7 + y4; + block[1 * 8 + i] = x4 + y3; + block[2 * 8 + i] = y5 - x0; + block[3 * 8 + i] = y6 - y7; + block[4 * 8 + i] = y6 + y7; + block[5 * 8 + i] = x0 + y5; + block[6 * 8 + i] = y3 - x4; + block[7 * 8 + i] = y4 - b7; + } + + // Transform rows + for (int i = 0; i < 64; i += 8) { + b1 = block[4 + i]; + b3 = block[2 + i] + block[6 + i]; + b4 = block[5 + i] - block[3 + i]; + tmp1 = block[1 + i] + block[7 + i]; + tmp2 = block[3 + i] + block[5 + i]; + b6 = block[1 + i] - block[7 + i]; + b7 = tmp1 + tmp2; + m0 = block[0 + i]; + x4 = ((b6 * 473 - b4 * 196 + 128) >> 8) - b7; + x0 = x4 - (((tmp1 - tmp2) * 362 + 128) >> 8); + x1 = m0 - b1; + x2 = (((block[2 + i] - block[6 + i]) * 362 + 128) >> 8) - b3; + x3 = m0 + b1; + y3 = x1 + x2; + y4 = x3 + b3; + y5 = x1 - x2; + y6 = x3 - b3; + y7 = -x0 - ((b4 * 473 + b6 * 196 + 128) >> 8); + block[0 + i] = (b7 + y4 + 128) >> 8; + block[1 + i] = (x4 + y3 + 128) >> 8; + block[2 + i] = (y5 - x0 + 128) >> 8; + block[3 + i] = (y6 - y7 + 128) >> 8; + block[4 + i] = (y6 + y7 + 128) >> 8; + block[5 + i] = (x0 + y5 + 128) >> 8; + block[6 + i] = (y3 - x4 + 128) >> 8; + block[7 + i] = (y4 - b7 + 128) >> 8; + } +} + +// YCbCr conversion following the BT.601 standard: +// https://infogalactic.com/info/YCbCr#ITU-R_BT.601_conversion + +#define PLM_PUT_PIXEL(RI, GI, BI, Y_OFFSET, DEST_OFFSET) \ + y = ((frame->y.data[y_index + Y_OFFSET]-16) * 76309) >> 16; \ + dest[d_index + DEST_OFFSET + RI] = plm_clamp(y + r); \ + dest[d_index + DEST_OFFSET + GI] = plm_clamp(y - g); \ + dest[d_index + DEST_OFFSET + BI] = plm_clamp(y + b); + +#define PLM_DEFINE_FRAME_CONVERT_FUNCTION(NAME, BYTES_PER_PIXEL, RI, GI, BI) \ + void NAME(plm_frame_t *frame, uint8_t *dest, int stride) { \ + int cols = frame->width >> 1; \ + int rows = frame->height >> 1; \ + int yw = frame->y.width; \ + int cw = frame->cb.width; \ + for (int row = 0; row < rows; row++) { \ + int c_index = row * cw; \ + int y_index = row * 2 * yw; \ + int d_index = row * 2 * stride; \ + for (int col = 0; col < cols; col++) { \ + int y; \ + int cr = frame->cr.data[c_index] - 128; \ + int cb = frame->cb.data[c_index] - 128; \ + int r = (cr * 104597) >> 16; \ + int g = (cb * 25674 + cr * 53278) >> 16; \ + int b = (cb * 132201) >> 16; \ + PLM_PUT_PIXEL(RI, GI, BI, 0, 0); \ + PLM_PUT_PIXEL(RI, GI, BI, 1, BYTES_PER_PIXEL); \ + PLM_PUT_PIXEL(RI, GI, BI, yw, stride); \ + PLM_PUT_PIXEL(RI, GI, BI, yw + 1, stride + BYTES_PER_PIXEL); \ + c_index += 1; \ + y_index += 2; \ + d_index += 2 * BYTES_PER_PIXEL; \ + } \ + } \ + } + +PLM_DEFINE_FRAME_CONVERT_FUNCTION(plm_frame_to_rgb, 3, 0, 1, 2) +PLM_DEFINE_FRAME_CONVERT_FUNCTION(plm_frame_to_bgr, 3, 2, 1, 0) +PLM_DEFINE_FRAME_CONVERT_FUNCTION(plm_frame_to_rgba, 4, 0, 1, 2) +PLM_DEFINE_FRAME_CONVERT_FUNCTION(plm_frame_to_bgra, 4, 2, 1, 0) +PLM_DEFINE_FRAME_CONVERT_FUNCTION(plm_frame_to_argb, 4, 1, 2, 3) +PLM_DEFINE_FRAME_CONVERT_FUNCTION(plm_frame_to_abgr, 4, 3, 2, 1) + + +#undef PLM_PUT_PIXEL +#undef PLM_DEFINE_FRAME_CONVERT_FUNCTION + + + +// ----------------------------------------------------------------------------- +// plm_audio implementation + +// Based on kjmp2 by Martin J. Fiedler +// http://keyj.emphy.de/kjmp2/ + +static const int PLM_AUDIO_FRAME_SYNC = 0x7ff; + +static const int PLM_AUDIO_MPEG_2_5 = 0x0; +static const int PLM_AUDIO_MPEG_2 = 0x2; +static const int PLM_AUDIO_MPEG_1 = 0x3; + +static const int PLM_AUDIO_LAYER_III = 0x1; +static const int PLM_AUDIO_LAYER_II = 0x2; +static const int PLM_AUDIO_LAYER_I = 0x3; + +static const int PLM_AUDIO_MODE_STEREO = 0x0; +static const int PLM_AUDIO_MODE_JOINT_STEREO = 0x1; +static const int PLM_AUDIO_MODE_DUAL_CHANNEL = 0x2; +static const int PLM_AUDIO_MODE_MONO = 0x3; + +static const unsigned short PLM_AUDIO_SAMPLE_RATE[] = { + 44100, 48000, 32000, 0, // MPEG-1 + 22050, 24000, 16000, 0 // MPEG-2 +}; + +static const short PLM_AUDIO_BIT_RATE[] = { + 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, // MPEG-1 + 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 // MPEG-2 +}; + +static const int PLM_AUDIO_SCALEFACTOR_BASE[] = { + 0x02000000, 0x01965FEA, 0x01428A30 +}; + +static const float PLM_AUDIO_SYNTHESIS_WINDOW[] = { + 0.0, -0.5, -0.5, -0.5, -0.5, -0.5, + -0.5, -1.0, -1.0, -1.0, -1.0, -1.5, + -1.5, -2.0, -2.0, -2.5, -2.5, -3.0, + -3.5, -3.5, -4.0, -4.5, -5.0, -5.5, + -6.5, -7.0, -8.0, -8.5, -9.5, -10.5, + -12.0, -13.0, -14.5, -15.5, -17.5, -19.0, + -20.5, -22.5, -24.5, -26.5, -29.0, -31.5, + -34.0, -36.5, -39.5, -42.5, -45.5, -48.5, + -52.0, -55.5, -58.5, -62.5, -66.0, -69.5, + -73.5, -77.0, -80.5, -84.5, -88.0, -91.5, + -95.0, -98.0, -101.0, -104.0, 106.5, 109.0, + 111.0, 112.5, 113.5, 114.0, 114.0, 113.5, + 112.0, 110.5, 107.5, 104.0, 100.0, 94.5, + 88.5, 81.5, 73.0, 63.5, 53.0, 41.5, + 28.5, 14.5, -1.0, -18.0, -36.0, -55.5, + -76.5, -98.5, -122.0, -147.0, -173.5, -200.5, + -229.5, -259.5, -290.5, -322.5, -355.5, -389.5, + -424.0, -459.5, -495.5, -532.0, -568.5, -605.0, + -641.5, -678.0, -714.0, -749.0, -783.5, -817.0, + -849.0, -879.5, -908.5, -935.0, -959.5, -981.0, + -1000.5, -1016.0, -1028.5, -1037.5, -1042.5, -1043.5, + -1040.0, -1031.5, 1018.5, 1000.0, 976.0, 946.5, + 911.0, 869.5, 822.0, 767.5, 707.0, 640.0, + 565.5, 485.0, 397.0, 302.5, 201.0, 92.5, + -22.5, -144.0, -272.5, -407.0, -547.5, -694.0, + -846.0, -1003.0, -1165.0, -1331.5, -1502.0, -1675.5, + -1852.5, -2031.5, -2212.5, -2394.0, -2576.5, -2758.5, + -2939.5, -3118.5, -3294.5, -3467.5, -3635.5, -3798.5, + -3955.0, -4104.5, -4245.5, -4377.5, -4499.0, -4609.5, + -4708.0, -4792.5, -4863.5, -4919.0, -4958.0, -4979.5, + -4983.0, -4967.5, -4931.5, -4875.0, -4796.0, -4694.5, + -4569.5, -4420.0, -4246.0, -4046.0, -3820.0, -3567.0, + 3287.0, 2979.5, 2644.0, 2280.5, 1888.0, 1467.5, + 1018.5, 541.0, 35.0, -499.0, -1061.0, -1650.0, + -2266.5, -2909.0, -3577.0, -4270.0, -4987.5, -5727.5, + -6490.0, -7274.0, -8077.5, -8899.5, -9739.0, -10594.5, + -11464.5, -12347.0, -13241.0, -14144.5, -15056.0, -15973.5, + -16895.5, -17820.0, -18744.5, -19668.0, -20588.0, -21503.0, + -22410.5, -23308.5, -24195.0, -25068.5, -25926.5, -26767.0, + -27589.0, -28389.0, -29166.5, -29919.0, -30644.5, -31342.0, + -32009.5, -32645.0, -33247.0, -33814.5, -34346.0, -34839.5, + -35295.0, -35710.0, -36084.5, -36417.5, -36707.5, -36954.0, + -37156.5, -37315.0, -37428.0, -37496.0, 37519.0, 37496.0, + 37428.0, 37315.0, 37156.5, 36954.0, 36707.5, 36417.5, + 36084.5, 35710.0, 35295.0, 34839.5, 34346.0, 33814.5, + 33247.0, 32645.0, 32009.5, 31342.0, 30644.5, 29919.0, + 29166.5, 28389.0, 27589.0, 26767.0, 25926.5, 25068.5, + 24195.0, 23308.5, 22410.5, 21503.0, 20588.0, 19668.0, + 18744.5, 17820.0, 16895.5, 15973.5, 15056.0, 14144.5, + 13241.0, 12347.0, 11464.5, 10594.5, 9739.0, 8899.5, + 8077.5, 7274.0, 6490.0, 5727.5, 4987.5, 4270.0, + 3577.0, 2909.0, 2266.5, 1650.0, 1061.0, 499.0, + -35.0, -541.0, -1018.5, -1467.5, -1888.0, -2280.5, + -2644.0, -2979.5, 3287.0, 3567.0, 3820.0, 4046.0, + 4246.0, 4420.0, 4569.5, 4694.5, 4796.0, 4875.0, + 4931.5, 4967.5, 4983.0, 4979.5, 4958.0, 4919.0, + 4863.5, 4792.5, 4708.0, 4609.5, 4499.0, 4377.5, + 4245.5, 4104.5, 3955.0, 3798.5, 3635.5, 3467.5, + 3294.5, 3118.5, 2939.5, 2758.5, 2576.5, 2394.0, + 2212.5, 2031.5, 1852.5, 1675.5, 1502.0, 1331.5, + 1165.0, 1003.0, 846.0, 694.0, 547.5, 407.0, + 272.5, 144.0, 22.5, -92.5, -201.0, -302.5, + -397.0, -485.0, -565.5, -640.0, -707.0, -767.5, + -822.0, -869.5, -911.0, -946.5, -976.0, -1000.0, + 1018.5, 1031.5, 1040.0, 1043.5, 1042.5, 1037.5, + 1028.5, 1016.0, 1000.5, 981.0, 959.5, 935.0, + 908.5, 879.5, 849.0, 817.0, 783.5, 749.0, + 714.0, 678.0, 641.5, 605.0, 568.5, 532.0, + 495.5, 459.5, 424.0, 389.5, 355.5, 322.5, + 290.5, 259.5, 229.5, 200.5, 173.5, 147.0, + 122.0, 98.5, 76.5, 55.5, 36.0, 18.0, + 1.0, -14.5, -28.5, -41.5, -53.0, -63.5, + -73.0, -81.5, -88.5, -94.5, -100.0, -104.0, + -107.5, -110.5, -112.0, -113.5, -114.0, -114.0, + -113.5, -112.5, -111.0, -109.0, 106.5, 104.0, + 101.0, 98.0, 95.0, 91.5, 88.0, 84.5, + 80.5, 77.0, 73.5, 69.5, 66.0, 62.5, + 58.5, 55.5, 52.0, 48.5, 45.5, 42.5, + 39.5, 36.5, 34.0, 31.5, 29.0, 26.5, + 24.5, 22.5, 20.5, 19.0, 17.5, 15.5, + 14.5, 13.0, 12.0, 10.5, 9.5, 8.5, + 8.0, 7.0, 6.5, 5.5, 5.0, 4.5, + 4.0, 3.5, 3.5, 3.0, 2.5, 2.5, + 2.0, 2.0, 1.5, 1.5, 1.0, 1.0, + 1.0, 1.0, 0.5, 0.5, 0.5, 0.5, + 0.5, 0.5 +}; + +// Quantizer lookup, step 1: bitrate classes +static const uint8_t PLM_AUDIO_QUANT_LUT_STEP_1[2][16] = { + // 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384 <- bitrate + { 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2 }, // mono + // 16, 24, 28, 32, 40, 48, 56, 64, 80, 96,112,128,160,192 <- bitrate / chan + { 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2 } // stereo +}; + +// Quantizer lookup, step 2: bitrate class, sample rate -> B2 table idx, sblimit +#define PLM_AUDIO_QUANT_TAB_A (27 | 64) // Table 3-B.2a: high-rate, sblimit = 27 +#define PLM_AUDIO_QUANT_TAB_B (30 | 64) // Table 3-B.2b: high-rate, sblimit = 30 +#define PLM_AUDIO_QUANT_TAB_C 8 // Table 3-B.2c: low-rate, sblimit = 8 +#define PLM_AUDIO_QUANT_TAB_D 12 // Table 3-B.2d: low-rate, sblimit = 12 + +static const uint8_t QUANT_LUT_STEP_2[3][3] = { + //44.1 kHz, 48 kHz, 32 kHz + { PLM_AUDIO_QUANT_TAB_C, PLM_AUDIO_QUANT_TAB_C, PLM_AUDIO_QUANT_TAB_D }, // 32 - 48 kbit/sec/ch + { PLM_AUDIO_QUANT_TAB_A, PLM_AUDIO_QUANT_TAB_A, PLM_AUDIO_QUANT_TAB_A }, // 56 - 80 kbit/sec/ch + { PLM_AUDIO_QUANT_TAB_B, PLM_AUDIO_QUANT_TAB_A, PLM_AUDIO_QUANT_TAB_B } // 96+ kbit/sec/ch +}; + +// Quantizer lookup, step 3: B2 table, subband -> nbal, row index +// (upper 4 bits: nbal, lower 4 bits: row index) +static const uint8_t PLM_AUDIO_QUANT_LUT_STEP_3[3][32] = { + // Low-rate table (3-B.2c and 3-B.2d) + { + 0x44,0x44, + 0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34,0x34 + }, + // High-rate table (3-B.2a and 3-B.2b) + { + 0x43,0x43,0x43, + 0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x42, + 0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31,0x31, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20 + }, + // MPEG-2 LSR table (B.2 in ISO 13818-3) + { + 0x45,0x45,0x45,0x45, + 0x34,0x34,0x34,0x34,0x34,0x34,0x34, + 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24, + 0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24,0x24 + } +}; + +// Quantizer lookup, step 4: table row, allocation[] value -> quant table index +static const uint8_t PLM_AUDIO_QUANT_LUT_STEP_4[6][16] = { + { 0, 1, 2, 17 }, + { 0, 1, 2, 3, 4, 5, 6, 17 }, + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17 }, + { 0, 1, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }, + { 0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17 }, + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } +}; + +typedef struct plm_quantizer_spec_t { + unsigned short levels; + unsigned char group; + unsigned char bits; +} plm_quantizer_spec_t; + +static const plm_quantizer_spec_t PLM_AUDIO_QUANT_TAB[] = { + { 3, 1, 5 }, // 1 + { 5, 1, 7 }, // 2 + { 7, 0, 3 }, // 3 + { 9, 1, 10 }, // 4 + { 15, 0, 4 }, // 5 + { 31, 0, 5 }, // 6 + { 63, 0, 6 }, // 7 + { 127, 0, 7 }, // 8 + { 255, 0, 8 }, // 9 + { 511, 0, 9 }, // 10 + { 1023, 0, 10 }, // 11 + { 2047, 0, 11 }, // 12 + { 4095, 0, 12 }, // 13 + { 8191, 0, 13 }, // 14 + { 16383, 0, 14 }, // 15 + { 32767, 0, 15 }, // 16 + { 65535, 0, 16 } // 17 +}; + +struct plm_audio_t { + double time; + int samples_decoded; + int samplerate_index; + int bitrate_index; + int version; + int layer; + int mode; + int bound; + int v_pos; + int next_frame_data_size; + int has_header; + + plm_buffer_t *buffer; + int destroy_buffer_when_done; + + const plm_quantizer_spec_t *allocation[2][32]; + uint8_t scale_factor_info[2][32]; + int scale_factor[2][32][3]; + int sample[2][32][3]; + + plm_samples_t samples; + float D[1024]; + float V[2][1024]; + float U[32]; +}; + +int plm_audio_find_frame_sync(plm_audio_t *self); +int plm_audio_decode_header(plm_audio_t *self); +void plm_audio_decode_frame(plm_audio_t *self); +const plm_quantizer_spec_t *plm_audio_read_allocation(plm_audio_t *self, int sb, int tab3); +void plm_audio_read_samples(plm_audio_t *self, int ch, int sb, int part); +void plm_audio_idct36(int s[32][3], int ss, float *d, int dp); + +plm_audio_t *plm_audio_create_with_buffer(plm_buffer_t *buffer, int destroy_when_done) { + plm_audio_t *self = (plm_audio_t *)PLM_MALLOC(sizeof(plm_audio_t)); + memset(self, 0, sizeof(plm_audio_t)); + + self->samples.count = PLM_AUDIO_SAMPLES_PER_FRAME; + self->buffer = buffer; + self->destroy_buffer_when_done = destroy_when_done; + self->samplerate_index = 3; // Indicates 0 + + memcpy(self->D, PLM_AUDIO_SYNTHESIS_WINDOW, 512 * sizeof(float)); + memcpy(self->D + 512, PLM_AUDIO_SYNTHESIS_WINDOW, 512 * sizeof(float)); + + // Attempt to decode first header + self->next_frame_data_size = plm_audio_decode_header(self); + + return self; +} + +void plm_audio_destroy(plm_audio_t *self) { + if (self->destroy_buffer_when_done) { + plm_buffer_destroy(self->buffer); + } + PLM_FREE(self); +} + +int plm_audio_has_header(plm_audio_t *self) { + if (self->has_header) { + return TRUE; + } + + self->next_frame_data_size = plm_audio_decode_header(self); + return self->has_header; +} + +int plm_audio_get_samplerate(plm_audio_t *self) { + return plm_audio_has_header(self) + ? PLM_AUDIO_SAMPLE_RATE[self->samplerate_index] + : 0; +} + +double plm_audio_get_time(plm_audio_t *self) { + return self->time; +} + +void plm_audio_set_time(plm_audio_t *self, double time) { + self->samples_decoded = time * + (double)PLM_AUDIO_SAMPLE_RATE[self->samplerate_index]; + self->time = time; +} + +void plm_audio_rewind(plm_audio_t *self) { + plm_buffer_rewind(self->buffer); + self->time = 0; + self->samples_decoded = 0; + self->next_frame_data_size = 0; +} + +int plm_audio_has_ended(plm_audio_t *self) { + return plm_buffer_has_ended(self->buffer); +} + +plm_samples_t *plm_audio_decode(plm_audio_t *self) { + // Do we have at least enough information to decode the frame header? + if (!self->next_frame_data_size) { + if (!plm_buffer_has(self->buffer, 48)) { + return NULL; + } + self->next_frame_data_size = plm_audio_decode_header(self); + } + + if ( + self->next_frame_data_size == 0 || + !plm_buffer_has(self->buffer, self->next_frame_data_size << 3) + ) { + return NULL; + } + + plm_audio_decode_frame(self); + self->next_frame_data_size = 0; + + self->samples.time = self->time; + + self->samples_decoded += PLM_AUDIO_SAMPLES_PER_FRAME; + self->time = (double)self->samples_decoded / + (double)PLM_AUDIO_SAMPLE_RATE[self->samplerate_index]; + + return &self->samples; +} + +int plm_audio_find_frame_sync(plm_audio_t *self) { + size_t i; + for (i = self->buffer->bit_index >> 3; i < self->buffer->length-1; i++) { + if ( + self->buffer->bytes[i] == 0xFF && + (self->buffer->bytes[i+1] & 0xFE) == 0xFC + ) { + self->buffer->bit_index = ((i+1) << 3) + 3; + return TRUE; + } + } + self->buffer->bit_index = (i + 1) << 3; + return FALSE; +} + +int plm_audio_decode_header(plm_audio_t *self) { + if (!plm_buffer_has(self->buffer, 48)) { + return 0; + } + + plm_buffer_skip_bytes(self->buffer, 0x00); + int sync = plm_buffer_read(self->buffer, 11); + + + // Attempt to resync if no syncword was found. This sucks balls. The MP2 + // stream contains a syncword just before every frame (11 bits set to 1). + // However, this syncword is not guaranteed to not occur elsewhere in the + // stream. So, if we have to resync, we also have to check if the header + // (samplerate, bitrate) differs from the one we had before. This all + // may still lead to garbage data being decoded :/ + + if (sync != PLM_AUDIO_FRAME_SYNC && !plm_audio_find_frame_sync(self)) { + return 0; + } + + self->version = plm_buffer_read(self->buffer, 2); + self->layer = plm_buffer_read(self->buffer, 2); + int hasCRC = !plm_buffer_read(self->buffer, 1); + + if ( + self->version != PLM_AUDIO_MPEG_1 || + self->layer != PLM_AUDIO_LAYER_II + ) { + return 0; + } + + int bitrate_index = plm_buffer_read(self->buffer, 4) - 1; + if (bitrate_index > 13) { + return 0; + } + + int samplerate_index = plm_buffer_read(self->buffer, 2); + if (samplerate_index == 3) { + return 0; + } + + int padding = plm_buffer_read(self->buffer, 1); + plm_buffer_skip(self->buffer, 1); // f_private + int mode = plm_buffer_read(self->buffer, 2); + + // If we already have a header, make sure the samplerate, bitrate and mode + // are still the same, otherwise we might have missed sync. + if ( + self->has_header && ( + self->bitrate_index != bitrate_index || + self->samplerate_index != samplerate_index || + self->mode != mode + ) + ) { + return 0; + } + + self->bitrate_index = bitrate_index; + self->samplerate_index = samplerate_index; + self->mode = mode; + self->has_header = TRUE; + + // Parse the mode_extension, set up the stereo bound + if (mode == PLM_AUDIO_MODE_JOINT_STEREO) { + self->bound = (plm_buffer_read(self->buffer, 2) + 1) << 2; + } + else { + plm_buffer_skip(self->buffer, 2); + self->bound = (mode == PLM_AUDIO_MODE_MONO) ? 0 : 32; + } + + // Discard the last 4 bits of the header and the CRC value, if present + plm_buffer_skip(self->buffer, 4); // copyright(1), original(1), emphasis(2) + if (hasCRC) { + plm_buffer_skip(self->buffer, 16); + } + + // Compute frame size, check if we have enough data to decode the whole + // frame. + int bitrate = PLM_AUDIO_BIT_RATE[self->bitrate_index]; + int samplerate = PLM_AUDIO_SAMPLE_RATE[self->samplerate_index]; + int frame_size = (144000 * bitrate / samplerate) + padding; + return frame_size - (hasCRC ? 6 : 4); +} + +void plm_audio_decode_frame(plm_audio_t *self) { + // Prepare the quantizer table lookups + int tab3 = 0; + int sblimit = 0; + + int tab1 = (self->mode == PLM_AUDIO_MODE_MONO) ? 0 : 1; + int tab2 = PLM_AUDIO_QUANT_LUT_STEP_1[tab1][self->bitrate_index]; + tab3 = QUANT_LUT_STEP_2[tab2][self->samplerate_index]; + sblimit = tab3 & 63; + tab3 >>= 6; + + if (self->bound > sblimit) { + self->bound = sblimit; + } + + // Read the allocation information + for (int sb = 0; sb < self->bound; sb++) { + self->allocation[0][sb] = plm_audio_read_allocation(self, sb, tab3); + self->allocation[1][sb] = plm_audio_read_allocation(self, sb, tab3); + } + + for (int sb = self->bound; sb < sblimit; sb++) { + self->allocation[0][sb] = + self->allocation[1][sb] = + plm_audio_read_allocation(self, sb, tab3); + } + + // Read scale factor selector information + int channels = (self->mode == PLM_AUDIO_MODE_MONO) ? 1 : 2; + for (int sb = 0; sb < sblimit; sb++) { + for (int ch = 0; ch < channels; ch++) { + if (self->allocation[ch][sb]) { + self->scale_factor_info[ch][sb] = plm_buffer_read(self->buffer, 2); + } + } + if (self->mode == PLM_AUDIO_MODE_MONO) { + self->scale_factor_info[1][sb] = self->scale_factor_info[0][sb]; + } + } + + // Read scale factors + for (int sb = 0; sb < sblimit; sb++) { + for (int ch = 0; ch < channels; ch++) { + if (self->allocation[ch][sb]) { + int *sf = self->scale_factor[ch][sb]; + switch (self->scale_factor_info[ch][sb]) { + case 0: + sf[0] = plm_buffer_read(self->buffer, 6); + sf[1] = plm_buffer_read(self->buffer, 6); + sf[2] = plm_buffer_read(self->buffer, 6); + break; + case 1: + sf[0] = + sf[1] = plm_buffer_read(self->buffer, 6); + sf[2] = plm_buffer_read(self->buffer, 6); + break; + case 2: + sf[0] = + sf[1] = + sf[2] = plm_buffer_read(self->buffer, 6); + break; + case 3: + sf[0] = plm_buffer_read(self->buffer, 6); + sf[1] = + sf[2] = plm_buffer_read(self->buffer, 6); + break; + } + } + } + if (self->mode == PLM_AUDIO_MODE_MONO) { + self->scale_factor[1][sb][0] = self->scale_factor[0][sb][0]; + self->scale_factor[1][sb][1] = self->scale_factor[0][sb][1]; + self->scale_factor[1][sb][2] = self->scale_factor[0][sb][2]; + } + } + + // Coefficient input and reconstruction + int out_pos = 0; + for (int part = 0; part < 3; part++) { + for (int granule = 0; granule < 4; granule++) { + + // Read the samples + for (int sb = 0; sb < self->bound; sb++) { + plm_audio_read_samples(self, 0, sb, part); + plm_audio_read_samples(self, 1, sb, part); + } + for (int sb = self->bound; sb < sblimit; sb++) { + plm_audio_read_samples(self, 0, sb, part); + self->sample[1][sb][0] = self->sample[0][sb][0]; + self->sample[1][sb][1] = self->sample[0][sb][1]; + self->sample[1][sb][2] = self->sample[0][sb][2]; + } + for (int sb = sblimit; sb < 32; sb++) { + self->sample[0][sb][0] = 0; + self->sample[0][sb][1] = 0; + self->sample[0][sb][2] = 0; + self->sample[1][sb][0] = 0; + self->sample[1][sb][1] = 0; + self->sample[1][sb][2] = 0; + } + + // Synthesis loop + for (int p = 0; p < 3; p++) { + // Shifting step + self->v_pos = (self->v_pos - 64) & 1023; + + for (int ch = 0; ch < 2; ch++) { + plm_audio_idct36(self->sample[ch], p, self->V[ch], self->v_pos); + + // Build U, windowing, calculate output + memset(self->U, 0, sizeof(self->U)); + + int d_index = 512 - (self->v_pos >> 1); + int v_index = (self->v_pos % 128) >> 1; + while (v_index < 1024) { + for (int i = 0; i < 32; ++i) { + self->U[i] += self->D[d_index++] * self->V[ch][v_index++]; + } + + v_index += 128 - 32; + d_index += 64 - 32; + } + + d_index -= (512 - 32); + v_index = (128 - 32 + 1024) - v_index; + while (v_index < 1024) { + for (int i = 0; i < 32; ++i) { + self->U[i] += self->D[d_index++] * self->V[ch][v_index++]; + } + + v_index += 128 - 32; + d_index += 64 - 32; + } + + // Output samples + #ifdef PLM_AUDIO_SEPARATE_CHANNELS + float *out_channel = ch == 0 + ? self->samples.left + : self->samples.right; + for (int j = 0; j < 32; j++) { + out_channel[out_pos + j] = self->U[j] / 2147418112.0f; + } + #else + for (int j = 0; j < 32; j++) { + self->samples.interleaved[((out_pos + j) << 1) + ch] = + self->U[j] / 2147418112.0f; + } + #endif + } // End of synthesis channel loop + out_pos += 32; + } // End of synthesis sub-block loop + + } // Decoding of the granule finished + } + + plm_buffer_align(self->buffer); +} + +const plm_quantizer_spec_t *plm_audio_read_allocation(plm_audio_t *self, int sb, int tab3) { + int tab4 = PLM_AUDIO_QUANT_LUT_STEP_3[tab3][sb]; + int qtab = PLM_AUDIO_QUANT_LUT_STEP_4[tab4 & 15][plm_buffer_read(self->buffer, tab4 >> 4)]; + return qtab ? (&PLM_AUDIO_QUANT_TAB[qtab - 1]) : 0; +} + +void plm_audio_read_samples(plm_audio_t *self, int ch, int sb, int part) { + const plm_quantizer_spec_t *q = self->allocation[ch][sb]; + int sf = self->scale_factor[ch][sb][part]; + int *sample = self->sample[ch][sb]; + int val = 0; + + if (!q) { + // No bits allocated for this subband + sample[0] = sample[1] = sample[2] = 0; + return; + } + + // Resolve scalefactor + if (sf == 63) { + sf = 0; + } + else { + int shift = (sf / 3) | 0; + sf = (PLM_AUDIO_SCALEFACTOR_BASE[sf % 3] + ((1 << shift) >> 1)) >> shift; + } + + // Decode samples + int adj = q->levels; + if (q->group) { + // Decode grouped samples + val = plm_buffer_read(self->buffer, q->bits); + sample[0] = val % adj; + val /= adj; + sample[1] = val % adj; + sample[2] = val / adj; + } + else { + // Decode direct samples + sample[0] = plm_buffer_read(self->buffer, q->bits); + sample[1] = plm_buffer_read(self->buffer, q->bits); + sample[2] = plm_buffer_read(self->buffer, q->bits); + } + + // Postmultiply samples + int scale = 65536 / (adj + 1); + adj = ((adj + 1) >> 1) - 1; + + val = (adj - sample[0]) * scale; + sample[0] = (val * (sf >> 12) + ((val * (sf & 4095) + 2048) >> 12)) >> 12; + + val = (adj - sample[1]) * scale; + sample[1] = (val * (sf >> 12) + ((val * (sf & 4095) + 2048) >> 12)) >> 12; + + val = (adj - sample[2]) * scale; + sample[2] = (val * (sf >> 12) + ((val * (sf & 4095) + 2048) >> 12)) >> 12; +} + +void plm_audio_idct36(int s[32][3], int ss, float *d, int dp) { + float t01, t02, t03, t04, t05, t06, t07, t08, t09, t10, t11, t12, + t13, t14, t15, t16, t17, t18, t19, t20, t21, t22, t23, t24, + t25, t26, t27, t28, t29, t30, t31, t32, t33; + + t01 = (float)(s[0][ss] + s[31][ss]); t02 = (float)(s[0][ss] - s[31][ss]) * 0.500602998235f; + t03 = (float)(s[1][ss] + s[30][ss]); t04 = (float)(s[1][ss] - s[30][ss]) * 0.505470959898f; + t05 = (float)(s[2][ss] + s[29][ss]); t06 = (float)(s[2][ss] - s[29][ss]) * 0.515447309923f; + t07 = (float)(s[3][ss] + s[28][ss]); t08 = (float)(s[3][ss] - s[28][ss]) * 0.53104259109f; + t09 = (float)(s[4][ss] + s[27][ss]); t10 = (float)(s[4][ss] - s[27][ss]) * 0.553103896034f; + t11 = (float)(s[5][ss] + s[26][ss]); t12 = (float)(s[5][ss] - s[26][ss]) * 0.582934968206f; + t13 = (float)(s[6][ss] + s[25][ss]); t14 = (float)(s[6][ss] - s[25][ss]) * 0.622504123036f; + t15 = (float)(s[7][ss] + s[24][ss]); t16 = (float)(s[7][ss] - s[24][ss]) * 0.674808341455f; + t17 = (float)(s[8][ss] + s[23][ss]); t18 = (float)(s[8][ss] - s[23][ss]) * 0.744536271002f; + t19 = (float)(s[9][ss] + s[22][ss]); t20 = (float)(s[9][ss] - s[22][ss]) * 0.839349645416f; + t21 = (float)(s[10][ss] + s[21][ss]); t22 = (float)(s[10][ss] - s[21][ss]) * 0.972568237862f; + t23 = (float)(s[11][ss] + s[20][ss]); t24 = (float)(s[11][ss] - s[20][ss]) * 1.16943993343f; + t25 = (float)(s[12][ss] + s[19][ss]); t26 = (float)(s[12][ss] - s[19][ss]) * 1.48416461631f; + t27 = (float)(s[13][ss] + s[18][ss]); t28 = (float)(s[13][ss] - s[18][ss]) * 2.05778100995f; + t29 = (float)(s[14][ss] + s[17][ss]); t30 = (float)(s[14][ss] - s[17][ss]) * 3.40760841847f; + t31 = (float)(s[15][ss] + s[16][ss]); t32 = (float)(s[15][ss] - s[16][ss]) * 10.1900081235f; + + t33 = t01 + t31; t31 = (t01 - t31) * 0.502419286188f; + t01 = t03 + t29; t29 = (t03 - t29) * 0.52249861494f; + t03 = t05 + t27; t27 = (t05 - t27) * 0.566944034816f; + t05 = t07 + t25; t25 = (t07 - t25) * 0.64682178336f; + t07 = t09 + t23; t23 = (t09 - t23) * 0.788154623451f; + t09 = t11 + t21; t21 = (t11 - t21) * 1.06067768599f; + t11 = t13 + t19; t19 = (t13 - t19) * 1.72244709824f; + t13 = t15 + t17; t17 = (t15 - t17) * 5.10114861869f; + t15 = t33 + t13; t13 = (t33 - t13) * 0.509795579104f; + t33 = t01 + t11; t01 = (t01 - t11) * 0.601344886935f; + t11 = t03 + t09; t09 = (t03 - t09) * 0.899976223136f; + t03 = t05 + t07; t07 = (t05 - t07) * 2.56291544774f; + t05 = t15 + t03; t15 = (t15 - t03) * 0.541196100146f; + t03 = t33 + t11; t11 = (t33 - t11) * 1.30656296488f; + t33 = t05 + t03; t05 = (t05 - t03) * 0.707106781187f; + t03 = t15 + t11; t15 = (t15 - t11) * 0.707106781187f; + t03 += t15; + t11 = t13 + t07; t13 = (t13 - t07) * 0.541196100146f; + t07 = t01 + t09; t09 = (t01 - t09) * 1.30656296488f; + t01 = t11 + t07; t07 = (t11 - t07) * 0.707106781187f; + t11 = t13 + t09; t13 = (t13 - t09) * 0.707106781187f; + t11 += t13; t01 += t11; + t11 += t07; t07 += t13; + t09 = t31 + t17; t31 = (t31 - t17) * 0.509795579104f; + t17 = t29 + t19; t29 = (t29 - t19) * 0.601344886935f; + t19 = t27 + t21; t21 = (t27 - t21) * 0.899976223136f; + t27 = t25 + t23; t23 = (t25 - t23) * 2.56291544774f; + t25 = t09 + t27; t09 = (t09 - t27) * 0.541196100146f; + t27 = t17 + t19; t19 = (t17 - t19) * 1.30656296488f; + t17 = t25 + t27; t27 = (t25 - t27) * 0.707106781187f; + t25 = t09 + t19; t19 = (t09 - t19) * 0.707106781187f; + t25 += t19; + t09 = t31 + t23; t31 = (t31 - t23) * 0.541196100146f; + t23 = t29 + t21; t21 = (t29 - t21) * 1.30656296488f; + t29 = t09 + t23; t23 = (t09 - t23) * 0.707106781187f; + t09 = t31 + t21; t31 = (t31 - t21) * 0.707106781187f; + t09 += t31; t29 += t09; t09 += t23; t23 += t31; + t17 += t29; t29 += t25; t25 += t09; t09 += t27; + t27 += t23; t23 += t19; t19 += t31; + t21 = t02 + t32; t02 = (t02 - t32) * 0.502419286188f; + t32 = t04 + t30; t04 = (t04 - t30) * 0.52249861494f; + t30 = t06 + t28; t28 = (t06 - t28) * 0.566944034816f; + t06 = t08 + t26; t08 = (t08 - t26) * 0.64682178336f; + t26 = t10 + t24; t10 = (t10 - t24) * 0.788154623451f; + t24 = t12 + t22; t22 = (t12 - t22) * 1.06067768599f; + t12 = t14 + t20; t20 = (t14 - t20) * 1.72244709824f; + t14 = t16 + t18; t16 = (t16 - t18) * 5.10114861869f; + t18 = t21 + t14; t14 = (t21 - t14) * 0.509795579104f; + t21 = t32 + t12; t32 = (t32 - t12) * 0.601344886935f; + t12 = t30 + t24; t24 = (t30 - t24) * 0.899976223136f; + t30 = t06 + t26; t26 = (t06 - t26) * 2.56291544774f; + t06 = t18 + t30; t18 = (t18 - t30) * 0.541196100146f; + t30 = t21 + t12; t12 = (t21 - t12) * 1.30656296488f; + t21 = t06 + t30; t30 = (t06 - t30) * 0.707106781187f; + t06 = t18 + t12; t12 = (t18 - t12) * 0.707106781187f; + t06 += t12; + t18 = t14 + t26; t26 = (t14 - t26) * 0.541196100146f; + t14 = t32 + t24; t24 = (t32 - t24) * 1.30656296488f; + t32 = t18 + t14; t14 = (t18 - t14) * 0.707106781187f; + t18 = t26 + t24; t24 = (t26 - t24) * 0.707106781187f; + t18 += t24; t32 += t18; + t18 += t14; t26 = t14 + t24; + t14 = t02 + t16; t02 = (t02 - t16) * 0.509795579104f; + t16 = t04 + t20; t04 = (t04 - t20) * 0.601344886935f; + t20 = t28 + t22; t22 = (t28 - t22) * 0.899976223136f; + t28 = t08 + t10; t10 = (t08 - t10) * 2.56291544774f; + t08 = t14 + t28; t14 = (t14 - t28) * 0.541196100146f; + t28 = t16 + t20; t20 = (t16 - t20) * 1.30656296488f; + t16 = t08 + t28; t28 = (t08 - t28) * 0.707106781187f; + t08 = t14 + t20; t20 = (t14 - t20) * 0.707106781187f; + t08 += t20; + t14 = t02 + t10; t02 = (t02 - t10) * 0.541196100146f; + t10 = t04 + t22; t22 = (t04 - t22) * 1.30656296488f; + t04 = t14 + t10; t10 = (t14 - t10) * 0.707106781187f; + t14 = t02 + t22; t02 = (t02 - t22) * 0.707106781187f; + t14 += t02; t04 += t14; t14 += t10; t10 += t02; + t16 += t04; t04 += t08; t08 += t14; t14 += t28; + t28 += t10; t10 += t20; t20 += t02; t21 += t16; + t16 += t32; t32 += t04; t04 += t06; t06 += t08; + t08 += t18; t18 += t14; t14 += t30; t30 += t28; + t28 += t26; t26 += t10; t10 += t12; t12 += t20; + t20 += t24; t24 += t02; + + d[dp + 48] = -t33; + d[dp + 49] = d[dp + 47] = -t21; + d[dp + 50] = d[dp + 46] = -t17; + d[dp + 51] = d[dp + 45] = -t16; + d[dp + 52] = d[dp + 44] = -t01; + d[dp + 53] = d[dp + 43] = -t32; + d[dp + 54] = d[dp + 42] = -t29; + d[dp + 55] = d[dp + 41] = -t04; + d[dp + 56] = d[dp + 40] = -t03; + d[dp + 57] = d[dp + 39] = -t06; + d[dp + 58] = d[dp + 38] = -t25; + d[dp + 59] = d[dp + 37] = -t08; + d[dp + 60] = d[dp + 36] = -t11; + d[dp + 61] = d[dp + 35] = -t18; + d[dp + 62] = d[dp + 34] = -t09; + d[dp + 63] = d[dp + 33] = -t14; + d[dp + 32] = -t05; + d[dp + 0] = t05; d[dp + 31] = -t30; + d[dp + 1] = t30; d[dp + 30] = -t27; + d[dp + 2] = t27; d[dp + 29] = -t28; + d[dp + 3] = t28; d[dp + 28] = -t07; + d[dp + 4] = t07; d[dp + 27] = -t26; + d[dp + 5] = t26; d[dp + 26] = -t23; + d[dp + 6] = t23; d[dp + 25] = -t10; + d[dp + 7] = t10; d[dp + 24] = -t15; + d[dp + 8] = t15; d[dp + 23] = -t12; + d[dp + 9] = t12; d[dp + 22] = -t19; + d[dp + 10] = t19; d[dp + 21] = -t20; + d[dp + 11] = t20; d[dp + 20] = -t13; + d[dp + 12] = t13; d[dp + 19] = -t24; + d[dp + 13] = t24; d[dp + 18] = -t31; + d[dp + 14] = t31; d[dp + 17] = -t02; + d[dp + 15] = t02; d[dp + 16] = 0.0; +} + + +#endif // PL_MPEG_IMPLEMENTATION diff --git a/dsp/mpeg/plm.c b/dsp/mpeg/plm.c deleted file mode 100644 index 7704643ff..000000000 --- a/dsp/mpeg/plm.c +++ /dev/null @@ -1,332 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:4;tab-width:4;coding:utf-8 -*-│ -│ vi: set noet ft=c ts=4 sw=4 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer │ -│ Dominic Szablewski - https://phoboslab.org │ -│ │ -│ The MIT License(MIT) │ -│ Copyright(c) 2019 Dominic Szablewski │ -│ │ -│ Permission is hereby granted, free of charge, to any person obtaining │ -│ a copy of this software and associated documentation files(the │ -│ "Software"), to deal in the Software without restriction, including │ -│ without limitation the rights to use, copy, modify, merge, publish, │ -│ distribute, sublicense, and / or sell copies of the Software, and to │ -│ permit persons to whom the Software is furnished to do so, subject to │ -│ the following conditions: │ -│ │ -│ The above copyright notice and this permission notice shall be │ -│ included in all copies or substantial portions of the Software. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, │ -│ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF │ -│ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND │ -│ NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE │ -│ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN │ -│ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN │ -│ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE │ -│ SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "dsp/mpeg/mpeg.h" -#include "libc/log/log.h" -#include "libc/mem/mem.h" -#include "libc/stdio/stdio.h" -#include "libc/str/str.h" -__static_yoink("pl_mpeg_notice"); - -/* clang-format off */ -// ----------------------------------------------------------------------------- -// plm (high-level interface) implementation - -typedef struct plm_t { - plm_demux_t *demux; - double time; - int has_ended; - int loop; - - int video_packet_type; - plm_buffer_t *video_buffer; - plm_video_t *video_decoder; - - int audio_packet_type; - double audio_lead_time; - plm_buffer_t *audio_buffer; - plm_audio_t *audio_decoder; - - plm_video_decode_callback video_decode_callback; - void *video_decode_callback_user_data; - - plm_audio_decode_callback audio_decode_callback; - void *audio_decode_callback_user_data; -} plm_t; - -void plm_handle_end(plm_t *self); -void plm_read_video_packet(plm_buffer_t *buffer, void *user); -void plm_read_audio_packet(plm_buffer_t *buffer, void *user); -void plm_read_packets(plm_t *self, int requested_type); - -plm_t *plm_create_with_filename(const char *filename) { - plm_buffer_t *buffer = plm_buffer_create_with_filename(filename); - if (!buffer) { - return NULL; - } - return plm_create_with_buffer(buffer, true); -} - -plm_t *plm_create_with_file(FILE *fh, int close_when_done) { - plm_buffer_t *buffer = plm_buffer_create_with_file(fh, close_when_done); - return plm_create_with_buffer(buffer, true); -} - -plm_t *plm_create_with_memory(uint8_t *bytes, size_t length, int free_when_done) { - plm_buffer_t *buffer = plm_buffer_create_with_memory(bytes, length, free_when_done); - return plm_create_with_buffer(buffer, true); -} - -plm_t *plm_create_with_buffer(plm_buffer_t *buffer, int destroy_when_done) { - plm_t *self = (plm_t *)malloc(sizeof(plm_t)); - memset(self, 0, sizeof(plm_t)); - - self->demux = plm_demux_create(buffer, destroy_when_done); - - // In theory we should check plm_demux_get_num_video_streams() and - // plm_demux_get_num_audio_streams() here, but older files typically - // do not specify these correctly. So we just assume we have a video and - // audio stream and create the decoders. - - self->video_packet_type = PLM_DEMUX_PACKET_VIDEO_1; - self->video_buffer = plm_buffer_create_with_capacity(PLM_BUFFER_DEFAULT_SIZE); - plm_buffer_set_load_callback(self->video_buffer, plm_read_video_packet, self); - - self->audio_packet_type = PLM_DEMUX_PACKET_AUDIO_1; - self->audio_buffer = plm_buffer_create_with_capacity(PLM_BUFFER_DEFAULT_SIZE); - plm_buffer_set_load_callback(self->audio_buffer, plm_read_audio_packet, self); - - self->video_decoder = plm_video_create_with_buffer(self->video_buffer, true); - self->audio_decoder = plm_audio_create_with_buffer(self->audio_buffer, true); - - return self; -} - -void plm_destroy(plm_t *self) { - plm_video_destroy(self->video_decoder); - plm_audio_destroy(self->audio_decoder); - plm_demux_destroy(self->demux); - free(self); -} - -int plm_get_audio_enabled(plm_t *self) { - return (self->audio_packet_type != 0); -} - -void plm_set_audio_enabled(plm_t *self, int enabled, int stream_index) { - /* int num_streams = plm_demux_get_num_audio_streams(self->demux); */ - self->audio_packet_type = (enabled && stream_index >= 0 && stream_index < 4) - ? PLM_DEMUX_PACKET_AUDIO_1 + stream_index - : 0; -} - -int plm_get_video_enabled(plm_t *self) { - return (self->video_packet_type != 0); -} - -void plm_set_video_enabled(plm_t *self, int enabled) { - self->video_packet_type = (enabled) - ? PLM_DEMUX_PACKET_VIDEO_1 - : 0; -} - -int plm_get_width(plm_t *self) { - return plm_video_get_width(self->video_decoder); -} - -double plm_get_pixel_aspect_ratio(plm_t *self) { - return plm_video_get_pixel_aspect_ratio(self->video_decoder); -} - -int plm_get_height(plm_t *self) { - return plm_video_get_height(self->video_decoder); -} - -double plm_get_framerate(plm_t *self) { - return plm_video_get_framerate(self->video_decoder); -} - -int plm_get_num_audio_streams(plm_t *self) { - // Some files do not specify the number of audio streams in the system header. - // If the reported number of streams is 0, we check if we have a samplerate, - // indicating at least one audio stream. - int num_streams = plm_demux_get_num_audio_streams(self->demux); - return num_streams == 0 && plm_get_samplerate(self) ? 1 : num_streams; -} - -int plm_get_samplerate(plm_t *self) { - return plm_audio_get_samplerate(self->audio_decoder); -} - -double plm_get_audio_lead_time(plm_t *self) { - return self->audio_lead_time; -} - -void plm_set_audio_lead_time(plm_t *self, double lead_time) { - self->audio_lead_time = lead_time; -} - -double plm_get_time(plm_t *self) { - return self->time; -} - -void plm_rewind(plm_t *self) { - plm_video_rewind(self->video_decoder); - plm_audio_rewind(self->audio_decoder); - plm_demux_rewind(self->demux); - self->time = 0; -} - -int plm_get_loop(plm_t *self) { - return self->loop; -} - -void plm_set_loop(plm_t *self, int loop) { - self->loop = loop; -} - -int plm_has_ended(plm_t *self) { - return self->has_ended; -} - -void plm_set_video_decode_callback(plm_t *self, plm_video_decode_callback fp, void *user) { - self->video_decode_callback = fp; - self->video_decode_callback_user_data = user; -} - -void plm_set_audio_decode_callback(plm_t *self, plm_audio_decode_callback fp, void *user) { - self->audio_decode_callback = fp; - self->audio_decode_callback_user_data = user; -} - -int plm_decode(plm_t *self, double tick) { - DEBUGF("%s", "plm_decode"); - - int decode_video = (self->video_decode_callback && self->video_packet_type); - int decode_audio = (self->audio_decode_callback && self->audio_packet_type); - - if (!decode_video && !decode_audio) { - // Nothing to do here - return false; - } - - int did_decode = false; - int video_ended = false; - int audio_ended = false; - - double video_target_time = self->time + tick; - double audio_target_time = self->time + tick; - - if (self->audio_lead_time > 0 && decode_audio) { - video_target_time -= self->audio_lead_time; - } - else { - audio_target_time -= self->audio_lead_time; - } - - do { - did_decode = false; - - if (decode_video && plm_video_get_time(self->video_decoder) < video_target_time) { - plm_frame_t *frame = plm_video_decode(self->video_decoder); - if (frame) { - self->video_decode_callback(self, frame, self->video_decode_callback_user_data); - did_decode = true; - } - else { - video_ended = true; - } - } - - if (decode_audio && plm_audio_get_time(self->audio_decoder) < audio_target_time) { - plm_samples_t *samples = plm_audio_decode(self->audio_decoder); - if (samples) { - self->audio_decode_callback(self, samples, self->audio_decode_callback_user_data); - did_decode = true; - } - else { - audio_ended = true; - } - } - } while (did_decode); - - // We wanted to decode something but failed -> the source must have ended - if ((!decode_video || video_ended) && (!decode_audio || audio_ended)) { - plm_handle_end(self); - } - else { - self->time += tick; - } - - return did_decode ? true : false; -} - -plm_frame_t *plm_decode_video(plm_t *self) { - if (!self->video_packet_type) { - return NULL; - } - - plm_frame_t *frame = plm_video_decode(self->video_decoder); - if (frame) { - self->time = frame->time; - } - else { - plm_handle_end(self); - } - return frame; -} - -plm_samples_t *plm_decode_audio(plm_t *self) { - if (!self->audio_packet_type) { - return NULL; - } - - plm_samples_t *samples = plm_audio_decode(self->audio_decoder); - if (samples) { - self->time = samples->time; - } - else { - plm_handle_end(self); - } - return samples; -} - -void plm_handle_end(plm_t *self) { - if (self->loop) { - plm_rewind(self); - } - else { - self->has_ended = true; - } -} - -void plm_read_video_packet(plm_buffer_t *buffer, void *user) { - plm_t *self = (plm_t *)user; - plm_read_packets(self, self->video_packet_type); -} - -void plm_read_audio_packet(plm_buffer_t *buffer, void *user) { - plm_t *self = (plm_t *)user; - plm_read_packets(self, self->audio_packet_type); -} - -void plm_read_packets(plm_t *self, int requested_type) { - plm_packet_t *packet; - while ((packet = plm_demux_decode(self->demux))) { - if (packet->type == self->video_packet_type) { - plm_buffer_write(self->video_buffer, packet->data, packet->length); - } - else if (packet->type == self->audio_packet_type) { - plm_buffer_write(self->audio_buffer, packet->data, packet->length); - } - if (packet->type == requested_type) { - return; - } - } -} diff --git a/dsp/mpeg/slowrgb.c b/dsp/mpeg/slowrgb.c deleted file mode 100644 index 35971f17f..000000000 --- a/dsp/mpeg/slowrgb.c +++ /dev/null @@ -1,83 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:4;tab-width:4;coding:utf-8 -*-│ -│ vi: set et ft=c ts=4 sw=4 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer │ -│ Dominic Szablewski - https://phoboslab.org │ -│ │ -│ The MIT License(MIT) │ -│ Copyright(c) 2019 Dominic Szablewski │ -│ │ -│ Permission is hereby granted, free of charge, to any person obtaining │ -│ a copy of this software and associated documentation files(the │ -│ "Software"), to deal in the Software without restriction, including │ -│ without limitation the rights to use, copy, modify, merge, publish, │ -│ distribute, sublicense, and / or sell copies of the Software, and to │ -│ permit persons to whom the Software is furnished to do so, subject to │ -│ the following conditions: │ -│ │ -│ The above copyright notice and this permission notice shall be │ -│ included in all copies or substantial portions of the Software. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, │ -│ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF │ -│ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND │ -│ NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE │ -│ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN │ -│ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN │ -│ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE │ -│ SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "dsp/mpeg/mpeg.h" -#include "libc/macros.h" -__static_yoink("pl_mpeg_notice"); - -/** - * @see YCbCr2RGB() in tool/viz/lib/ycbcr2rgb.c - */ -void plm_frame_to_rgb(plm_frame_t *frame, uint8_t *rgb) { - // Chroma values are the same for each block of 4 pixels, so we process - // 2 lines at a time, 2 neighboring pixels each. - int w = frame->y.width, w2 = w >> 1; - int y_index1 = 0, y_index2 = w, y_next_2_lines = w + (w - frame->width); - int c_index = 0, c_next_line = w2 - (frame->width >> 1); - int rgb_index1 = 0, rgb_index2 = frame->width * 3, - rgb_next_2_lines = frame->width * 3; - int cols = frame->width >> 1, rows = frame->height >> 1; - int ccb, ccr, r, g, b; - uint8_t *y = frame->y.data, *cb = frame->cb.data, *cr = frame->cr.data; - for (int row = 0; row < rows; row++) { - for (int col = 0; col < cols; col++) { - ccb = cb[c_index]; - ccr = cr[c_index]; - c_index++; - r = (ccr + ((ccr * 103) >> 8)) - 179; - g = ((ccb * 88) >> 8) - 44 + ((ccr * 183) >> 8) - 91; - b = (ccb + ((ccb * 198) >> 8)) - 227; - // Line 1 - int y1 = y[y_index1++]; - int y2 = y[y_index1++]; - rgb[rgb_index1 + 0] = MAX(0, MIN(255, y1 + r)); - rgb[rgb_index1 + 1] = MAX(0, MIN(255, y1 - g)); - rgb[rgb_index1 + 2] = MAX(0, MIN(255, y1 + b)); - rgb[rgb_index1 + 3] = MAX(0, MIN(255, y2 + r)); - rgb[rgb_index1 + 4] = MAX(0, MIN(255, y2 - g)); - rgb[rgb_index1 + 5] = MAX(0, MIN(255, y2 + b)); - rgb_index1 += 6; - // Line 2 - int y3 = y[y_index2++]; - int y4 = y[y_index2++]; - rgb[rgb_index2 + 0] = MAX(0, MIN(255, y3 + r)); - rgb[rgb_index2 + 1] = MAX(0, MIN(255, y3 - g)); - rgb[rgb_index2 + 2] = MAX(0, MIN(255, y3 + b)); - rgb[rgb_index2 + 3] = MAX(0, MIN(255, y4 + r)); - rgb[rgb_index2 + 4] = MAX(0, MIN(255, y4 - g)); - rgb[rgb_index2 + 5] = MAX(0, MIN(255, y4 + b)); - rgb_index2 += 6; - } - y_index1 += y_next_2_lines; - y_index2 += y_next_2_lines; - rgb_index1 += rgb_next_2_lines; - rgb_index2 += rgb_next_2_lines; - c_index += c_next_line; - } -} diff --git a/dsp/mpeg/video.h b/dsp/mpeg/video.h deleted file mode 100644 index d2ed05181..000000000 --- a/dsp/mpeg/video.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef COSMOPOLITAN_DSP_MPEG_VIDEO_H_ -#define COSMOPOLITAN_DSP_MPEG_VIDEO_H_ -#include "dsp/mpeg/mpeg.h" -COSMOPOLITAN_C_START_ - -typedef struct { - int full_px; - int is_set; - int r_size; - int h; - int v; -} plm_video_motion_t; - -typedef struct plm_video_t { - double framerate; - double time; - double pixel_aspect_ratio; - int frames_decoded; - int width; - int height; - int mb_width; - int mb_height; - int mb_size; - int luma_width; - int luma_height; - int chroma_width; - int chroma_height; - int start_code; - int picture_type; - plm_video_motion_t motion_forward; - plm_video_motion_t motion_backward; - int has_sequence_header; - int quantizer_scale; - int slice_begin; - int macroblock_address; - int mb_row; - int mb_col; - int macroblock_type; - int macroblock_intra; - int dc_predictor[3]; - plm_buffer_t *buffer; - int destroy_buffer_when_done; - plm_frame_t frame_current; - plm_frame_t frame_forward; - plm_frame_t frame_backward; - uint8_t *frames_data; - int block_data[64]; - uint8_t intra_quant_matrix[64]; - uint8_t non_intra_quant_matrix[64]; - int has_reference_frame; - int assume_no_b_frames; -} plm_video_t; - -void plm_video_process_macroblock_8(plm_video_t *, uint8_t *, uint8_t *, int, - int, bool); -void plm_video_process_macroblock_16(plm_video_t *, uint8_t *, uint8_t *, int, - int, bool); - -COSMOPOLITAN_C_END_ -#endif /* COSMOPOLITAN_DSP_MPEG_VIDEO_H_ */ diff --git a/dsp/scale/cdecimate2xuint8x8.c b/dsp/scale/cdecimate2xuint8x8.c index 4a7e8a084..2a96284d9 100644 --- a/dsp/scale/cdecimate2xuint8x8.c +++ b/dsp/scale/cdecimate2xuint8x8.c @@ -16,17 +16,20 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/assert.h" -#include "libc/intrin/packuswb.h" -#include "libc/intrin/paddw.h" -#include "libc/intrin/palignr.h" -#include "libc/intrin/pmaddubsw.h" -#include "libc/intrin/psraw.h" -#include "libc/log/check.h" -#include "libc/log/log.h" +#include "libc/macros.h" #include "libc/nexgen32e/x86feature.h" #include "libc/str/str.h" +#include "third_party/intel/immintrin.internal.h" +/** + * Performs 2D Motion Picture Convolution Acceleration by Leveraging SSSE3. + * + * @note H/T John Costella, Jean-Baptiste Joseph Fourier + * @note RIP Huixiang Chen + */ +void *cDecimate2xUint8x8(unsigned long n, unsigned char A[n], + const signed char K[8]) { +#ifdef __x86_64__ #define TAPS 8 #define RATIO 2 #define OFFSET 3 @@ -37,62 +40,107 @@ #define LOOKAHEAD (SPREAD - LOOKBEHIND) #define SCALE 5 #define ROUND (1 << (SCALE - 1)) - -/** - * Performs 2D Motion Picture Convolution Acceleration by Leveraging SSSE3. - * - * @note H/T John Costella, Jean-Baptiste Joseph Fourier - * @note RIP Huixiang Chen - */ -void *cDecimate2xUint8x8(unsigned long n, unsigned char A[n], - const signed char K[8]) { - short kRound[8] = {ROUND, ROUND, ROUND, ROUND, ROUND, ROUND, ROUND, ROUND}; - signed char kMadd1[16] = {K[0], K[1], K[0], K[1], K[0], K[1], K[0], K[1], - K[0], K[1], K[0], K[1], K[0], K[1], K[0], K[1]}; - signed char kMadd2[16] = {K[2], K[3], K[2], K[3], K[2], K[3], K[2], K[3], - K[2], K[3], K[2], K[3], K[2], K[3], K[2], K[3]}; - signed char kMadd3[16] = {K[4], K[5], K[4], K[5], K[4], K[5], K[4], K[5], - K[4], K[5], K[4], K[5], K[4], K[5], K[4], K[5]}; - signed char kMadd4[16] = {K[6], K[7], K[6], K[7], K[6], K[7], K[6], K[7], - K[6], K[7], K[6], K[7], K[6], K[7], K[6], K[7]}; - unsigned char bv0[16], bv1[16], bv2[16], bv3[16]; - unsigned char in1[16], in2[16], in3[16]; - short wv0[8], wv1[8], wv2[8], wv3[8]; + __m128i kRound = _mm_set1_epi16(ROUND); + __m128i kMadd1 = _mm_set_epi8(K[1], K[0], K[1], K[0], K[1], K[0], K[1], K[0], + K[1], K[0], K[1], K[0], K[1], K[0], K[1], K[0]); + __m128i kMadd2 = _mm_set_epi8(K[3], K[2], K[3], K[2], K[3], K[2], K[3], K[2], + K[3], K[2], K[3], K[2], K[3], K[2], K[3], K[2]); + __m128i kMadd3 = _mm_set_epi8(K[5], K[4], K[5], K[4], K[5], K[4], K[5], K[4], + K[5], K[4], K[5], K[4], K[5], K[4], K[5], K[4]); + __m128i kMadd4 = _mm_set_epi8(K[7], K[6], K[7], K[6], K[7], K[6], K[7], K[6], + K[7], K[6], K[7], K[6], K[7], K[6], K[7], K[6]); + __m128i bv0, bv1, bv2, bv3; + __m128i in1, in2, in3; + __m128i wv0, wv1, wv2, wv3; unsigned long i, j, w; if (n >= STRIDE) { i = 0; w = (n + RATIO / 2) / RATIO; - memset(in1, A[0], sizeof(in1)); - memset(in2, A[n - 1], 16); - memcpy(in2, A, MIN(16, n)); + in1 = _mm_set1_epi8(A[0]); + in2 = _mm_set1_epi8(A[n - 1]); + _mm_storeu_si128((__m128i *)&in2, _mm_loadu_si128((__m128i *)A)); for (; i < w; i += STRIDE) { j = i * RATIO + 16; if (j + 16 <= n) { - memcpy(in3, &A[j], 16); + in3 = _mm_loadu_si128((__m128i *)&A[j]); } else { - memset(in3, A[n - 1], 16); + in3 = _mm_set1_epi8(A[n - 1]); if (j < n) { - memcpy(in3, &A[j], n - j); + // SSSE3-compatible way to handle partial loads + __m128i mask = _mm_loadu_si128((__m128i *)&A[j]); + __m128i shuffle_mask = _mm_set_epi8(15, 14, 13, 12, 11, 10, 9, 8, 7, + 6, 5, 4, 3, 2, 1, 0); + __m128i index = _mm_set1_epi8(n - j); + __m128i cmp = _mm_cmplt_epi8(shuffle_mask, index); + in3 = _mm_or_si128(_mm_and_si128(cmp, mask), + _mm_andnot_si128(cmp, in3)); } } - palignr(bv0, in2, in1, 13); - palignr(bv1, in2, in1, 15); - palignr(bv2, in3, in2, 1); - palignr(bv3, in3, in2, 3); - pmaddubsw(wv0, bv0, kMadd1); - pmaddubsw(wv1, bv1, kMadd2); - pmaddubsw(wv2, bv2, kMadd3); - pmaddubsw(wv3, bv3, kMadd4); - paddw(wv0, wv0, kRound); - paddw(wv0, wv0, wv1); - paddw(wv0, wv0, wv2); - paddw(wv0, wv0, wv3); - psraw(wv0, wv0, SCALE); - packuswb(bv2, wv0, wv0); - memcpy(&A[i], bv2, STRIDE); - memcpy(in1, in2, 16); - memcpy(in2, in3, 16); + bv0 = _mm_alignr_epi8(in2, in1, 13); + bv1 = _mm_alignr_epi8(in2, in1, 15); + bv2 = _mm_alignr_epi8(in3, in2, 1); + bv3 = _mm_alignr_epi8(in3, in2, 3); + wv0 = _mm_maddubs_epi16(bv0, kMadd1); + wv1 = _mm_maddubs_epi16(bv1, kMadd2); + wv2 = _mm_maddubs_epi16(bv2, kMadd3); + wv3 = _mm_maddubs_epi16(bv3, kMadd4); + wv0 = _mm_add_epi16(wv0, kRound); + wv0 = _mm_add_epi16(wv0, wv1); + wv0 = _mm_add_epi16(wv0, wv2); + wv0 = _mm_add_epi16(wv0, wv3); + wv0 = _mm_srai_epi16(wv0, SCALE); + bv2 = _mm_packus_epi16(wv0, wv0); + _mm_storel_epi64((__m128i *)&A[i], bv2); + in1 = in2; + in2 = in3; } } return A; +#else + long h, i; + if (n < 2) + return A; + unsigned char M[3 + n + 4]; + unsigned char *q = M; + q[0] = A[0]; + q[1] = A[0]; + q[2] = A[0]; + memcpy(q + 3, A, n); + q[3 + n + 0] = A[n - 1]; + q[3 + n + 1] = A[n - 1]; + q[3 + n + 2] = A[n - 1]; + q[3 + n + 3] = A[n - 1]; + q += 3; + h = (n + 1) >> 1; + for (i = 0; i < h; ++i) { + short x0, x1, x2, x3, x4, x5, x6, x7; + x0 = q[i * 2 - 3]; + x1 = q[i * 2 - 2]; + x2 = q[i * 2 - 1]; + x3 = q[i * 2 + 0]; + x4 = q[i * 2 + 1]; + x5 = q[i * 2 + 2]; + x6 = q[i * 2 + 3]; + x7 = q[i * 2 + 4]; + x0 *= K[0]; + x1 *= K[1]; + x2 *= K[2]; + x3 *= K[3]; + x4 *= K[4]; + x5 *= K[5]; + x6 *= K[6]; + x7 *= K[7]; + x0 += x1; + x2 += x3; + x4 += x5; + x6 += x7; + x0 += x2; + x4 += x6; + x0 += x4; + x0 += 1 << 4; + x0 >>= 5; + A[i] = MIN(255, MAX(0, x0)); + } + return A; +#endif } diff --git a/libc/intrin/macros.h b/libc/intrin/macros.h deleted file mode 100644 index 38d6324bd..000000000 --- a/libc/intrin/macros.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_INTRIN_MACROS_H_ -#define COSMOPOLITAN_LIBC_INTRIN_MACROS_H_ -#include "libc/dce.h" -#include "libc/nexgen32e/x86feature.h" - -#define INTRIN_COMMUTATIVE "%" -#define INTRIN_NONCOMMUTATIVE - -#if defined(__x86_64__) && !defined(__STRICT_ANSI__) - -typedef char __intrin_xmm_t - __attribute__((__vector_size__(16), __aligned__(16), __may_alias__)); - -#define INTRIN_SSEVEX_X_X_X_(PURE, ISA, OP, FLAGS, A, B, C) \ - do { \ - if (X86_HAVE(ISA)) { \ - __intrin_xmm_t *Xmm0 = (void *)(A); \ - const __intrin_xmm_t *Xmm1 = (const __intrin_xmm_t *)(B); \ - const __intrin_xmm_t *Xmm2 = (const __intrin_xmm_t *)(C); \ - if (!X86_NEED(AVX)) { \ - asm(OP "\t%1,%0" : "=x"(*Xmm0) : FLAGS "x"(*Xmm2), "0"(*Xmm1)); \ - } else { \ - asm("v" OP "\t%2,%1,%0" : "=x"(*Xmm0) : FLAGS "x"(*Xmm1), "x"(*Xmm2)); \ - } \ - } else { \ - PURE(A, B, C); \ - } \ - } while (0) - -#define INTRIN_SSEVEX_X_X_I_(PURE, ISA, OP, A, B, I) \ - do { \ - if (X86_HAVE(ISA)) { \ - __intrin_xmm_t *Xmm0 = (void *)(A); \ - const __intrin_xmm_t *Xmm1 = (const __intrin_xmm_t *)(B); \ - if (!X86_NEED(AVX)) { \ - asm(OP "\t%2,%1,%0" : "=x"(*Xmm0) : "x"(*Xmm1), "i"(I)); \ - } else { \ - asm("v" OP "\t%2,%1,%0" : "=x"(*Xmm0) : "x"(*Xmm1), "i"(I)); \ - } \ - } else { \ - PURE(A, B, I); \ - } \ - } while (0) - -#define INTRIN_SSEVEX_X_X_(PURE, ISA, OP, A, B) \ - do { \ - if (X86_HAVE(ISA)) { \ - __intrin_xmm_t *Xmm0 = (void *)(A); \ - const __intrin_xmm_t *Xmm1 = (const __intrin_xmm_t *)(B); \ - if (!X86_NEED(AVX)) { \ - asm(OP "\t%1,%0" : "=x"(*Xmm0) : "0"(*Xmm1)); \ - } else { \ - asm("v" OP "\t%1,%0" : "=x"(*Xmm0) : "x"(*Xmm1)); \ - } \ - } else { \ - PURE(A, B); \ - } \ - } while (0) - -#define INTRIN_SSEVEX_X_I_(PURE, ISA, OP, A, B, I) \ - do { \ - if (!IsModeDbg() && X86_HAVE(ISA)) { \ - __intrin_xmm_t *Xmm0 = (void *)(A); \ - const __intrin_xmm_t *Xmm1 = (const __intrin_xmm_t *)(B); \ - if (!X86_NEED(AVX)) { \ - asm(OP "\t%1,%0" : "=x"(*Xmm0) : "i"(I), "0"(*Xmm1)); \ - } else { \ - asm("v" OP "\t%2,%1,%0" : "=x"(*Xmm0) : "x"(*Xmm1), "i"(I)); \ - } \ - } else { \ - PURE(A, B, I); \ - } \ - } while (0) - -#else -#define INTRIN_SSEVEX_X_X_X_(PURE, ISA, OP, FLAGS, A, B, C) PURE(A, B, C) -#define INTRIN_SSEVEX_X_X_I_(PURE, ISA, OP, A, B, I) PURE(A, B, I) -#define INTRIN_SSEVEX_X_I_(PURE, ISA, OP, A, B, I) PURE(A, B, I) -#define INTRIN_SSEVEX_X_X_(PURE, ISA, OP, A, B) PURE(A, B) -#endif /* X86 && !ANSI */ - -#endif /* COSMOPOLITAN_LIBC_INTRIN_MACROS_H_ */ diff --git a/libc/intrin/packsswb.c b/libc/intrin/packsswb.c deleted file mode 100644 index 84ae0b712..000000000 --- a/libc/intrin/packsswb.c +++ /dev/null @@ -1,40 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/intrin/packsswb.h" -#include "libc/limits.h" -#include "libc/macros.h" -#include "libc/str/str.h" - -/** - * Casts shorts to signed chars w/ saturation. - * - * 𝑎 ← {CLAMP[𝑏ᵢ]|𝑖∈[0,4)} ║ {CLAMP[𝑐ᵢ]|𝑖∈[4,8)} - * - * @see packuswb() - * @mayalias - */ -void(packsswb)(int8_t a[16], const int16_t b[8], const int16_t c[8]) { - unsigned i; - int8_t r[16]; - for (i = 0; i < 8; ++i) - r[i + 0] = MIN(INT8_MAX, MAX(INT8_MIN, b[i])); - for (i = 0; i < 8; ++i) - r[i + 8] = MIN(INT8_MAX, MAX(INT8_MIN, c[i])); - __builtin_memcpy(a, r, 16); -} diff --git a/libc/intrin/packsswb.h b/libc/intrin/packsswb.h deleted file mode 100644 index fae659b0b..000000000 --- a/libc/intrin/packsswb.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_INTRIN_PACKSSWB_H_ -#define COSMOPOLITAN_LIBC_INTRIN_PACKSSWB_H_ -#include "libc/intrin/macros.h" -COSMOPOLITAN_C_START_ - -void packsswb(int8_t[16], const int16_t[8], const int16_t[8]); - -#define packsswb(A, B, C) \ - INTRIN_SSEVEX_X_X_X_(packsswb, SSE2, "packsswb", INTRIN_NONCOMMUTATIVE, A, \ - B, C) - -COSMOPOLITAN_C_END_ -#endif /* COSMOPOLITAN_LIBC_INTRIN_PACKSSWB_H_ */ diff --git a/libc/intrin/packuswb.c b/libc/intrin/packuswb.c deleted file mode 100644 index c4200a3e8..000000000 --- a/libc/intrin/packuswb.c +++ /dev/null @@ -1,40 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/intrin/packuswb.h" -#include "libc/limits.h" -#include "libc/macros.h" -#include "libc/str/str.h" - -/** - * Casts shorts to unsigned chars w/ saturation. - * - * 𝑎 ← {CLAMP[𝑏ᵢ]|𝑖∈[0,4)} ║ {CLAMP[𝑐ᵢ]|𝑖∈[4,8)} - * - * @see packsswb() - * @mayalias - */ -void(packuswb)(uint8_t a[16], const int16_t b[8], const int16_t c[8]) { - unsigned i; - uint8_t r[16]; - for (i = 0; i < 8; ++i) - r[i + 0] = MIN(UINT8_MAX, MAX(UINT8_MIN, b[i])); - for (i = 0; i < 8; ++i) - r[i + 8] = MIN(UINT8_MAX, MAX(UINT8_MIN, c[i])); - __builtin_memcpy(a, r, 16); -} diff --git a/libc/intrin/packuswb.h b/libc/intrin/packuswb.h deleted file mode 100644 index 3c8ddf7da..000000000 --- a/libc/intrin/packuswb.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_INTRIN_PACKUSWB_H_ -#define COSMOPOLITAN_LIBC_INTRIN_PACKUSWB_H_ -#include "libc/intrin/macros.h" -COSMOPOLITAN_C_START_ - -void packuswb(uint8_t[16], const int16_t[8], const int16_t[8]); - -#define packuswb(A, B, C) \ - INTRIN_SSEVEX_X_X_X_(packuswb, SSE2, "packuswb", INTRIN_NONCOMMUTATIVE, A, \ - B, C) - -COSMOPOLITAN_C_END_ -#endif /* COSMOPOLITAN_LIBC_INTRIN_PACKUSWB_H_ */ diff --git a/libc/intrin/paddw.c b/libc/intrin/paddw.c deleted file mode 100644 index ea3351e4f..000000000 --- a/libc/intrin/paddw.c +++ /dev/null @@ -1,39 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/intrin/paddw.h" -#include "libc/str/str.h" - -/** - * Adds 16-bit integers. - * - * @param 𝑎 [w/o] receives result - * @param 𝑏 [r/o] supplies first input vector - * @param 𝑐 [r/o] supplies second input vector - * @note shorts can't overflow so ubsan won't report it when it happens - * @see paddsw() - * @mayalias - */ -void(paddw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) { - unsigned i; - int16_t r[8]; - for (i = 0; i < 8; ++i) { - r[i] = b[i] + c[i]; - } - __builtin_memcpy(a, r, 16); -} diff --git a/libc/intrin/paddw.h b/libc/intrin/paddw.h deleted file mode 100644 index bdad518d9..000000000 --- a/libc/intrin/paddw.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_INTRIN_PADDW_H_ -#define COSMOPOLITAN_LIBC_INTRIN_PADDW_H_ -#include "libc/intrin/macros.h" -COSMOPOLITAN_C_START_ - -void paddw(int16_t[8], const int16_t[8], const int16_t[8]); - -#define paddw(A, B, C) \ - INTRIN_SSEVEX_X_X_X_(paddw, SSE2, "paddw", INTRIN_COMMUTATIVE, A, B, C) - -COSMOPOLITAN_C_END_ -#endif /* COSMOPOLITAN_LIBC_INTRIN_PADDW_H_ */ diff --git a/libc/intrin/palignr.c b/libc/intrin/palignr.c deleted file mode 100644 index 549d339aa..000000000 --- a/libc/intrin/palignr.c +++ /dev/null @@ -1,43 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/intrin/palignr.h" -#include "libc/assert.h" -#include "libc/macros.h" - -/** - * Overlaps vectors. - * - * 𝑖= 0 means 𝑐←𝑎 - * 0<𝑖<16 means 𝑐←𝑎║𝑏 - * 𝑖=16 means 𝑐←𝑏 - * 16<𝑖<32 means 𝑐←𝑏║0 - * 𝑖≥32 means 𝑐←0 - * - * @param 𝑖 goes faster as constexpr - * @note not compatible with mmx - * @see pvalignr() - * @mayalias - */ -void(palignr)(void *c, const void *b, const void *a, unsigned long i) { - char t[48]; - __builtin_memcpy(t, a, 16); - __builtin_memcpy(t + 16, b, 16); - __builtin_memset(t + 32, 0, 16); - __builtin_memcpy(c, t + MIN(i, 32), 16); -} diff --git a/libc/intrin/palignr.h b/libc/intrin/palignr.h deleted file mode 100644 index 3995bd4a2..000000000 --- a/libc/intrin/palignr.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_INTRIN_PALIGNR_H_ -#define COSMOPOLITAN_LIBC_INTRIN_PALIGNR_H_ -#include "libc/intrin/macros.h" -#include "libc/str/str.h" -COSMOPOLITAN_C_START_ - -void palignr(void *, const void *, const void *, unsigned long); - -#if !defined(__STRICT_ANSI__) && !defined(__chibicc__) && defined(__x86_64__) -__intrin_xmm_t __palignrs(__intrin_xmm_t, __intrin_xmm_t); -#define palignr(C, B, A, I) \ - do { \ - if (__builtin_expect(!IsModeDbg() && X86_NEED(SSE) && X86_HAVE(SSSE3), \ - 1)) { \ - __intrin_xmm_t *Xmm0 = (void *)(C); \ - const __intrin_xmm_t *Xmm1 = (const __intrin_xmm_t *)(B); \ - const __intrin_xmm_t *Xmm2 = (const __intrin_xmm_t *)(A); \ - if (__builtin_constant_p(I)) { \ - if (!X86_NEED(AVX)) { \ - asm("palignr\t%2,%1,%0" \ - : "=x"(*Xmm0) \ - : "x"(*Xmm2), "i"(I), "0"(*Xmm1)); \ - } else { \ - asm("vpalignr\t%3,%2,%1,%0" \ - : "=x"(*Xmm0) \ - : "x"(*Xmm1), "x"(*Xmm2), "i"(I)); \ - } \ - } else { \ - unsigned long Vimm = (I); \ - typeof(__palignrs) *Fn; \ - if (__builtin_expect(Vimm < 32, 1)) { \ - Fn = (typeof(__palignrs) *)((uintptr_t) & __palignrs + Vimm * 8); \ - *Xmm0 = Fn(*Xmm1, *Xmm2); \ - } else { \ - memset(Xmm0, 0, 16); \ - } \ - } \ - } else { \ - palignr(C, B, A, I); \ - } \ - } while (0) -#endif - -COSMOPOLITAN_C_END_ -#endif /* COSMOPOLITAN_LIBC_INTRIN_PALIGNR_H_ */ diff --git a/libc/intrin/palignrs.S b/libc/intrin/palignrs.S deleted file mode 100644 index 6d8b8225f..000000000 --- a/libc/intrin/palignrs.S +++ /dev/null @@ -1,125 +0,0 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│ vi: set noet ft=asm ts=8 sw=8 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/macros.h" - -// Jump table for palignr() with non-constexpr immediate parameter. -// -// @note needs ssse3 cf. prescott c. 2004 cf. bulldozer c. 2011 -// @see palignr() - .balign 8 -__palignrs: - palignr $0,%xmm1,%xmm0 - ret - nop - palignr $1,%xmm1,%xmm0 - ret - nop - palignr $2,%xmm1,%xmm0 - ret - nop - palignr $3,%xmm1,%xmm0 - ret - nop - palignr $4,%xmm1,%xmm0 - ret - nop - palignr $5,%xmm1,%xmm0 - ret - nop - palignr $6,%xmm1,%xmm0 - ret - nop - palignr $7,%xmm1,%xmm0 - ret - nop - palignr $8,%xmm1,%xmm0 - ret - nop - palignr $9,%xmm1,%xmm0 - ret - nop - palignr $10,%xmm1,%xmm0 - ret - nop - palignr $11,%xmm1,%xmm0 - ret - nop - palignr $12,%xmm1,%xmm0 - ret - nop - palignr $13,%xmm1,%xmm0 - ret - nop - palignr $14,%xmm1,%xmm0 - ret - nop - palignr $15,%xmm1,%xmm0 - ret - nop - palignr $16,%xmm1,%xmm0 - ret - nop - palignr $17,%xmm1,%xmm0 - ret - nop - palignr $18,%xmm1,%xmm0 - ret - nop - palignr $19,%xmm1,%xmm0 - ret - nop - palignr $20,%xmm1,%xmm0 - ret - nop - palignr $21,%xmm1,%xmm0 - ret - nop - palignr $22,%xmm1,%xmm0 - ret - nop - palignr $23,%xmm1,%xmm0 - ret - nop - palignr $24,%xmm1,%xmm0 - ret - nop - palignr $25,%xmm1,%xmm0 - ret - nop - palignr $26,%xmm1,%xmm0 - ret - nop - palignr $27,%xmm1,%xmm0 - ret - nop - palignr $28,%xmm1,%xmm0 - ret - nop - palignr $29,%xmm1,%xmm0 - ret - nop - palignr $30,%xmm1,%xmm0 - ret - nop - palignr $31,%xmm1,%xmm0 - ret - .if . - __palignrs != 8 * 32 - 1 - .error "bad assemblage" - .endif - .endfn __palignrs,globl diff --git a/libc/intrin/pmaddubsw.c b/libc/intrin/pmaddubsw.c deleted file mode 100644 index 9f730f029..000000000 --- a/libc/intrin/pmaddubsw.c +++ /dev/null @@ -1,42 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/intrin/pmaddubsw.h" -#include "libc/limits.h" -#include "libc/macros.h" -#include "libc/str/str.h" - -/** - * Multiplies bytes and adds adjacent results w/ short saturation. - * - * 𝑤ᵢ ← CLAMP[ 𝑏₂ᵢ𝑐₂ᵢ + 𝑏₍₂ᵢ₊₁₎𝑐₍₂ᵢ₊₁₎ ] - * - * @param 𝑤 [w/o] receives shorts - * @param 𝑏 [r/o] is your byte data - * @param 𝑐 [r/o] are your int8 coefficients - * @note SSSE3 w/ Prescott c. 2004, Bulldozer c. 2011 - * @note greatest simd op, like, ever - * @mayalias - */ -void(pmaddubsw)(int16_t w[8], const uint8_t b[16], const int8_t c[16]) { - unsigned i; - for (i = 0; i < 8; ++i) { - w[i] = MIN(SHRT_MAX, MAX(SHRT_MIN, (c[i * 2 + 0] * b[i * 2 + 0] + - c[i * 2 + 1] * b[i * 2 + 1]))); - } -} diff --git a/libc/intrin/pmaddubsw.h b/libc/intrin/pmaddubsw.h deleted file mode 100644 index 5e503c56c..000000000 --- a/libc/intrin/pmaddubsw.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_INTRIN_PMADDUBSW_H_ -#define COSMOPOLITAN_LIBC_INTRIN_PMADDUBSW_H_ -#include "libc/intrin/macros.h" -COSMOPOLITAN_C_START_ - -void pmaddubsw(int16_t[8], const uint8_t[16], const int8_t[16]); - -#define pmaddubsw(W, B, C) \ - INTRIN_SSEVEX_X_X_X_(pmaddubsw, SSSE3, "pmaddubsw", INTRIN_NONCOMMUTATIVE, \ - W, B, C) - -COSMOPOLITAN_C_END_ -#endif /* COSMOPOLITAN_LIBC_INTRIN_PMADDUBSW_H_ */ diff --git a/libc/intrin/pmulhrsw.c b/libc/intrin/pmulhrsw.c deleted file mode 100644 index 4326542e0..000000000 --- a/libc/intrin/pmulhrsw.c +++ /dev/null @@ -1,36 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/intrin/pmulhrsw.h" -#include "libc/str/str.h" - -/** - * Multiplies Q15 numbers. - * - * @note goes fast w/ ssse3 (intel c. 2004, amd c. 2011) - * @note a.k.a. packed multiply high w/ round & scale - * @see Q2F(15,𝑥), F2Q(15,𝑥) - * @mayalias - */ -void(pmulhrsw)(int16_t a[8], const int16_t b[8], const int16_t c[8]) { - unsigned i; - int16_t r[8]; - for (i = 0; i < 8; ++i) - r[i] = (((b[i] * c[i]) >> 14) + 1) >> 1; - __builtin_memcpy(a, r, 16); -} diff --git a/libc/intrin/pmulhrsw.h b/libc/intrin/pmulhrsw.h deleted file mode 100644 index 2182c3404..000000000 --- a/libc/intrin/pmulhrsw.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_INTRIN_PMULHRSW_H_ -#define COSMOPOLITAN_LIBC_INTRIN_PMULHRSW_H_ -#include "libc/intrin/macros.h" -COSMOPOLITAN_C_START_ - -void pmulhrsw(int16_t a[8], const int16_t b[8], const int16_t c[8]); - -#define pmulhrsw(A, B, C) \ - INTRIN_SSEVEX_X_X_X_(pmulhrsw, SSSE3, "pmulhrsw", INTRIN_COMMUTATIVE, A, B, C) - -COSMOPOLITAN_C_END_ -#endif /* COSMOPOLITAN_LIBC_INTRIN_PMULHRSW_H_ */ diff --git a/libc/intrin/psraw.c b/libc/intrin/psraw.c deleted file mode 100644 index 0ea62c129..000000000 --- a/libc/intrin/psraw.c +++ /dev/null @@ -1,35 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/intrin/psraw.h" - -/** - * Divides shorts by two power. - * - * @note c needs to be a literal, asmconstexpr, or linkconstsym - * @note arithmetic shift right will sign extend negatives - * @mayalias - */ -void(psraw)(int16_t a[8], const int16_t b[8], unsigned char k) { - unsigned i; - if (k > 15) - k = 15; - for (i = 0; i < 8; ++i) { - a[i] = b[i] >> k; - } -} diff --git a/libc/intrin/psraw.h b/libc/intrin/psraw.h deleted file mode 100644 index 083bb7445..000000000 --- a/libc/intrin/psraw.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef COSMOPOLITAN_LIBC_INTRIN_PSRAW_H_ -#define COSMOPOLITAN_LIBC_INTRIN_PSRAW_H_ -#include "libc/intrin/macros.h" -COSMOPOLITAN_C_START_ - -void psraw(int16_t[8], const int16_t[8], unsigned char) libcesque; - -#define psraw(A, B, I) INTRIN_SSEVEX_X_I_(psraw, SSE2, "psraw", A, B, I) - -COSMOPOLITAN_C_END_ -#endif /* COSMOPOLITAN_LIBC_INTRIN_PSRAW_H_ */ diff --git a/test/dsp/core/scalevolume_test.c b/test/dsp/core/scalevolume_test.c index a69d2fbf4..a6247fc54 100644 --- a/test/dsp/core/scalevolume_test.c +++ b/test/dsp/core/scalevolume_test.c @@ -17,7 +17,6 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "dsp/core/core.h" -#include "dsp/mpeg/mpeg.h" #include "libc/limits.h" #include "libc/log/check.h" #include "libc/macros.h" diff --git a/test/tool/viz/lib/ycbcr2rgb2_test.c b/test/tool/viz/lib/ycbcr2rgb2_test.c index 4136cf4d3..bb2d6e90b 100644 --- a/test/tool/viz/lib/ycbcr2rgb2_test.c +++ b/test/tool/viz/lib/ycbcr2rgb2_test.c @@ -16,7 +16,6 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "dsp/mpeg/mpeg.h" #include "libc/macros.h" #include "libc/stdio/rand.h" #include "libc/stdio/stdio.h" diff --git a/third_party/python/Python/marshal.c b/third_party/python/Python/marshal.c index 573e7689b..53984862a 100644 --- a/third_party/python/Python/marshal.c +++ b/third_party/python/Python/marshal.c @@ -5,7 +5,6 @@ │ https://docs.python.org/3/license.html │ ╚─────────────────────────────────────────────────────────────────────────────*/ #define PY_SSIZE_T_CLEAN -#include "dsp/mpeg/video.h" #include "libc/calls/calls.h" #include "libc/calls/weirdtypes.h" #include "libc/mem/mem.h" diff --git a/tool/viz/lib/ycbcr2rgb3.c b/tool/viz/lib/ycbcr2rgb3.c index 958c5ef1e..783730ade 100644 --- a/tool/viz/lib/ycbcr2rgb3.c +++ b/tool/viz/lib/ycbcr2rgb3.c @@ -30,7 +30,6 @@ #include "libc/calls/struct/sigset.h" #include "libc/calls/struct/timespec.h" #include "libc/intrin/bsr.h" -#include "libc/intrin/pmulhrsw.h" #include "libc/log/check.h" #include "libc/log/log.h" #include "libc/macros.h" diff --git a/tool/viz/printvideo.c b/tool/viz/printvideo.c index 3621ee5b9..967afe06b 100644 --- a/tool/viz/printvideo.c +++ b/tool/viz/printvideo.c @@ -20,7 +20,7 @@ #include "dsp/core/core.h" #include "dsp/core/half.h" #include "dsp/core/illumination.h" -#include "dsp/mpeg/mpeg.h" +#include "dsp/mpeg/pl_mpeg.h" #include "dsp/scale/scale.h" #include "dsp/tty/quant.h" #include "dsp/tty/tty.h" @@ -239,7 +239,7 @@ static const struct NamedVector kLightings[] = { static plm_t *plm_; static float gamma_; -static int volscale_; +static float volscale_; struct CosmoAudio *ca_; static enum Blur blur_; static enum Sharp sharp_; @@ -425,6 +425,9 @@ static bool OpenSpeaker(void) { static void OnAudio(plm_t *mpeg, plm_samples_t *samples, void *user) { if (!ca_) return; + if (volscale_ != 1.f) + for (unsigned i = 0; i < samples->count * chans_; ++i) + samples->interleaved[i] *= volscale_; cosmoaudio_write(ca_, samples->interleaved, samples->count); } @@ -752,9 +755,9 @@ static void OpenVideo(void) { FormatInt64(chansstr_, (chans_ = 2)); FormatInt64(sratestr_, (srate_ = plm_get_samplerate(plm_))); if (plm_get_num_audio_streams(plm_) && OpenSpeaker()) { - plm_set_audio_enabled(plm_, true, 0); + plm_set_audio_enabled(plm_, true); } else { - plm_set_audio_enabled(plm_, false, 0); + plm_set_audio_enabled(plm_, false); } g2_ = g1_ = resizegraphic(&graphic_[0], yn, xn); } @@ -942,10 +945,10 @@ static optimizesize void ReadKeyboard(void) { case '[': switch (b[i++]) { case 'A': /* "\e[A" is up arrow */ - ++volscale_; + volscale_ *= 1.05f; break; case 'B': /* "\e[B" is down arrow */ - --volscale_; + volscale_ *= 0.95f; break; case 'C': /* "\e[C" is right arrow */ break; @@ -1405,7 +1408,7 @@ int main(int argc, char *argv[]) { sigset_t wut; ShowCrashReports(); gamma_ = 2.4; - volscale_ -= 2; + volscale_ = 1.f; dither_ = true; sigemptyset(&wut); sigaddset(&wut, SIGCHLD);