Initial import

This commit is contained in:
Justine Tunney 2020-06-15 07:18:57 -07:00
commit c91b3c5006
14915 changed files with 590219 additions and 0 deletions

92
dsp/mpeg/README.txt Normal file
View 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
View 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
View 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
View 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_ */

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

337
dsp/mpeg/plm.c Normal file
View 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
View 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
View 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
View 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
View 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_ */