mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-05-22 21:32:31 +00:00
Initial import
This commit is contained in:
commit
c91b3c5006
14915 changed files with 590219 additions and 0 deletions
92
dsp/mpeg/README.txt
Normal file
92
dsp/mpeg/README.txt
Normal file
|
@ -0,0 +1,92 @@
|
|||
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.
|
22
dsp/mpeg/blockset.h
Normal file
22
dsp/mpeg/blockset.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
#ifndef COSMOPOLITAN_DSP_MPEG_BLOCKSET_H_
|
||||
#define COSMOPOLITAN_DSP_MPEG_BLOCKSET_H_
|
||||
#if !(__ASSEMBLER__ + __LINKER__ + 0)
|
||||
|
||||
#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 /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||
#endif /* COSMOPOLITAN_DSP_MPEG_BLOCKSET_H_ */
|
158
dsp/mpeg/buffer.c
Normal file
158
dsp/mpeg/buffer.c
Normal file
|
@ -0,0 +1,158 @@
|
|||
/*-*- 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/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"
|
||||
|
||||
asm(".ident\t\"\\n\\n\
|
||||
PL_MPEG (MIT License)\\n\
|
||||
Copyright(c) 2019 Dominic Szablewski\\n\
|
||||
https://phoboslab.org\"");
|
||||
asm(".include \"libc/disclaimer.inc\"");
|
||||
|
||||
/* 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;
|
||||
}
|
163
dsp/mpeg/buffer.h
Normal file
163
dsp/mpeg/buffer.h
Normal file
|
@ -0,0 +1,163 @@
|
|||
#ifndef COSMOPOLITAN_DSP_MPEG_BUFFER_H_
|
||||
#define COSMOPOLITAN_DSP_MPEG_BUFFER_H_
|
||||
#include "dsp/mpeg/mpeg.h"
|
||||
#if !(__ASSEMBLER__ + __LINKER__ + 0)
|
||||
COSMOPOLITAN_C_START_
|
||||
|
||||
struct FILE;
|
||||
|
||||
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;
|
||||
struct 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 /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||
#endif /* COSMOPOLITAN_DSP_MPEG_BUFFER_H_ */
|
32
dsp/mpeg/clamp4int256-core.S
Normal file
32
dsp/mpeg/clamp4int256-core.S
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*-*- mode:asm; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
|
||||
│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2020 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ This program is free software; you can redistribute it and/or modify │
|
||||
│ it under the terms of the GNU General Public License as published by │
|
||||
│ the Free Software Foundation; version 2 of the License. │
|
||||
│ │
|
||||
│ This program is distributed in the hope that it will be useful, but │
|
||||
│ WITHOUT ANY WARRANTY; without even the implied warranty of │
|
||||
│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
|
||||
│ General Public License for more details. │
|
||||
│ │
|
||||
│ You should have received a copy of the GNU General Public License │
|
||||
│ along with this program; if not, write to the Free Software │
|
||||
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
|
||||
│ 02110-1301 USA │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/macros.h"
|
||||
.yoink __FILE__
|
||||
|
||||
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
|
208
dsp/mpeg/demux.c
Normal file
208
dsp/mpeg/demux.c
Normal file
|
@ -0,0 +1,208 @@
|
|||
/*-*- 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/buffer.h"
|
||||
#include "dsp/mpeg/demux.h"
|
||||
#include "dsp/mpeg/mpeg.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/str/str.h"
|
||||
|
||||
asm(".ident\t\"\\n\\n\
|
||||
PL_MPEG (MIT License)\\n\
|
||||
Copyright(c) 2019 Dominic Szablewski\\n\
|
||||
https://phoboslab.org\"");
|
||||
asm(".include \"libc/disclaimer.inc\"");
|
||||
|
||||
/* 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;
|
||||
}
|
31
dsp/mpeg/demux.h
Normal file
31
dsp/mpeg/demux.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
#ifndef COSMOPOLITAN_DSP_MPEG_DEMUX_H_
|
||||
#define COSMOPOLITAN_DSP_MPEG_DEMUX_H_
|
||||
#include "dsp/mpeg/mpeg.h"
|
||||
#if !(__ASSEMBLER__ + __LINKER__ + 0)
|
||||
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 /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||
#endif /* COSMOPOLITAN_DSP_MPEG_DEMUX_H_ */
|
106
dsp/mpeg/idct.c
Normal file
106
dsp/mpeg/idct.c
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*-*- 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"
|
||||
|
||||
asm(".ident\t\"\\n\\n\
|
||||
PL_MPEG (MIT License)\\n\
|
||||
Copyright(c) 2019 Dominic Szablewski\\n\
|
||||
https://phoboslab.org\"");
|
||||
asm(".include \"libc/disclaimer.inc\"");
|
||||
|
||||
/**
|
||||
* Computes Fixed-Point 8x8 Inverse Discrete Cosine Transform.
|
||||
*
|
||||
* @note discovered by Nasir Ahmed
|
||||
*/
|
||||
void plm_video_idct(int block[8][8]) {
|
||||
int i, j, 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;
|
||||
}
|
||||
}
|
10
dsp/mpeg/idct.h
Normal file
10
dsp/mpeg/idct.h
Normal file
|
@ -0,0 +1,10 @@
|
|||
#ifndef COSMOPOLITAN_DSP_MPEG_IDCT_H_
|
||||
#define COSMOPOLITAN_DSP_MPEG_IDCT_H_
|
||||
#if !(__ASSEMBLER__ + __LINKER__ + 0)
|
||||
COSMOPOLITAN_C_START_
|
||||
|
||||
void plm_video_idct(int *);
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||
#endif /* COSMOPOLITAN_DSP_MPEG_IDCT_H_ */
|
171
dsp/mpeg/macroblock.c
Normal file
171
dsp/mpeg/macroblock.c
Normal file
|
@ -0,0 +1,171 @@
|
|||
/*-*- 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 *restrict d,
|
||||
uint8_t *restrict 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 *restrict d,
|
||||
uint8_t *restrict 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 *restrict d,
|
||||
uint8_t *restrict 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);
|
||||
}
|
775
dsp/mpeg/mp2.c
Normal file
775
dsp/mpeg/mp2.c
Normal file
|
@ -0,0 +1,775 @@
|
|||
/*-*- 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/buffer.h"
|
||||
#include "dsp/mpeg/mpeg.h"
|
||||
#include "libc/log/log.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/str/str.h"
|
||||
|
||||
asm(".ident\t\"\\n\\n\
|
||||
PL_MPEG (MIT License)\\n\
|
||||
Copyright(c) 2019 Dominic Szablewski\\n\
|
||||
https://phoboslab.org\"");
|
||||
asm(".include \"libc/disclaimer.inc\"");
|
||||
|
||||
/* 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];
|
||||
} aligned(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;
|
||||
};
|
||||
|
450
dsp/mpeg/mpeg.h
Normal file
450
dsp/mpeg/mpeg.h
Normal file
|
@ -0,0 +1,450 @@
|
|||
#ifndef COSMOPOLITAN_DSP_MPEG_MPEG_H_
|
||||
#define COSMOPOLITAN_DSP_MPEG_MPEG_H_
|
||||
#if !(__ASSEMBLER__ + __LINKER__ + 0)
|
||||
COSMOPOLITAN_C_START_
|
||||
|
||||
struct FILE;
|
||||
|
||||
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] aligned(32);
|
||||
float right[PLM_AUDIO_SAMPLES_PER_FRAME] aligned(32);
|
||||
#else
|
||||
float interleaved[PLM_AUDIO_SAMPLES_PER_FRAME * 2] aligned(32);
|
||||
#endif
|
||||
} aligned(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(struct 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(struct 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 /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||
#endif /* COSMOPOLITAN_DSP_MPEG_MPEG_H_ */
|
69
dsp/mpeg/mpeg.mk
Normal file
69
dsp/mpeg/mpeg.mk
Normal file
|
@ -0,0 +1,69 @@
|
|||
#-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐
|
||||
#───vi: set et ft=make ts=8 tw=8 fenc=utf-8 :vi───────────────────────┘
|
||||
|
||||
PKGS += DSP_MPEG
|
||||
|
||||
DSP_MPEG_ARTIFACTS += DSP_MPEG_A
|
||||
DSP_MPEG = $(DSP_MPEG_A_DEPS) $(DSP_MPEG_A)
|
||||
DSP_MPEG_A = o/$(MODE)/dsp/mpeg/mpeg.a
|
||||
DSP_MPEG_A_FILES := $(wildcard dsp/mpeg/*)
|
||||
DSP_MPEG_A_HDRS = $(filter %.h,$(DSP_MPEG_A_FILES))
|
||||
DSP_MPEG_A_SRCS_S = $(filter %.S,$(DSP_MPEG_A_FILES))
|
||||
DSP_MPEG_A_SRCS_C = $(filter %.c,$(DSP_MPEG_A_FILES))
|
||||
|
||||
DSP_MPEG_A_SRCS = \
|
||||
$(DSP_MPEG_A_SRCS_S) \
|
||||
$(DSP_MPEG_A_SRCS_C)
|
||||
|
||||
DSP_MPEG_A_OBJS = \
|
||||
$(DSP_MPEG_A_SRCS:%=o/$(MODE)/%.zip.o) \
|
||||
$(DSP_MPEG_A_SRCS_S:%.S=o/$(MODE)/%.o) \
|
||||
$(DSP_MPEG_A_SRCS_C:%.c=o/$(MODE)/%.o)
|
||||
|
||||
DSP_MPEG_A_CHECKS = \
|
||||
$(DSP_MPEG_A).pkg \
|
||||
$(DSP_MPEG_A_HDRS:%=o/$(MODE)/%.ok)
|
||||
|
||||
DSP_MPEG_A_DIRECTDEPS = \
|
||||
LIBC_CALLS \
|
||||
LIBC_LOG \
|
||||
LIBC_RUNTIME \
|
||||
LIBC_TINYMATH \
|
||||
LIBC_TIME \
|
||||
LIBC_STUBS \
|
||||
LIBC_STR \
|
||||
LIBC_NEXGEN32E \
|
||||
LIBC_STDIO \
|
||||
LIBC_SYSV \
|
||||
LIBC_MEM \
|
||||
LIBC_LOG \
|
||||
LIBC_FMT \
|
||||
LIBC_UNICODE \
|
||||
|
||||
DSP_MPEG_A_DEPS := \
|
||||
$(call uniq,$(foreach x,$(DSP_MPEG_A_DIRECTDEPS),$($(x))))
|
||||
|
||||
$(DSP_MPEG_A): dsp/mpeg/ \
|
||||
$(DSP_MPEG_A).pkg \
|
||||
$(DSP_MPEG_A_OBJS)
|
||||
|
||||
$(DSP_MPEG_A).pkg: \
|
||||
$(DSP_MPEG_A_OBJS) \
|
||||
$(foreach x,$(DSP_MPEG_A_DIRECTDEPS),$($(x)_A).pkg)
|
||||
|
||||
o/$(MODE)/dsp/mpeg/clamp4int256-k8.o: \
|
||||
OVERRIDE_CFLAGS += \
|
||||
-Os
|
||||
|
||||
#o/$(MODE)/dsp/mpeg/macroblock.o: \
|
||||
CC = $(CLANG)
|
||||
|
||||
DSP_MPEG_LIBS = $(foreach x,$(DSP_MPEG_ARTIFACTS),$($(x)))
|
||||
DSP_MPEG_SRCS = $(foreach x,$(DSP_MPEG_ARTIFACTS),$($(x)_SRCS))
|
||||
DSP_MPEG_HDRS = $(foreach x,$(DSP_MPEG_ARTIFACTS),$($(x)_HDRS))
|
||||
DSP_MPEG_CHECKS = $(foreach x,$(DSP_MPEG_ARTIFACTS),$($(x)_CHECKS))
|
||||
DSP_MPEG_OBJS = $(foreach x,$(DSP_MPEG_ARTIFACTS),$($(x)_OBJS))
|
||||
$(DSP_MPEG_OBJS): $(BUILD_FILES) dsp/mpeg/mpeg.mk
|
||||
|
||||
.PHONY: o/$(MODE)/dsp/mpeg
|
||||
o/$(MODE)/dsp/mpeg: $(DSP_MPEG_CHECKS)
|
1115
dsp/mpeg/mpeg1.c
Normal file
1115
dsp/mpeg/mpeg1.c
Normal file
File diff suppressed because it is too large
Load diff
337
dsp/mpeg/plm.c
Normal file
337
dsp/mpeg/plm.c
Normal file
|
@ -0,0 +1,337 @@
|
|||
/*-*- 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/log/log.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/stdio/stdio.h"
|
||||
#include "libc/str/str.h"
|
||||
|
||||
asm(".ident\t\"\\n\\n\
|
||||
PL_MPEG (MIT License)\\n\
|
||||
Copyright(c) 2019 Dominic Szablewski\\n\
|
||||
https://phoboslab.org\"");
|
||||
asm(".include \"libc/disclaimer.inc\"");
|
||||
|
||||
/* 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 correcly. 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;
|
||||
}
|
||||
}
|
||||
}
|
88
dsp/mpeg/slowrgb.c
Normal file
88
dsp/mpeg/slowrgb.c
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*-*- 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"
|
||||
|
||||
asm(".ident\t\"\\n\\n\
|
||||
PL_MPEG (MIT License)\\n\
|
||||
Copyright(c) 2019 Dominic Szablewski\\n\
|
||||
https://phoboslab.org\"");
|
||||
asm(".include \"libc/disclaimer.inc\"");
|
||||
|
||||
/**
|
||||
* @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 proccess
|
||||
// 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;
|
||||
}
|
||||
}
|
62
dsp/mpeg/video.h
Normal file
62
dsp/mpeg/video.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
#ifndef COSMOPOLITAN_DSP_MPEG_VIDEO_H_
|
||||
#define COSMOPOLITAN_DSP_MPEG_VIDEO_H_
|
||||
#include "dsp/mpeg/mpeg.h"
|
||||
#if !(__ASSEMBLER__ + __LINKER__ + 0)
|
||||
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 *restrict,
|
||||
uint8_t *restrict, int, int, bool);
|
||||
void plm_video_process_macroblock_16(plm_video_t *, uint8_t *restrict,
|
||||
uint8_t *restrict, int, int, bool);
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||
#endif /* COSMOPOLITAN_DSP_MPEG_VIDEO_H_ */
|
139
dsp/mpeg/ycbcrio.c
Normal file
139
dsp/mpeg/ycbcrio.c
Normal file
|
@ -0,0 +1,139 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2020 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ This program is free software; you can redistribute it and/or modify │
|
||||
│ it under the terms of the GNU General Public License as published by │
|
||||
│ the Free Software Foundation; version 2 of the License. │
|
||||
│ │
|
||||
│ This program is distributed in the hope that it will be useful, but │
|
||||
│ WITHOUT ANY WARRANTY; without even the implied warranty of │
|
||||
│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
|
||||
│ General Public License for more details. │
|
||||
│ │
|
||||
│ You should have received a copy of the GNU General Public License │
|
||||
│ along with this program; if not, write to the Free Software │
|
||||
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
|
||||
│ 02110-1301 USA │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "dsp/mpeg/mpeg.h"
|
||||
#include "dsp/mpeg/ycbcrio.h"
|
||||
#include "libc/bits/bits.h"
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/calls/struct/stat.h"
|
||||
#include "libc/log/check.h"
|
||||
#include "libc/macros.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/map.h"
|
||||
#include "libc/sysv/consts/o.h"
|
||||
#include "libc/sysv/consts/prot.h"
|
||||
|
||||
static void CheckPlmFrame(const struct plm_frame_t *frame) {
|
||||
CHECK_NE(0, frame->width);
|
||||
CHECK_NE(0, frame->height);
|
||||
CHECK_GE(frame->y.width, frame->width);
|
||||
CHECK_GE(frame->y.height, frame->height);
|
||||
CHECK_EQ(frame->cr.width, frame->cb.width);
|
||||
CHECK_EQ(frame->cr.height, frame->cb.height);
|
||||
CHECK_EQ(frame->y.width, frame->cr.width * 2);
|
||||
CHECK_EQ(frame->y.height, frame->cr.height * 2);
|
||||
CHECK_NOTNULL(frame->y.data);
|
||||
CHECK_NOTNULL(frame->cr.data);
|
||||
CHECK_NOTNULL(frame->cb.data);
|
||||
}
|
||||
|
||||
static size_t GetHeaderBytes(const struct plm_frame_t *frame) {
|
||||
return MAX(sizeof(struct Ycbcrio), ROUNDUP(frame->y.width, 16));
|
||||
}
|
||||
|
||||
static size_t GetPlaneBytes(const struct plm_plane_t *plane) {
|
||||
/*
|
||||
* planes must be 16-byte aligned, but due to their hugeness, and the
|
||||
* recommendation of intel's 6,000 page manual, it makes sense to have
|
||||
* planes on isolated 64kb frames for multiprocessing.
|
||||
*/
|
||||
return ROUNDUP(ROUNDUP(plane->height, 16) * ROUNDUP(plane->width, 16),
|
||||
FRAMESIZE);
|
||||
}
|
||||
|
||||
static size_t CalcMapBytes(const struct plm_frame_t *frame) {
|
||||
return ROUNDUP(GetHeaderBytes(frame) + GetPlaneBytes(&frame->y) +
|
||||
GetPlaneBytes(&frame->cb) + GetPlaneBytes(&frame->cb),
|
||||
FRAMESIZE);
|
||||
}
|
||||
|
||||
static void FixupPointers(struct Ycbcrio *map) {
|
||||
map->frame.y.data = (unsigned char *)map + GetHeaderBytes(&map->frame);
|
||||
map->frame.cr.data = map->frame.y.data + GetPlaneBytes(&map->frame.y);
|
||||
map->frame.cb.data = map->frame.cr.data + GetPlaneBytes(&map->frame.cr);
|
||||
}
|
||||
|
||||
static struct Ycbcrio *YcbcrioOpenNew(const char *path,
|
||||
const struct plm_frame_t *frame) {
|
||||
int fd;
|
||||
size_t size;
|
||||
struct stat st;
|
||||
struct Ycbcrio *map;
|
||||
CheckPlmFrame(frame);
|
||||
size = CalcMapBytes(frame);
|
||||
CHECK_NE(-1, (fd = open(path, O_CREAT | O_RDWR, 0644)));
|
||||
CHECK_NE(-1, fstat(fd, &st));
|
||||
if (st.st_size < size) {
|
||||
CHECK_NE(-1, ftruncate(fd, size));
|
||||
}
|
||||
CHECK_NE(MAP_FAILED,
|
||||
(map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)));
|
||||
map->magic = YCBCRIO_MAGIC;
|
||||
map->fd = fd;
|
||||
map->size = size;
|
||||
memcpy(&map->frame, frame, sizeof(map->frame));
|
||||
FixupPointers(map);
|
||||
memcpy(&map->frame.y.data, frame->y.data, GetPlaneBytes(&frame->y));
|
||||
memcpy(&map->frame.cb.data, frame->cb.data, GetPlaneBytes(&frame->cb));
|
||||
memcpy(&map->frame.cr.data, frame->cr.data, GetPlaneBytes(&frame->cr));
|
||||
return map;
|
||||
}
|
||||
|
||||
static struct Ycbcrio *YcbcrioOpenExisting(const char *path) {
|
||||
int fd;
|
||||
struct stat st;
|
||||
struct Ycbcrio *map;
|
||||
CHECK_NE(-1, (fd = open(path, O_RDWR)));
|
||||
CHECK_NE(-1, fstat(fd, &st));
|
||||
CHECK_NE(MAP_FAILED, (map = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, fd, 0)));
|
||||
CHECK_EQ(YCBCRIO_MAGIC, map->magic);
|
||||
CHECK_GE(st.st_size, CalcMapBytes(&map->frame));
|
||||
FixupPointers(map);
|
||||
map->fd = fd;
|
||||
map->size = st.st_size;
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens shareable persistable MPEG video frame memory.
|
||||
*
|
||||
* @param path is a file name
|
||||
* @param frame if NULL means open existing file, otherwise copies new
|
||||
* @param points to pointer returned by YcbcrioOpen() which is cleared
|
||||
* @return memory mapping needing YcbcrioClose()
|
||||
*/
|
||||
struct Ycbcrio *YcbcrioOpen(const char *path, const struct plm_frame_t *frame) {
|
||||
if (frame) {
|
||||
return YcbcrioOpenNew(path, frame);
|
||||
} else {
|
||||
return YcbcrioOpenExisting(path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes mapped video frame file.
|
||||
*
|
||||
* @param points to pointer returned by YcbcrioOpen() which is cleared
|
||||
*/
|
||||
void YcbcrioClose(struct Ycbcrio **map) {
|
||||
CHECK_NE(-1, close_s(&(*map)->fd));
|
||||
CHECK_NE(-1, munmap_s(map, (*map)->size));
|
||||
}
|
27
dsp/mpeg/ycbcrio.h
Normal file
27
dsp/mpeg/ycbcrio.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
#ifndef COSMOPOLITAN_DSP_MPEG_YCBCRIO_H_
|
||||
#define COSMOPOLITAN_DSP_MPEG_YCBCRIO_H_
|
||||
#include "dsp/mpeg/mpeg.h"
|
||||
#include "libc/bits/bits.h"
|
||||
#if !(__ASSEMBLER__ + __LINKER__ + 0)
|
||||
COSMOPOLITAN_C_START_
|
||||
|
||||
#define YCBCRIO_MAGIC bswap_32(0xBCCBCCBCu)
|
||||
|
||||
/**
|
||||
* Mappable persistable MPEG-2 video frame in Y-Cr-Cb colorspace.
|
||||
*/
|
||||
struct Ycbcrio {
|
||||
uint32_t magic;
|
||||
int32_t fd;
|
||||
uint64_t size;
|
||||
plm_frame_t frame;
|
||||
};
|
||||
|
||||
struct Ycbcrio *YcbcrioOpen(const char *, const struct plm_frame_t *)
|
||||
paramsnonnull((1)) vallocesque returnsnonnull;
|
||||
|
||||
void YcbcrioClose(struct Ycbcrio **) paramsnonnull();
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||
#endif /* COSMOPOLITAN_DSP_MPEG_YCBCRIO_H_ */
|
Loading…
Add table
Add a link
Reference in a new issue