Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • flow3r/flow3r-firmware
  • Vespasian/flow3r-firmware
  • alxndr42/flow3r-firmware
  • pl/flow3r-firmware
  • Kari/flow3r-firmware
  • raimue/flow3r-firmware
  • grandchild/flow3r-firmware
  • mu5tach3/flow3r-firmware
  • Nervengift/flow3r-firmware
  • arachnist/flow3r-firmware
  • TheNewCivilian/flow3r-firmware
  • alibi/flow3r-firmware
  • manuel_v/flow3r-firmware
  • xeniter/flow3r-firmware
  • maxbachmann/flow3r-firmware
  • yGifoom/flow3r-firmware
  • istobic/flow3r-firmware
  • EiNSTeiN_/flow3r-firmware
  • gnudalf/flow3r-firmware
  • 999eagle/flow3r-firmware
  • toerb/flow3r-firmware
  • pandark/flow3r-firmware
  • teal/flow3r-firmware
  • x42/flow3r-firmware
  • alufers/flow3r-firmware
  • dos/flow3r-firmware
  • yrlf/flow3r-firmware
  • LuKaRo/flow3r-firmware
  • ThomasElRubio/flow3r-firmware
  • ai/flow3r-firmware
  • T_X/flow3r-firmware
  • highTower/flow3r-firmware
  • beanieboi/flow3r-firmware
  • Woazboat/flow3r-firmware
  • gooniesbro/flow3r-firmware
  • marvino/flow3r-firmware
  • kressnerd/flow3r-firmware
  • quazgar/flow3r-firmware
  • aoid/flow3r-firmware
  • jkj/flow3r-firmware
  • naomi/flow3r-firmware
41 results
Show changes
Commits on Source (4)
Showing
with 8741 additions and 0 deletions
idf_component_register(
SRCS
audio_mod.c
INCLUDE_DIRS
.
../ctx
../st3m
)
#ifndef __clang__
#pragma GCC optimize("O2")
#endif
#include <fcntl.h>
#include <st3m_audio.h>
#include <st3m_media.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "ctx.h"
#define POCKETMOD_IMPLEMENTATION
#include "pocketmod.h"
typedef struct {
st3m_media control;
pocketmod_context pocketmod;
uint8_t *data;
size_t size;
char *path;
} mod_state;
static void mod_draw(st3m_media *media, Ctx *ctx) {
mod_state *self = (void *)media;
ctx_rectangle(ctx, -120, -120, 240, 240);
ctx_gray(ctx, 0);
ctx_fill(ctx);
// ctx_arc(ctx, 0, 0, 10, 10);
ctx_rgb(ctx, 1.0, 1.0, 1.0);
ctx_font_size(ctx, 20);
char buf[100];
sprintf(buf, "p:%i/%i l:%i lc:%i", self->pocketmod.pattern,
self->pocketmod.num_patterns, self->pocketmod.line,
self->pocketmod.loop_count);
ctx_text_align(ctx, CTX_TEXT_ALIGN_CENTER);
ctx_move_to(ctx, 0, -20);
ctx_text(ctx, buf);
ctx_fill(ctx);
ctx_font_size(ctx, 14);
ctx_move_to(ctx, 0, 14);
ctx_gray(ctx, 0.6);
ctx_text(ctx, self->path);
}
static void mod_think(st3m_media *media, float ms_elapsed) {
int samples_needed = (ms_elapsed / 1000.0) * 48000;
if (samples_needed > 1000) samples_needed = 1000;
float rendered[samples_needed * 2];
mod_state *self = (void *)media;
int rend = pocketmod_render(&self->pocketmod, rendered, sizeof(rendered));
for (int i = 0; i < rend / 4; i++) {
self->control.audio_buffer[self->control.audio_w++] =
rendered[i] * 20000;
if (self->control.audio_w >= AUDIO_BUF_SIZE) self->control.audio_w = 0;
}
}
static void mod_destroy(st3m_media *media) {
mod_state *self = (void *)media;
if (self->data) free(self->data);
if (self->path) free(self->path);
free(self);
}
static int file_get_contents(const char *path, uint8_t **contents,
size_t *length) {
FILE *file;
long size;
long remaining;
uint8_t *buffer;
file = fopen(path, "rb");
if (!file) {
return -1;
}
fseek(file, 0, SEEK_END);
size = remaining = ftell(file);
if (length) {
*length = size;
}
rewind(file);
buffer = malloc(size + 2);
if (!buffer) {
fclose(file);
return -1;
}
remaining -= fread(buffer, 1, remaining, file);
if (remaining) {
fclose(file);
free(buffer);
return -1;
}
fclose(file);
*contents = (unsigned char *)buffer;
buffer[size] = 0;
return 0;
}
st3m_media *st3m_media_load_mod(const char *path) {
mod_state *self = (mod_state *)malloc(sizeof(mod_state));
memset(self, 0, sizeof(mod_state));
self->control.draw = mod_draw;
self->control.think = mod_think;
self->control.destroy = mod_destroy;
file_get_contents(path, &self->data, &self->size);
if (!self->data ||
!pocketmod_init(&self->pocketmod, self->data, self->size, 48000)) {
printf("BOOO\n");
if (self->data) free(self->data);
free(self);
return NULL;
}
self->path = strdup(path);
return (st3m_media *)self;
}
/* See end of file for license */
#ifndef POCKETMOD_H_INCLUDED
#define POCKETMOD_H_INCLUDED
#ifdef __cplusplus
extern "C" {
#endif
typedef struct pocketmod_context pocketmod_context;
int pocketmod_init(pocketmod_context *c, const void *data, int size, int rate);
int pocketmod_render(pocketmod_context *c, void *buffer, int size);
int pocketmod_loop_count(pocketmod_context *c);
#ifndef POCKETMOD_MAX_CHANNELS
#define POCKETMOD_MAX_CHANNELS 32
#endif
#ifndef POCKETMOD_MAX_SAMPLES
#define POCKETMOD_MAX_SAMPLES 31
#endif
typedef struct {
signed char *data; /* Sample data buffer */
unsigned int length; /* Data length (in bytes) */
} _pocketmod_sample;
typedef struct {
unsigned char dirty; /* Pitch/volume dirty flags */
unsigned char sample; /* Sample number (0..31) */
unsigned char volume; /* Base volume without tremolo (0..64) */
unsigned char balance; /* Stereo balance (0..255) */
unsigned short period; /* Note period (113..856) */
unsigned short delayed; /* Delayed note period (113..856) */
unsigned short target; /* Target period (for tone portamento) */
unsigned char finetune; /* Note finetune (0..15) */
unsigned char loop_count; /* E6x loop counter */
unsigned char loop_line; /* E6x target line */
unsigned char lfo_step; /* Vibrato/tremolo LFO step counter */
unsigned char lfo_type[2]; /* LFO type for vibrato/tremolo */
unsigned char effect; /* Current effect (0x0..0xf or 0xe0..0xef) */
unsigned char param; /* Raw effect parameter value */
unsigned char param3; /* Parameter memory for 3xx */
unsigned char param4; /* Parameter memory for 4xy */
unsigned char param7; /* Parameter memory for 7xy */
unsigned char param9; /* Parameter memory for 9xx */
unsigned char paramE1; /* Parameter memory for E1x */
unsigned char paramE2; /* Parameter memory for E2x */
unsigned char paramEA; /* Parameter memory for EAx */
unsigned char paramEB; /* Parameter memory for EBx */
unsigned char real_volume; /* Volume (with tremolo adjustment) */
float position; /* Position in sample data buffer */
float increment; /* Position increment per output sample */
} _pocketmod_chan;
struct pocketmod_context {
/* Read-only song data */
_pocketmod_sample samples[POCKETMOD_MAX_SAMPLES];
unsigned char *source; /* Pointer to source MOD data */
unsigned char *order; /* Pattern order table */
unsigned char *patterns; /* Start of pattern data */
unsigned char length; /* Patterns in the order (1..128) */
unsigned char reset; /* Pattern to loop back to (0..127) */
unsigned char num_patterns; /* Patterns in the file (1..128) */
unsigned char num_samples; /* Sample count (15 or 31) */
unsigned char num_channels; /* Channel count (1..32) */
/* Timing variables */
int samples_per_second; /* Sample rate (set by user) */
int ticks_per_line; /* A.K.A. song speed (initially 6) */
float samples_per_tick; /* Depends on sample rate and BPM */
/* Loop detection state */
unsigned char visited[16]; /* Bit mask of previously visited patterns */
int loop_count; /* How many times the song has looped */
/* Render state */
_pocketmod_chan channels[POCKETMOD_MAX_CHANNELS];
unsigned char pattern_delay; /* EEx pattern delay counter */
unsigned int lfo_rng; /* RNG used for the random LFO waveform */
/* Position in song (from least to most granular) */
signed char pattern; /* Current pattern in order */
signed char line; /* Current line in pattern */
short tick; /* Current tick in line */
float sample; /* Current sample in tick */
};
#ifdef POCKETMOD_IMPLEMENTATION
/* Memorize a parameter unless the new value is zero */
#define POCKETMOD_MEM(dst, src) \
do { \
(dst) = (src) ? (src) : (dst); \
} while (0)
/* Same thing, but memorize each nibble separately */
#define POCKETMOD_MEM2(dst, src) \
do { \
(dst) = (((src)&0x0f) ? ((src)&0x0f) : ((dst)&0x0f)) | \
(((src)&0xf0) ? ((src)&0xf0) : ((dst)&0xf0)); \
} while (0)
/* Shortcut to sample metadata (sample must be nonzero) */
#define POCKETMOD_SAMPLE(c, sample) ((c)->source + 12 + 30 * (sample))
/* Channel dirty flags */
#define POCKETMOD_PITCH 0x01
#define POCKETMOD_VOLUME 0x02
/* The size of one sample in bytes */
#define POCKETMOD_SAMPLE_SIZE sizeof(float[2])
/* Finetune adjustment table. Three octaves for each finetune setting. */
static const signed char _pocketmod_finetune[16][36] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ -6, -6, -5, -5, -4, -3, -3, -3, -3, -3, -3, -3, -3, -3, -2, -3, -2, -2,
-2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0 },
{ -12, -12, -10, -11, -8, -8, -7, -7, -6, -6, -6, -6,
-6, -6, -5, -5, -4, -4, -4, -3, -3, -3, -3, -2,
-3, -3, -2, -3, -3, -2, -2, -2, -2, -2, -2, -1 },
{ -18, -17, -16, -16, -13, -12, -12, -11, -10, -10, -10, -9,
-9, -9, -8, -8, -7, -6, -6, -5, -5, -5, -5, -4,
-5, -4, -3, -4, -4, -3, -3, -3, -3, -2, -2, -2 },
{ -24, -23, -21, -21, -18, -17, -16, -15, -14, -13, -13, -12,
-12, -12, -11, -10, -9, -8, -8, -7, -7, -7, -7, -6,
-6, -6, -5, -5, -5, -4, -4, -4, -4, -3, -3, -3 },
{ -30, -29, -26, -26, -23, -21, -20, -19, -18, -17, -17, -16,
-15, -14, -13, -13, -11, -11, -10, -9, -9, -9, -8, -7,
-8, -7, -6, -6, -6, -5, -5, -5, -5, -4, -4, -4 },
{ -36, -34, -32, -31, -27, -26, -24, -23, -22, -21, -20, -19,
-18, -17, -16, -15, -14, -13, -12, -11, -11, -10, -10, -9,
-9, -9, -7, -8, -7, -6, -6, -6, -6, -5, -5, -4 },
{ -42, -40, -37, -36, -32, -30, -29, -27, -25, -24, -23, -22,
-21, -20, -18, -18, -16, -15, -14, -13, -13, -12, -12, -10,
-10, -10, -9, -9, -9, -8, -7, -7, -7, -6, -6, -5 },
{ 51, 48, 46, 42, 42, 38, 36, 34, 32, 30, 24, 27, 25, 24, 23, 21, 21, 19,
18, 17, 16, 15, 14, 14, 12, 12, 12, 10, 10, 10, 9, 8, 8, 8, 7, 7 },
{ 44, 42, 40, 37, 37, 35, 32, 31, 29, 27, 25, 24, 22, 21, 20, 19, 18, 17,
16, 15, 15, 14, 13, 12, 11, 10, 10, 9, 9, 9, 8, 7, 7, 7, 6, 6 },
{ 38, 36, 34, 32, 31, 30, 28, 27, 25, 24, 22, 21, 19, 18, 17, 16, 16, 15,
14, 13, 13, 12, 11, 11, 9, 9, 9, 8, 7, 7, 7, 6, 6, 6, 5, 5 },
{ 31, 30, 29, 26, 26, 25, 24, 22, 21, 20, 18, 17, 16, 15, 14, 13, 13, 12,
12, 11, 11, 10, 9, 9, 8, 7, 8, 7, 6, 6, 6, 5, 5, 5, 5, 5 },
{ 25, 24, 23, 21, 21, 20, 19, 18, 17, 16, 14, 14, 13, 12, 11, 10, 11, 10,
10, 9, 9, 8, 7, 7, 6, 6, 6, 5, 5, 5, 5, 4, 4, 4, 3, 4 },
{ 19, 18, 17, 16, 16, 15, 15, 14, 13, 12, 11, 10, 9, 9, 9, 8, 8, 18,
7, 7, 7, 6, 5, 6, 5, 4, 5, 4, 4, 4, 4, 3, 3, 3, 3, 3 },
{ 12, 12, 12, 10, 11, 11, 10, 10, 9, 8, 7, 7, 6, 6, 6, 5, 6, 5,
5, 5, 5, 4, 4, 4, 3, 3, 3, 3, 2, 3, 3, 2, 2, 2, 2, 2 },
{ 6, 6, 6, 5, 6, 6, 6, 5, 5, 5, 4, 4, 3, 3, 3, 3, 3, 3,
3, 3, 3, 2, 2, 2, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
};
/* Min/max helper functions */
static int _pocketmod_min(int x, int y) { return x < y ? x : y; }
static int _pocketmod_max(int x, int y) { return x > y ? x : y; }
/* Clamp a volume value to the 0..64 range */
static int _pocketmod_clamp_volume(int x) {
x = _pocketmod_max(x, 0x00);
x = _pocketmod_min(x, 0x40);
return x;
}
/* Zero out a block of memory */
static void _pocketmod_zero(void *data, int size) {
char *byte = data, *end = byte + size;
while (byte != end) {
*byte++ = 0;
}
}
/* Convert a period (at finetune = 0) to a note index in 0..35 */
static int _pocketmod_period_to_note(int period) {
switch (period) {
case 856:
return 0;
case 808:
return 1;
case 762:
return 2;
case 720:
return 3;
case 678:
return 4;
case 640:
return 5;
case 604:
return 6;
case 570:
return 7;
case 538:
return 8;
case 508:
return 9;
case 480:
return 10;
case 453:
return 11;
case 428:
return 12;
case 404:
return 13;
case 381:
return 14;
case 360:
return 15;
case 339:
return 16;
case 320:
return 17;
case 302:
return 18;
case 285:
return 19;
case 269:
return 20;
case 254:
return 21;
case 240:
return 22;
case 226:
return 23;
case 214:
return 24;
case 202:
return 25;
case 190:
return 26;
case 180:
return 27;
case 170:
return 28;
case 160:
return 29;
case 151:
return 30;
case 143:
return 31;
case 135:
return 32;
case 127:
return 33;
case 120:
return 34;
case 113:
return 35;
default:
return 0;
}
}
/* Table-based sine wave oscillator */
static int _pocketmod_sin(int step) {
/* round(sin(x * pi / 32) * 255) for x in 0..15 */
static const unsigned char sin[16] = { 0x00, 0x19, 0x32, 0x4a, 0x62, 0x78,
0x8e, 0xa2, 0xb4, 0xc5, 0xd4, 0xe0,
0xec, 0xf4, 0xfa, 0xfe };
int x = sin[step & 0x0f];
x = (step & 0x1f) < 0x10 ? x : 0xff - x;
return step < 0x20 ? x : -x;
}
/* Oscillators for vibrato/tremolo effects */
static int _pocketmod_lfo(pocketmod_context *c, _pocketmod_chan *ch, int step) {
switch (ch->lfo_type[ch->effect == 7] & 3) {
case 0:
return _pocketmod_sin(step & 0x3f); /* Sine */
case 1:
return 0xff - ((step & 0x3f) << 3); /* Saw */
case 2:
return (step & 0x3f) < 0x20 ? 0xff : -0xff; /* Square */
case 3:
return (c->lfo_rng & 0x1ff) - 0xff; /* Random */
default:
return 0; /* Hush little compiler */
}
}
static void _pocketmod_update_pitch(pocketmod_context *c, _pocketmod_chan *ch) {
/* Don't do anything if the period is zero */
ch->increment = 0.0f;
if (ch->period) {
float period = ch->period;
/* Apply vibrato (if active) */
if (ch->effect == 0x4 || ch->effect == 0x6) {
int step = (ch->param4 >> 4) * ch->lfo_step;
int rate = ch->param4 & 0x0f;
period += _pocketmod_lfo(c, ch, step) * rate / 128.0f;
/* Apply arpeggio (if active) */
} else if (ch->effect == 0x0 && ch->param) {
static const float arpeggio[16] = {
/* 2^(X/12) for X in 0..15 */
1.000000f, 1.059463f, 1.122462f, 1.189207f,
1.259921f, 1.334840f, 1.414214f, 1.498307f,
1.587401f, 1.681793f, 1.781797f, 1.887749f,
2.000000f, 2.118926f, 2.244924f, 2.378414f
};
int step = (ch->param >> ((2 - c->tick % 3) << 2)) & 0x0f;
period /= arpeggio[step];
}
/* Calculate sample buffer position increment */
ch->increment = 3546894.6f / (period * c->samples_per_second);
}
/* Clear the pitch dirty flag */
ch->dirty &= ~POCKETMOD_PITCH;
}
static void _pocketmod_update_volume(pocketmod_context *c,
_pocketmod_chan *ch) {
int volume = ch->volume;
if (ch->effect == 0x7) {
int step = ch->lfo_step * (ch->param7 >> 4);
volume += _pocketmod_lfo(c, ch, step) * (ch->param7 & 0x0f) >> 6;
}
ch->real_volume = _pocketmod_clamp_volume(volume);
ch->dirty &= ~POCKETMOD_VOLUME;
}
static void _pocketmod_pitch_slide(_pocketmod_chan *ch, int amount) {
int max = 856 + _pocketmod_finetune[ch->finetune][0];
int min = 113 + _pocketmod_finetune[ch->finetune][35];
ch->period += amount;
ch->period = _pocketmod_max(ch->period, min);
ch->period = _pocketmod_min(ch->period, max);
ch->dirty |= POCKETMOD_PITCH;
}
static void _pocketmod_volume_slide(_pocketmod_chan *ch, int param) {
/* Undocumented quirk: If both x and y are nonzero, then the value of x */
/* takes precedence. (Yes, there are songs that rely on this behavior.) */
int change = (param & 0xf0) ? (param >> 4) : -(param & 0x0f);
ch->volume = _pocketmod_clamp_volume(ch->volume + change);
ch->dirty |= POCKETMOD_VOLUME;
}
static void _pocketmod_next_line(pocketmod_context *c) {
unsigned char(*data)[4];
int i, pos, pattern_break = -1;
/* When entering a new pattern order index, mark it as "visited" */
if (c->line == 0) {
c->visited[c->pattern >> 3] |= 1 << (c->pattern & 7);
}
/* Move to the next pattern if this was the last line */
if (++c->line == 64) {
if (++c->pattern == c->length) {
c->pattern = c->reset;
}
c->line = 0;
}
/* Find the pattern data for the current line */
pos = (c->order[c->pattern] * 64 + c->line) * c->num_channels * 4;
data = (unsigned char(*)[4])(c->patterns + pos);
for (i = 0; i < c->num_channels; i++) {
/* Decode columns */
int sample = (data[i][0] & 0xf0) | (data[i][2] >> 4);
int period = ((data[i][0] & 0x0f) << 8) | data[i][1];
int effect = ((data[i][2] & 0x0f) << 8) | data[i][3];
/* Memorize effect parameter values */
_pocketmod_chan *ch = &c->channels[i];
ch->effect = (effect >> 8) != 0xe ? (effect >> 8) : (effect >> 4);
ch->param = (effect >> 8) != 0xe ? (effect & 0xff) : (effect & 0x0f);
/* Set sample */
if (sample) {
if (sample <= POCKETMOD_MAX_SAMPLES) {
unsigned char *sample_data = POCKETMOD_SAMPLE(c, sample);
ch->sample = sample;
ch->finetune = sample_data[2] & 0x0f;
ch->volume = _pocketmod_min(sample_data[3], 0x40);
if (ch->effect != 0xED) {
ch->dirty |= POCKETMOD_VOLUME;
}
} else {
ch->sample = 0;
}
}
/* Set note */
if (period) {
int note = _pocketmod_period_to_note(period);
period += _pocketmod_finetune[ch->finetune][note];
if (ch->effect != 0x3) {
if (ch->effect != 0xED) {
ch->period = period;
ch->dirty |= POCKETMOD_PITCH;
ch->position = 0.0f;
ch->lfo_step = 0;
} else {
ch->delayed = period;
}
}
}
/* Handle pattern effects */
switch (ch->effect) {
/* Memorize parameters */
case 0x3:
POCKETMOD_MEM(ch->param3, ch->param); /* Fall through */
case 0x5:
POCKETMOD_MEM(ch->target, period);
break;
case 0x4:
POCKETMOD_MEM2(ch->param4, ch->param);
break;
case 0x7:
POCKETMOD_MEM2(ch->param7, ch->param);
break;
case 0xE1:
POCKETMOD_MEM(ch->paramE1, ch->param);
break;
case 0xE2:
POCKETMOD_MEM(ch->paramE2, ch->param);
break;
case 0xEA:
POCKETMOD_MEM(ch->paramEA, ch->param);
break;
case 0xEB:
POCKETMOD_MEM(ch->paramEB, ch->param);
break;
/* 8xx: Set stereo balance (nonstandard) */
case 0x8: {
ch->balance = ch->param;
} break;
/* 9xx: Set sample offset */
case 0x9: {
if (period != 0 || sample != 0) {
ch->param9 = ch->param ? ch->param : ch->param9;
ch->position = ch->param9 << 8;
}
} break;
/* Bxx: Jump to pattern */
case 0xB: {
c->pattern = ch->param < c->length ? ch->param : 0;
c->line = -1;
} break;
/* Cxx: Set volume */
case 0xC: {
ch->volume = _pocketmod_clamp_volume(ch->param);
ch->dirty |= POCKETMOD_VOLUME;
} break;
/* Dxy: Pattern break */
case 0xD: {
pattern_break = (ch->param >> 4) * 10 + (ch->param & 15);
} break;
/* E4x: Set vibrato waveform */
case 0xE4: {
ch->lfo_type[0] = ch->param;
} break;
/* E5x: Set sample finetune */
case 0xE5: {
ch->finetune = ch->param;
ch->dirty |= POCKETMOD_PITCH;
} break;
/* E6x: Pattern loop */
case 0xE6: {
if (ch->param) {
if (!ch->loop_count) {
ch->loop_count = ch->param;
c->line = ch->loop_line;
} else if (--ch->loop_count) {
c->line = ch->loop_line;
}
} else {
ch->loop_line = c->line - 1;
}
} break;
/* E7x: Set tremolo waveform */
case 0xE7: {
ch->lfo_type[1] = ch->param;
} break;
/* E8x: Set stereo balance (nonstandard) */
case 0xE8: {
ch->balance = ch->param << 4;
} break;
/* EEx: Pattern delay */
case 0xEE: {
c->pattern_delay = ch->param;
} break;
/* Fxx: Set speed */
case 0xF: {
if (ch->param != 0) {
if (ch->param < 0x20) {
c->ticks_per_line = ch->param;
} else {
float rate = c->samples_per_second;
c->samples_per_tick = rate / (0.4f * ch->param);
}
}
} break;
default:
break;
}
}
/* Pattern breaks are handled here, so that only one jump happens even */
/* when multiple Dxy commands appear on the same line. (You guessed it: */
/* There are songs that rely on this behavior!) */
if (pattern_break != -1) {
c->line = (pattern_break < 64 ? pattern_break : 0) - 1;
if (++c->pattern == c->length) {
c->pattern = c->reset;
}
}
}
static void _pocketmod_next_tick(pocketmod_context *c) {
int i;
/* Move to the next line if this was the last tick */
if (++c->tick == c->ticks_per_line) {
if (c->pattern_delay > 0) {
c->pattern_delay--;
} else {
_pocketmod_next_line(c);
}
c->tick = 0;
}
/* Make per-tick adjustments for all channels */
for (i = 0; i < c->num_channels; i++) {
_pocketmod_chan *ch = &c->channels[i];
int param = ch->param;
/* Advance the LFO random number generator */
c->lfo_rng = 0x0019660d * c->lfo_rng + 0x3c6ef35f;
/* Handle effects that may happen on any tick of a line */
switch (ch->effect) {
/* 0xy: Arpeggio */
case 0x0: {
ch->dirty |= POCKETMOD_PITCH;
} break;
/* E9x: Retrigger note every x ticks */
case 0xE9: {
if (!(param && c->tick % param)) {
ch->position = 0.0f;
ch->lfo_step = 0;
}
} break;
/* ECx: Cut note after x ticks */
case 0xEC: {
if (c->tick == param) {
ch->volume = 0;
ch->dirty |= POCKETMOD_VOLUME;
}
} break;
/* EDx: Delay note for x ticks */
case 0xED: {
if (c->tick == param && ch->sample) {
ch->dirty |= POCKETMOD_VOLUME | POCKETMOD_PITCH;
ch->period = ch->delayed;
ch->position = 0.0f;
ch->lfo_step = 0;
}
} break;
default:
break;
}
/* Handle effects that only happen on the first tick of a line */
if (c->tick == 0) {
switch (ch->effect) {
case 0xE1:
_pocketmod_pitch_slide(ch, -ch->paramE1);
break;
case 0xE2:
_pocketmod_pitch_slide(ch, +ch->paramE2);
break;
case 0xEA:
_pocketmod_volume_slide(ch, ch->paramEA << 4);
break;
case 0xEB:
_pocketmod_volume_slide(ch, ch->paramEB & 15);
break;
default:
break;
}
/* Handle effects that are not applied on the first tick of a line
*/
} else {
switch (ch->effect) {
/* 1xx: Portamento up */
case 0x1: {
_pocketmod_pitch_slide(ch, -param);
} break;
/* 2xx: Portamento down */
case 0x2: {
_pocketmod_pitch_slide(ch, +param);
} break;
/* 5xy: Volume slide + tone portamento */
case 0x5: {
_pocketmod_volume_slide(ch, param);
} /* Fall through */
/* 3xx: Tone portamento */
case 0x3: {
int rate = ch->param3;
int order = ch->period < ch->target;
int closer = ch->period + (order ? rate : -rate);
int new_order = closer < ch->target;
ch->period = new_order == order ? closer : ch->target;
ch->dirty |= POCKETMOD_PITCH;
} break;
/* 6xy: Volume slide + vibrato */
case 0x6: {
_pocketmod_volume_slide(ch, param);
} /* Fall through */
/* 4xy: Vibrato */
case 0x4: {
ch->lfo_step++;
ch->dirty |= POCKETMOD_PITCH;
} break;
/* 7xy: Tremolo */
case 0x7: {
ch->lfo_step++;
ch->dirty |= POCKETMOD_VOLUME;
} break;
/* Axy: Volume slide */
case 0xA: {
_pocketmod_volume_slide(ch, param);
} break;
default:
break;
}
}
/* Update channel volume/pitch if either is out of date */
if (ch->dirty & POCKETMOD_VOLUME) {
_pocketmod_update_volume(c, ch);
}
if (ch->dirty & POCKETMOD_PITCH) {
_pocketmod_update_pitch(c, ch);
}
}
}
static void _pocketmod_render_channel(pocketmod_context *c,
_pocketmod_chan *chan, float *output,
int samples_to_write) {
/* Gather some loop data */
_pocketmod_sample *sample = &c->samples[chan->sample - 1];
unsigned char *data = POCKETMOD_SAMPLE(c, chan->sample);
const int loop_start = ((data[4] << 8) | data[5]) << 1;
const int loop_length = ((data[6] << 8) | data[7]) << 1;
const int loop_end = loop_length > 2 ? loop_start + loop_length : 0xffffff;
const float sample_end = 1 + _pocketmod_min(loop_end, sample->length);
/* Calculate left/right levels */
const float volume = chan->real_volume / (float)(128 * 64 * 4);
const float level_l = volume * (1.0f - chan->balance / 255.0f);
const float level_r = volume * (0.0f + chan->balance / 255.0f);
/* Write samples */
int i, num;
do {
/* Calculate how many samples we can write in one go */
num = (sample_end - chan->position) / chan->increment;
num = _pocketmod_min(num, samples_to_write);
/* Resample and write 'num' samples */
for (i = 0; i < num; i++) {
int x0 = chan->position;
#ifdef POCKETMOD_NO_INTERPOLATION
float s = sample->data[x0];
#else
int x1 = x0 + 1 - loop_length * (x0 + 1 >= loop_end);
float t = chan->position - x0;
float s = (1.0f - t) * sample->data[x0] + t * sample->data[x1];
#endif
chan->position += chan->increment;
*output++ += level_l * s;
*output++ += level_r * s;
}
/* Rewind the sample when reaching the loop point */
if (chan->position >= loop_end) {
chan->position -= loop_length;
/* Cut the sample if the end is reached */
} else if (chan->position >= sample->length) {
chan->position = -1.0f;
break;
}
samples_to_write -= num;
} while (num > 0);
}
static int _pocketmod_ident(pocketmod_context *c, unsigned char *data,
int size) {
int i, j;
/* 31-instrument files are at least 1084 bytes long */
if (size >= 1084) {
/* The format tag is located at offset 1080 */
unsigned char *tag = data + 1080;
/* List of recognized format tags (possibly incomplete) */
static const struct {
char name[5];
char channels;
} tags[] = {
/* TODO: FLT8 intentionally omitted because I haven't been able */
/* to find a specimen to test its funky pattern pairing format */
{ "M.K.", 4 }, { "M!K!", 4 }, { "FLT4", 4 }, { "4CHN", 4 },
{ "OKTA", 8 }, { "OCTA", 8 }, { "CD81", 8 }, { "FA08", 8 },
{ "1CHN", 1 }, { "2CHN", 2 }, { "3CHN", 3 }, { "4CHN", 4 },
{ "5CHN", 5 }, { "6CHN", 6 }, { "7CHN", 7 }, { "8CHN", 8 },
{ "9CHN", 9 }, { "10CH", 10 }, { "11CH", 11 }, { "12CH", 12 },
{ "13CH", 13 }, { "14CH", 14 }, { "15CH", 15 }, { "16CH", 16 },
{ "17CH", 17 }, { "18CH", 18 }, { "19CH", 19 }, { "20CH", 20 },
{ "21CH", 21 }, { "22CH", 22 }, { "23CH", 23 }, { "24CH", 24 },
{ "25CH", 25 }, { "26CH", 26 }, { "27CH", 27 }, { "28CH", 28 },
{ "29CH", 29 }, { "30CH", 30 }, { "31CH", 31 }, { "32CH", 32 }
};
/* Check the format tag to determine if this is a 31-sample MOD */
for (i = 0; i < (int)(sizeof(tags) / sizeof(*tags)); i++) {
if (tags[i].name[0] == tag[0] && tags[i].name[1] == tag[1] &&
tags[i].name[2] == tag[2] && tags[i].name[3] == tag[3]) {
c->num_channels = tags[i].channels;
c->length = data[950];
c->reset = data[951];
c->order = &data[952];
c->patterns = &data[1084];
c->num_samples = 31;
return 1;
}
}
}
/* A 15-instrument MOD has to be at least 600 bytes long */
if (size < 600) {
return 0;
}
/* Check that the song title only contains ASCII bytes (or null) */
for (i = 0; i < 20; i++) {
if (data[i] != '\0' && (data[i] < ' ' || data[i] > '~')) {
return 0;
}
}
/* Check that sample names only contain ASCII bytes (or null) */
for (i = 0; i < 15; i++) {
for (j = 0; j < 22; j++) {
char chr = data[20 + i * 30 + j];
if (chr != '\0' && (chr < ' ' || chr > '~')) {
return 0;
}
}
}
/* It looks like we have an older 15-instrument MOD */
c->length = data[470];
c->reset = data[471];
c->order = &data[472];
c->patterns = &data[600];
c->num_samples = 15;
c->num_channels = 4;
return 1;
}
int pocketmod_init(pocketmod_context *c, const void *data, int size, int rate) {
int i, remaining, header_bytes, pattern_bytes;
unsigned char *byte = (unsigned char *)c;
signed char *sample_data;
/* Check that arguments look more or less sane */
if (!c || !data || rate <= 0 || size <= 0) {
return 0;
}
/* Zero out the whole context and identify the MOD type */
_pocketmod_zero(c, sizeof(pocketmod_context));
c->source = (unsigned char *)data;
if (!_pocketmod_ident(c, c->source, size)) {
return 0;
}
/* Check that we are compiled with support for enough channels */
if (c->num_channels > POCKETMOD_MAX_CHANNELS) {
return 0;
}
/* Check that we have enough sample slots for this file */
if (POCKETMOD_MAX_SAMPLES < 31) {
byte = (unsigned char *)data + 20;
for (i = 0; i < c->num_samples; i++) {
unsigned int length = 2 * ((byte[22] << 8) | byte[23]);
if (i >= POCKETMOD_MAX_SAMPLES && length > 2) {
return 0; /* Can't fit this sample */
}
byte += 30;
}
}
/* Check that the song length is in valid range (1..128) */
if (c->length == 0 || c->length > 128) {
return 0;
}
/* Make sure that the reset pattern doesn't take us out of bounds */
if (c->reset >= c->length) {
c->reset = 0;
}
/* Count how many patterns there are in the file */
c->num_patterns = 0;
for (i = 0; i < 128 && c->order[i] < 128; i++) {
c->num_patterns = _pocketmod_max(c->num_patterns, c->order[i]);
}
pattern_bytes = 256 * c->num_channels * ++c->num_patterns;
header_bytes = (int)((char *)c->patterns - (char *)data);
/* Check that each pattern in the order is within file bounds */
for (i = 0; i < c->length; i++) {
if (header_bytes + 256 * c->num_channels * c->order[i] > size) {
return 0; /* Reading this pattern would be a buffer over-read! */
}
}
/* Check that the pattern data doesn't extend past the end of the file */
if (header_bytes + pattern_bytes > size) {
return 0;
}
/* Load sample payload data, truncating ones that extend outside the file */
remaining = size - header_bytes - pattern_bytes;
sample_data = (signed char *)data + header_bytes + pattern_bytes;
for (i = 0; i < c->num_samples; i++) {
unsigned char *data = POCKETMOD_SAMPLE(c, i + 1);
unsigned int length = ((data[0] << 8) | data[1]) << 1;
_pocketmod_sample *sample = &c->samples[i];
sample->data = sample_data;
sample->length = _pocketmod_min(length > 2 ? length : 0, remaining);
sample_data += sample->length;
remaining -= sample->length;
}
/* Set up ProTracker default panning for all channels */
for (i = 0; i < c->num_channels; i++) {
c->channels[i].balance = 0x80 + ((((i + 1) >> 1) & 1) ? 0x20 : -0x20);
}
/* Prepare to render from the start */
c->ticks_per_line = 6;
c->samples_per_second = rate;
c->samples_per_tick = rate / 50.0f;
c->lfo_rng = 0xbadc0de;
c->line = -1;
c->tick = c->ticks_per_line - 1;
_pocketmod_next_tick(c);
return 1;
}
int pocketmod_render(pocketmod_context *c, void *buffer, int buffer_size) {
int i, samples_rendered = 0;
int samples_remaining = buffer_size / POCKETMOD_SAMPLE_SIZE;
if (c && buffer) {
float(*output)[2] = (float(*)[2])buffer;
while (samples_remaining > 0) {
/* Calculate the number of samples left in this tick */
int num = (int)(c->samples_per_tick - c->sample);
num = _pocketmod_min(num + !num, samples_remaining);
/* Render and mix 'num' samples from each channel */
_pocketmod_zero(output, num * POCKETMOD_SAMPLE_SIZE);
for (i = 0; i < c->num_channels; i++) {
_pocketmod_chan *chan = &c->channels[i];
if (chan->sample != 0 && chan->position >= 0.0f) {
_pocketmod_render_channel(c, chan, *output, num);
}
}
samples_remaining -= num;
samples_rendered += num;
output += num;
/* Advance song position by 'num' samples */
if ((c->sample += num) >= c->samples_per_tick) {
c->sample -= c->samples_per_tick;
_pocketmod_next_tick(c);
/* Stop if a new pattern was reached */
if (c->line == 0 && c->tick == 0) {
/* Increment loop counter as needed */
if (c->visited[c->pattern >> 3] & (1 << (c->pattern & 7))) {
_pocketmod_zero(c->visited, sizeof(c->visited));
c->loop_count++;
}
break;
}
}
}
}
return samples_rendered * POCKETMOD_SAMPLE_SIZE;
}
int pocketmod_loop_count(pocketmod_context *c) { return c->loop_count; }
#endif /* #ifdef POCKETMOD_IMPLEMENTATION */
#ifdef __cplusplus
}
#endif
#endif /* #ifndef POCKETMOD_H_INCLUDED */
/*******************************************************************************
MIT License
Copyright (c) 2018 rombankzero
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.
*******************************************************************************/
idf_component_register(
SRCS
audio_mp3.c
INCLUDE_DIRS
.
../ctx
../st3m
)
CC0 1.0 Universal
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator and
subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for the
purpose of contributing to a commons of creative, cultural and scientific
works ("Commons") that the public can reliably and without fear of later
claims of infringement build upon, modify, incorporate in other works, reuse
and redistribute as freely as possible in any form whatsoever and for any
purposes, including without limitation commercial purposes. These owners may
contribute to the Commons to promote the ideal of a free culture and the
further production of creative, cultural and scientific works, or to gain
reputation or greater distribution for their Work in part through the use and
efforts of others.
For these and/or other purposes and motivations, and without any expectation
of additional consideration or compensation, the person associating CC0 with a
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
and publicly distribute the Work under its terms, with knowledge of his or her
Copyright and Related Rights in the Work and the meaning and intended legal
effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not limited
to, the following:
i. the right to reproduce, adapt, distribute, perform, display, communicate,
and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or likeness
depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data in
a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation thereof,
including any amended or successor version of such directive); and
vii. other similar, equivalent or corresponding rights throughout the world
based on applicable law or treaty, and any national implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention of,
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
and Related Rights and associated claims and causes of action, whether now
known or unknown (including existing as well as future claims and causes of
action), in the Work (i) in all territories worldwide, (ii) for the maximum
duration provided by applicable law or treaty (including future time
extensions), (iii) in any current or future medium and for any number of
copies, and (iv) for any purpose whatsoever, including without limitation
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
the Waiver for the benefit of each member of the public at large and to the
detriment of Affirmer's heirs and successors, fully intending that such Waiver
shall not be subject to revocation, rescission, cancellation, termination, or
any other legal or equitable action to disrupt the quiet enjoyment of the Work
by the public as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason be
judged legally invalid or ineffective under applicable law, then the Waiver
shall be preserved to the maximum extent permitted taking into account
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
is so judged Affirmer hereby grants to each affected person a royalty-free,
non transferable, non sublicensable, non exclusive, irrevocable and
unconditional license to exercise Affirmer's Copyright and Related Rights in
the Work (i) in all territories worldwide, (ii) for the maximum duration
provided by applicable law or treaty (including future time extensions), (iii)
in any current or future medium and for any number of copies, and (iv) for any
purpose whatsoever, including without limitation commercial, advertising or
promotional purposes (the "License"). The License shall be deemed effective as
of the date CC0 was applied by Affirmer to the Work. Should any part of the
License for any reason be judged legally invalid or ineffective under
applicable law, such partial invalidity or ineffectiveness shall not
invalidate the remainder of the License, and in such case Affirmer hereby
affirms that he or she will not (i) exercise any of his or her remaining
Copyright and Related Rights in the Work or (ii) assert any associated claims
and causes of action with respect to the Work, in either case contrary to
Affirmer's express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or warranties
of any kind concerning the Work, express, implied, statutory or otherwise,
including without limitation warranties of title, merchantability, fitness
for a particular purpose, non infringement, or the absence of latent or
other defects, accuracy, or the present or absence of errors, whether or not
discoverable, all to the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without limitation
any person's Copyright and Related Rights in the Work. Further, Affirmer
disclaims responsibility for obtaining any necessary consents, permissions
or other rights required for any use of the Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to this
CC0 or use of the Work.
For more information, please see
<http://creativecommons.org/publicdomain/zero/1.0/>
#ifndef __clang__
#pragma GCC optimize("O2")
#endif
#include <fcntl.h>
#include <st3m_audio.h>
#include <st3m_media.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "lwip/igmp.h"
#include "lwip/ip4.h"
#include "lwip/netdb.h"
#include "lwip/sockets.h"
#include "ctx.h"
#define MINIMP3_NONSTANDARD_BUT_LOGICAL
#define MINIMP3_NO_SIMD
#define MINIMP3_IMPLEMENTATION
#include "minimp3.h"
typedef struct {
st3m_media control;
mp3dec_t mp3d;
char *path;
char *artist;
char *title;
int year;
int started;
int samplerate;
int channels;
uint8_t *data;
size_t size;
size_t count;
int pos;
int offset;
int buffer_size;
int file_size;
FILE *file;
int socket;
int in_buffering;
} mp3_state;
static int has_data(mp3_state *mp3) {
if (mp3->file) return 1;
fd_set rfds;
struct timeval tv = { 0, 0 };
FD_ZERO(&rfds);
FD_SET(mp3->socket, &rfds);
if (select(mp3->socket + 1, &rfds, NULL, NULL, &tv) == 1)
return FD_ISSET(mp3->socket, &rfds);
return 0;
}
static void mp3_fetch_data(mp3_state *mp3) {
if (mp3->pos) {
memmove(mp3->data, &mp3->data[mp3->pos], mp3->count - mp3->pos);
mp3->offset += mp3->pos;
mp3->count -= mp3->pos;
mp3->pos = 0;
}
// if incoming data-buffer falls below 16kb - do a full buffer fill
if (!mp3->file && (mp3->count < 16 * 1024)) {
mp3->in_buffering = 1;
}
if ((mp3->size - mp3->count > 0) && has_data(mp3)) {
int desire_bytes = (mp3->size - mp3->count);
if (desire_bytes > 2048) desire_bytes = 2048;
if (desire_bytes) {
int read_bytes;
if (mp3->file)
read_bytes =
fread(mp3->data + mp3->count, 1, desire_bytes, mp3->file);
else
read_bytes =
read(mp3->socket, mp3->data + mp3->count, desire_bytes);
mp3->count += read_bytes;
}
}
}
static void mp3_draw(st3m_media *media, Ctx *ctx) {
mp3_state *self = (void *)media;
ctx_rectangle(ctx, -120, -120, 240, 240);
ctx_gray(ctx, 0);
ctx_fill(ctx);
ctx_rgb(ctx, 1.0, 1.0, 1.0);
ctx_rectangle(ctx, -120, 0, 240, 1);
ctx_rectangle(ctx, -120 + self->offset * 240.0 / self->file_size, -32, 2,
64);
ctx_fill(ctx);
ctx_font_size(ctx, 24);
ctx_text_align(ctx, CTX_TEXT_ALIGN_CENTER);
ctx_move_to(ctx, 0, -40);
ctx_text(ctx, self->artist);
ctx_move_to(ctx, 0, 64);
ctx_text(ctx, self->title);
ctx_font_size(ctx, 14);
ctx_move_to(ctx, 0, 14);
ctx_gray(ctx, 0.6);
ctx_text(ctx, self->path);
if (!self->file) {
ctx_rectangle(ctx, -100, 65, self->count * 200.0 / self->size, 55);
if (self->in_buffering)
ctx_rgba(ctx, 0.8, 0.2, 0.0, 1.0);
else
ctx_gray(ctx, 0.2);
ctx_fill(ctx);
}
}
static void mp3_think(st3m_media *media, float ms_elapsed) {
mp3_state *self = (void *)media;
mp3_fetch_data(self);
if (self->in_buffering) {
if (self->size - self->count > 0) return;
self->in_buffering = 0;
}
if (!self->started) {
self->started = 1;
mp3_think(media, 100);
}
int samples_needed =
((AUDIO_BUF_SIZE - st3m_media_samples_queued()) / 2) - 2400;
int samples;
mp3dec_frame_info_t info = {
0,
};
if (samples_needed > 0 &&
((self->offset + 512 < self->file_size) || (!self->file))) {
do {
int16_t rendered[MINIMP3_MAX_SAMPLES_PER_FRAME];
samples =
mp3dec_decode_frame(&self->mp3d, self->data + self->pos,
self->count - self->pos, rendered, &info);
self->samplerate = info.hz;
self->channels = info.channels;
if (info.frame_bytes > samples) {
printf("[[%s]]\n", self->data + self->pos);
}
self->pos += info.frame_bytes;
if (self->samplerate != 48000) {
int phase = 0;
int fraction = ((48000.0 / self->samplerate) - 1.0) * 65536;
if (info.channels == 1)
for (int i = 0; i < samples; i++) {
again1:
self->control.audio_buffer[self->control.audio_w++] =
rendered[i] / 2;
if (self->control.audio_w >= AUDIO_BUF_SIZE)
self->control.audio_w = 0;
phase += fraction;
if (phase > 65536) {
phase -= 65536;
phase -= fraction;
goto again1;
}
}
else if (info.channels == 2) {
int phase = 0;
for (int i = 0; i < samples; i++) {
again2:
self->control.audio_buffer[self->control.audio_w++] =
rendered[i * 2] / 2;
if (self->control.audio_w >= AUDIO_BUF_SIZE)
self->control.audio_w = 0;
self->control.audio_buffer[self->control.audio_w++] =
rendered[i * 2 + 1] / 2;
if (self->control.audio_w >= AUDIO_BUF_SIZE)
self->control.audio_w = 0;
phase += fraction;
if (phase > 65536) {
phase -= 65536;
phase -= fraction;
goto again2;
}
}
}
} else {
if (info.channels == 1)
for (int i = 0; i < samples; i++) {
self->control.audio_buffer[self->control.audio_w++] =
rendered[i];
if (self->control.audio_w >= AUDIO_BUF_SIZE)
self->control.audio_w = 0;
self->control.audio_buffer[self->control.audio_w++] =
rendered[i];
if (self->control.audio_w >= AUDIO_BUF_SIZE)
self->control.audio_w = 0;
}
else if (info.channels == 2) {
for (int i = 0; i < samples; i++) {
self->control.audio_buffer[self->control.audio_w++] =
rendered[i * 2];
if (self->control.audio_w >= AUDIO_BUF_SIZE)
self->control.audio_w = 0;
self->control.audio_buffer[self->control.audio_w++] =
rendered[i * 2 + 1];
if (self->control.audio_w >= AUDIO_BUF_SIZE)
self->control.audio_w = 0;
}
}
}
samples_needed -= (samples);
} while (samples_needed > 0);
}
}
static void mp3_destroy(st3m_media *media) {
mp3_state *self = (void *)media;
if (self->data) free(self->data);
if (self->file) fclose(self->file);
if (self->socket) {
shutdown(self->socket, SHUT_RDWR);
close(self->socket);
}
if (self->path) free(self->path);
if (self->title) free(self->title);
if (self->artist) free(self->artist);
free(self);
}
typedef struct {
char tag[3];
char artist[30];
char title[30];
char year[4];
char pad[128];
} id3tag_t;
st3m_media *st3m_media_load_mp3(const char *path) {
mp3_state *self = (mp3_state *)malloc(sizeof(mp3_state));
id3tag_t id3;
memset(self, 0, sizeof(mp3_state));
self->control.draw = mp3_draw;
self->control.think = mp3_think;
self->control.destroy = mp3_destroy;
self->samplerate = 44100;
self->buffer_size = 32 * 1024;
if (!strncmp(path, "http://", 7)) {
int port = 80;
char *hostname = strdup(path + 7);
char *rest = NULL;
self->buffer_size = 96 * 1024;
rest = strchr(hostname, '/') + 1;
strchr(hostname, '/')[0] = 0;
if (strchr(hostname, ':')) {
port = atoi(strchr(hostname, ':') + 1);
strchr(hostname, ':')[0] = 0;
}
struct hostent *host;
struct sockaddr_in addr;
self->socket = socket(PF_INET, SOCK_STREAM, 0);
if (self->socket < 0) {
free(hostname);
free(self);
return NULL;
}
int flag = 1;
setsockopt(self->socket, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int));
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
host = gethostbyname(hostname);
if (!host) {
free(self);
free(hostname);
return NULL;
}
addr.sin_addr.s_addr = ((long unsigned int **)host->h_addr_list)[0][0];
if (connect(self->socket, (struct sockaddr *)&addr, sizeof(addr)) ==
0) {
char s[1024];
sprintf(s, "GET /%s HTTP/1.1\r\n", rest);
write(self->socket, s, strlen(s));
sprintf(s, "Range: bytes=0-\r\n");
write(self->socket, s, strlen(s));
if (hostname) {
sprintf(s, "Host: %s\r\n", hostname);
write(self->socket, s, strlen(s));
}
sprintf(s, "User-Agent: flow3r\r\n");
write(self->socket, s, strlen(s));
sprintf(s, "\r\n");
write(self->socket, s, strlen(s));
fsync(self->socket);
self->data = malloc(self->buffer_size);
self->size = self->buffer_size;
mp3dec_init(&self->mp3d);
self->control.duration = 1200;
free(hostname);
self->in_buffering = 1;
self->path = strdup(path);
return (st3m_media *)self;
}
free(hostname);
free(self);
return NULL;
}
self->file = fopen(path, "r");
fseek(self->file, 0, SEEK_END);
self->file_size = ftell(self->file);
fseek(self->file, self->file_size - 128, SEEK_SET);
fread(&id3, 128, 1, self->file);
if (id3.tag[0] == 'T' && id3.tag[1] == 'A' && id3.tag[2] == 'G') {
self->title = strndup(id3.title, 30);
while (self->title[strlen(self->title) - 1] == ' ')
self->title[strlen(self->title) - 1] = 0;
self->artist = strndup(id3.artist, 30);
while (self->artist[strlen(self->artist) - 1] == ' ')
self->artist[strlen(self->artist) - 1] = 0;
self->year = atoi(id3.year);
} else {
self->artist = strdup("-");
self->title = strdup(strrchr(path, '/') + 1);
}
self->path = strdup(path);
rewind(self->file);
self->data = malloc(self->buffer_size);
self->size = self->buffer_size;
if (!self->file) {
free(self);
return NULL;
}
mp3dec_init(&self->mp3d);
self->control.duration = 1200.0;
return (st3m_media *)self;
}
#ifndef MINIMP3_H
#define MINIMP3_H
/*
https://github.com/lieff/minimp3
To the extent possible under law, the author(s) have dedicated all copyright
and related and neighboring rights to this software to the public domain
worldwide. This software is distributed without any warranty. See
<http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include <stdint.h>
#define MINIMP3_MAX_SAMPLES_PER_FRAME (1152 * 2)
typedef struct {
int frame_bytes, frame_offset, channels, hz, layer, bitrate_kbps;
} mp3dec_frame_info_t;
typedef struct _mp3dec_scratch_t mp3dec_scratch_t;
typedef struct {
const uint8_t *buf;
int pos, limit;
} bs_t;
typedef struct {
const uint8_t *sfbtab;
uint16_t part_23_length, big_values, scalefac_compress;
uint8_t global_gain, block_type, mixed_block_flag, n_long_sfb, n_short_sfb;
uint8_t table_select[3], region_count[3], subblock_gain[3];
uint8_t preflag, scalefac_scale, count1_table, scfsi;
} L3_gr_info_t;
typedef struct {
float scf[3 * 64];
uint8_t total_bands, stereo_bands, bitalloc[64], scfcod[64];
} L12_scale_info;
#define MAX_BITRESERVOIR_BYTES 511
#define MAX_FREE_FORMAT_FRAME_SIZE 2304 /* more than ISO spec's */
#define MAX_L3_FRAME_PAYLOAD_BYTES \
MAX_FREE_FORMAT_FRAME_SIZE /* MUST be >= 320000/8/32000*1152 = 1440 */
struct _mp3dec_scratch_t {
bs_t bs;
uint8_t maindata[MAX_BITRESERVOIR_BYTES + MAX_L3_FRAME_PAYLOAD_BYTES];
L3_gr_info_t gr_info[4];
float grbuf[2][576], scf[45], syn[18 + 15][2 * 32];
uint8_t ist_pos[2][39];
};
typedef struct {
float mdct_overlap[2][9 * 32], qmf_state[15 * 2 * 32];
int reserv, free_format_bytes;
unsigned char header[4], reserv_buf[511];
mp3dec_scratch_t scratch;
L12_scale_info sci;
} mp3dec_t;
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
void mp3dec_init(mp3dec_t *dec);
#ifndef MINIMP3_FLOAT_OUTPUT
typedef int16_t mp3d_sample_t;
#else /* MINIMP3_FLOAT_OUTPUT */
typedef float mp3d_sample_t;
void mp3dec_f32_to_s16(const float *in, int16_t *out, int num_samples);
#endif /* MINIMP3_FLOAT_OUTPUT */
int mp3dec_decode_frame(mp3dec_t *dec, const uint8_t *mp3, int mp3_bytes,
mp3d_sample_t *pcm, mp3dec_frame_info_t *info);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* MINIMP3_H */
#if defined(MINIMP3_IMPLEMENTATION) && !defined(_MINIMP3_IMPLEMENTATION_GUARD)
#define _MINIMP3_IMPLEMENTATION_GUARD
#include <stdlib.h>
#include <string.h>
#ifndef MAX_FRAME_SYNC_MATCHES
#define MAX_FRAME_SYNC_MATCHES 10
#endif /* MAX_FRAME_SYNC_MATCHES */
#define SHORT_BLOCK_TYPE 2
#define STOP_BLOCK_TYPE 3
#define MODE_MONO 3
#define MODE_JOINT_STEREO 1
#define HDR_SIZE 4
#define HDR_IS_MONO(h) (((h[3]) & 0xC0) == 0xC0)
#define HDR_IS_MS_STEREO(h) (((h[3]) & 0xE0) == 0x60)
#define HDR_IS_FREE_FORMAT(h) (((h[2]) & 0xF0) == 0)
#define HDR_IS_CRC(h) (!((h[1]) & 1))
#define HDR_TEST_PADDING(h) ((h[2]) & 0x2)
#define HDR_TEST_MPEG1(h) ((h[1]) & 0x8)
#define HDR_TEST_NOT_MPEG25(h) ((h[1]) & 0x10)
#define HDR_TEST_I_STEREO(h) ((h[3]) & 0x10)
#define HDR_TEST_MS_STEREO(h) ((h[3]) & 0x20)
#define HDR_GET_STEREO_MODE(h) (((h[3]) >> 6) & 3)
#define HDR_GET_STEREO_MODE_EXT(h) (((h[3]) >> 4) & 3)
#define HDR_GET_LAYER(h) (((h[1]) >> 1) & 3)
#define HDR_GET_BITRATE(h) ((h[2]) >> 4)
#define HDR_GET_SAMPLE_RATE(h) (((h[2]) >> 2) & 3)
#define HDR_GET_MY_SAMPLE_RATE(h) \
(HDR_GET_SAMPLE_RATE(h) + (((h[1] >> 3) & 1) + ((h[1] >> 4) & 1)) * 3)
#define HDR_IS_FRAME_576(h) ((h[1] & 14) == 2)
#define HDR_IS_LAYER_1(h) ((h[1] & 6) == 6)
#define BITS_DEQUANTIZER_OUT -1
#define MAX_SCF (255 + BITS_DEQUANTIZER_OUT * 4 - 210)
#define MAX_SCFI ((MAX_SCF + 3) & ~3)
#define MINIMP3_MIN(a, b) ((a) > (b) ? (b) : (a))
#define MINIMP3_MAX(a, b) ((a) < (b) ? (b) : (a))
#if !defined(MINIMP3_NO_SIMD)
#if !defined(MINIMP3_ONLY_SIMD) && (defined(_M_X64) || defined(__x86_64__) || \
defined(__aarch64__) || defined(_M_ARM64))
/* x64 always have SSE2, arm64 always have neon, no need for generic code */
#define MINIMP3_ONLY_SIMD
#endif /* SIMD checks... */
#if (defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))) || \
((defined(__i386__) || defined(__x86_64__)) && defined(__SSE2__))
#if defined(_MSC_VER)
#include <intrin.h>
#endif /* defined(_MSC_VER) */
#include <immintrin.h>
#define HAVE_SSE 1
#define HAVE_SIMD 1
#define VSTORE _mm_storeu_ps
#define VLD _mm_loadu_ps
#define VSET _mm_set1_ps
#define VADD _mm_add_ps
#define VSUB _mm_sub_ps
#define VMUL _mm_mul_ps
#define VMAC(a, x, y) _mm_add_ps(a, _mm_mul_ps(x, y))
#define VMSB(a, x, y) _mm_sub_ps(a, _mm_mul_ps(x, y))
#define VMUL_S(x, s) _mm_mul_ps(x, _mm_set1_ps(s))
#define VREV(x) _mm_shuffle_ps(x, x, _MM_SHUFFLE(0, 1, 2, 3))
typedef __m128 f4;
#if defined(_MSC_VER) || defined(MINIMP3_ONLY_SIMD)
#define minimp3_cpuid __cpuid
#else /* defined(_MSC_VER) || defined(MINIMP3_ONLY_SIMD) */
static __inline__ __attribute__((always_inline)) void minimp3_cpuid(
int CPUInfo[], const int InfoType) {
#if defined(__PIC__)
__asm__ __volatile__(
#if defined(__x86_64__)
"push %%rbx\n"
"cpuid\n"
"xchgl %%ebx, %1\n"
"pop %%rbx\n"
#else /* defined(__x86_64__) */
"xchgl %%ebx, %1\n"
"cpuid\n"
"xchgl %%ebx, %1\n"
#endif /* defined(__x86_64__) */
: "=a"(CPUInfo[0]), "=r"(CPUInfo[1]), "=c"(CPUInfo[2]), "=d"(CPUInfo[3])
: "a"(InfoType));
#else /* defined(__PIC__) */
__asm__ __volatile__("cpuid"
: "=a"(CPUInfo[0]), "=b"(CPUInfo[1]), "=c"(CPUInfo[2]),
"=d"(CPUInfo[3])
: "a"(InfoType));
#endif /* defined(__PIC__)*/
}
#endif /* defined(_MSC_VER) || defined(MINIMP3_ONLY_SIMD) */
static int have_simd(void) {
#ifdef MINIMP3_ONLY_SIMD
return 1;
#else /* MINIMP3_ONLY_SIMD */
static int g_have_simd;
int CPUInfo[4];
#ifdef MINIMP3_TEST
static int g_counter;
if (g_counter++ > 100) return 0;
#endif /* MINIMP3_TEST */
if (g_have_simd) goto end;
minimp3_cpuid(CPUInfo, 0);
g_have_simd = 1;
if (CPUInfo[0] > 0) {
minimp3_cpuid(CPUInfo, 1);
g_have_simd = (CPUInfo[3] & (1 << 26)) + 1; /* SSE2 */
}
end:
return g_have_simd - 1;
#endif /* MINIMP3_ONLY_SIMD */
}
#elif defined(__ARM_NEON) || defined(__aarch64__) || defined(_M_ARM64)
#include <arm_neon.h>
#define HAVE_SSE 0
#define HAVE_SIMD 1
#define VSTORE vst1q_f32
#define VLD vld1q_f32
#define VSET vmovq_n_f32
#define VADD vaddq_f32
#define VSUB vsubq_f32
#define VMUL vmulq_f32
#define VMAC(a, x, y) vmlaq_f32(a, x, y)
#define VMSB(a, x, y) vmlsq_f32(a, x, y)
#define VMUL_S(x, s) vmulq_f32(x, vmovq_n_f32(s))
#define VREV(x) \
vcombine_f32(vget_high_f32(vrev64q_f32(x)), vget_low_f32(vrev64q_f32(x)))
typedef float32x4_t f4;
static int have_simd() { /* TODO: detect neon for !MINIMP3_ONLY_SIMD */
return 1;
}
#else /* SIMD checks... */
#define HAVE_SSE 0
#define HAVE_SIMD 0
#ifdef MINIMP3_ONLY_SIMD
#error MINIMP3_ONLY_SIMD used, but SSE/NEON not enabled
#endif /* MINIMP3_ONLY_SIMD */
#endif /* SIMD checks... */
#else /* !defined(MINIMP3_NO_SIMD) */
#define HAVE_SIMD 0
#endif /* !defined(MINIMP3_NO_SIMD) */
#if defined(__ARM_ARCH) && (__ARM_ARCH >= 6) && !defined(__aarch64__) && \
!defined(_M_ARM64)
#define HAVE_ARMV6 1
static __inline__ __attribute__((always_inline)) int32_t minimp3_clip_int16_arm(
int32_t a) {
int32_t x = 0;
__asm__("ssat %0, #16, %1" : "=r"(x) : "r"(a));
return x;
}
#else
#define HAVE_ARMV6 0
#endif
typedef struct {
uint8_t tab_offset, code_tab_width, band_count;
} L12_subband_alloc_t;
static void bs_init(bs_t *bs, const uint8_t *data, int bytes) {
bs->buf = data;
bs->pos = 0;
bs->limit = bytes * 8;
}
static uint32_t get_bits(bs_t *bs, int n) {
uint32_t next, cache = 0, s = bs->pos & 7;
int shl = n + s;
const uint8_t *p = bs->buf + (bs->pos >> 3);
if ((bs->pos += n) > bs->limit) return 0;
next = *p++ & (255 >> s);
while ((shl -= 8) > 0) {
cache |= next << shl;
next = *p++;
}
return cache | (next >> -shl);
}
static int hdr_valid(const uint8_t *h) {
return h[0] == 0xff && ((h[1] & 0xF0) == 0xf0 || (h[1] & 0xFE) == 0xe2) &&
(HDR_GET_LAYER(h) != 0) && (HDR_GET_BITRATE(h) != 15) &&
(HDR_GET_SAMPLE_RATE(h) != 3);
}
static int hdr_compare(const uint8_t *h1, const uint8_t *h2) {
return hdr_valid(h2) && ((h1[1] ^ h2[1]) & 0xFE) == 0 &&
((h1[2] ^ h2[2]) & 0x0C) == 0 &&
!(HDR_IS_FREE_FORMAT(h1) ^ HDR_IS_FREE_FORMAT(h2));
}
static unsigned hdr_bitrate_kbps(const uint8_t *h) {
static const uint8_t halfrate[2][3][15] = {
{ { 0, 4, 8, 12, 16, 20, 24, 28, 32, 40, 48, 56, 64, 72, 80 },
{ 0, 4, 8, 12, 16, 20, 24, 28, 32, 40, 48, 56, 64, 72, 80 },
{ 0, 16, 24, 28, 32, 40, 48, 56, 64, 72, 80, 88, 96, 112, 128 } },
{ { 0, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160 },
{ 0, 16, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192 },
{ 0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208,
224 } },
};
return 2 * halfrate[!!HDR_TEST_MPEG1(h)][HDR_GET_LAYER(h) - 1]
[HDR_GET_BITRATE(h)];
}
static unsigned hdr_sample_rate_hz(const uint8_t *h) {
static const unsigned g_hz[3] = { 44100, 48000, 32000 };
return g_hz[HDR_GET_SAMPLE_RATE(h)] >> (int)!HDR_TEST_MPEG1(h) >>
(int)!HDR_TEST_NOT_MPEG25(h);
}
static unsigned hdr_frame_samples(const uint8_t *h) {
return HDR_IS_LAYER_1(h) ? 384 : (1152 >> (int)HDR_IS_FRAME_576(h));
}
static int hdr_frame_bytes(const uint8_t *h, int free_format_size) {
int frame_bytes = hdr_frame_samples(h) * hdr_bitrate_kbps(h) * 125 /
hdr_sample_rate_hz(h);
if (HDR_IS_LAYER_1(h)) {
frame_bytes &= ~3; /* slot align */
}
return frame_bytes ? frame_bytes : free_format_size;
}
static int hdr_padding(const uint8_t *h) {
return HDR_TEST_PADDING(h) ? (HDR_IS_LAYER_1(h) ? 4 : 1) : 0;
}
#ifndef MINIMP3_ONLY_MP3
static const L12_subband_alloc_t *L12_subband_alloc_table(const uint8_t *hdr,
L12_scale_info *sci) {
const L12_subband_alloc_t *alloc;
int mode = HDR_GET_STEREO_MODE(hdr);
int nbands, stereo_bands = (mode == MODE_MONO) ? 0
: (mode == MODE_JOINT_STEREO)
? (HDR_GET_STEREO_MODE_EXT(hdr) << 2) + 4
: 32;
if (HDR_IS_LAYER_1(hdr)) {
static const L12_subband_alloc_t g_alloc_L1[] = { { 76, 4, 32 } };
alloc = g_alloc_L1;
nbands = 32;
} else if (!HDR_TEST_MPEG1(hdr)) {
static const L12_subband_alloc_t g_alloc_L2M2[] = { { 60, 4, 4 },
{ 44, 3, 7 },
{ 44, 2, 19 } };
alloc = g_alloc_L2M2;
nbands = 30;
} else {
static const L12_subband_alloc_t g_alloc_L2M1[] = {
{ 0, 4, 3 }, { 16, 4, 8 }, { 32, 3, 12 }, { 40, 2, 7 }
};
int sample_rate_idx = HDR_GET_SAMPLE_RATE(hdr);
unsigned kbps = hdr_bitrate_kbps(hdr) >> (int)(mode != MODE_MONO);
if (!kbps) /* free-format */
{
kbps = 192;
}
alloc = g_alloc_L2M1;
nbands = 27;
if (kbps < 56) {
static const L12_subband_alloc_t g_alloc_L2M1_lowrate[] = {
{ 44, 4, 2 }, { 44, 3, 10 }
};
alloc = g_alloc_L2M1_lowrate;
nbands = sample_rate_idx == 2 ? 12 : 8;
} else if (kbps >= 96 && sample_rate_idx != 1) {
nbands = 30;
}
}
sci->total_bands = (uint8_t)nbands;
sci->stereo_bands = (uint8_t)MINIMP3_MIN(stereo_bands, nbands);
return alloc;
}
static void L12_read_scalefactors(bs_t *bs, uint8_t *pba, uint8_t *scfcod,
int bands, float *scf) {
static const float g_deq_L12[18 * 3] = {
#define DQ(x) 9.53674316e-07f / x, 7.56931807e-07f / x, 6.00777173e-07f / x
DQ(3), DQ(7), DQ(15), DQ(31), DQ(63), DQ(127),
DQ(255), DQ(511), DQ(1023), DQ(2047), DQ(4095), DQ(8191),
DQ(16383), DQ(32767), DQ(65535), DQ(3), DQ(5), DQ(9)
};
int i, m;
for (i = 0; i < bands; i++) {
float s = 0;
int ba = *pba++;
int mask = ba ? 4 + ((19 >> scfcod[i]) & 3) : 0;
for (m = 4; m; m >>= 1) {
if (mask & m) {
int b = get_bits(bs, 6);
s = g_deq_L12[ba * 3 - 6 + b % 3] * (1 << 21 >> b / 3);
}
*scf++ = s;
}
}
}
static void L12_read_scale_info(const uint8_t *hdr, bs_t *bs,
L12_scale_info *sci) {
static const uint8_t g_bitalloc_code_tab[] = {
0, 17, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
0, 17, 18, 3, 19, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 16,
0, 17, 18, 3, 19, 4, 5, 16, 0, 17, 18, 16, 0, 17, 18, 19,
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 17, 18, 3,
19, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 0, 2, 3, 4,
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
};
const L12_subband_alloc_t *subband_alloc =
L12_subband_alloc_table(hdr, sci);
int i, k = 0, ba_bits = 0;
const uint8_t *ba_code_tab = g_bitalloc_code_tab;
for (i = 0; i < sci->total_bands; i++) {
uint8_t ba;
if (i == k) {
k += subband_alloc->band_count;
ba_bits = subband_alloc->code_tab_width;
ba_code_tab = g_bitalloc_code_tab + subband_alloc->tab_offset;
subband_alloc++;
}
ba = ba_code_tab[get_bits(bs, ba_bits)];
sci->bitalloc[2 * i] = ba;
if (i < sci->stereo_bands) {
ba = ba_code_tab[get_bits(bs, ba_bits)];
}
sci->bitalloc[2 * i + 1] = sci->stereo_bands ? ba : 0;
}
for (i = 0; i < 2 * sci->total_bands; i++) {
sci->scfcod[i] =
sci->bitalloc[i] ? HDR_IS_LAYER_1(hdr) ? 2 : get_bits(bs, 2) : 6;
}
L12_read_scalefactors(bs, sci->bitalloc, sci->scfcod, sci->total_bands * 2,
sci->scf);
for (i = sci->stereo_bands; i < sci->total_bands; i++) {
sci->bitalloc[2 * i + 1] = 0;
}
}
static int L12_dequantize_granule(float *grbuf, bs_t *bs, L12_scale_info *sci,
int group_size) {
int i, j, k, choff = 576;
for (j = 0; j < 4; j++) {
float *dst = grbuf + group_size * j;
for (i = 0; i < 2 * sci->total_bands; i++) {
int ba = sci->bitalloc[i];
if (ba != 0) {
if (ba < 17) {
int half = (1 << (ba - 1)) - 1;
for (k = 0; k < group_size; k++) {
dst[k] = (float)((int)get_bits(bs, ba) - half);
}
} else {
unsigned mod = (2 << (ba - 17)) + 1; /* 3, 5, 9 */
unsigned code =
get_bits(bs, mod + 2 - (mod >> 3)); /* 5, 7, 10 */
for (k = 0; k < group_size; k++, code /= mod) {
dst[k] = (float)((int)(code % mod - mod / 2));
}
}
}
dst += choff;
choff = 18 - choff;
}
}
return group_size * 4;
}
static void L12_apply_scf_384(L12_scale_info *sci, const float *scf,
float *dst) {
int i, k;
memcpy(dst + 576 + sci->stereo_bands * 18, dst + sci->stereo_bands * 18,
(sci->total_bands - sci->stereo_bands) * 18 * sizeof(float));
for (i = 0; i < sci->total_bands; i++, dst += 18, scf += 6) {
for (k = 0; k < 12; k++) {
dst[k + 0] *= scf[0];
dst[k + 576] *= scf[3];
}
}
}
#endif /* MINIMP3_ONLY_MP3 */
static int L3_read_side_info(bs_t *bs, L3_gr_info_t *gr, const uint8_t *hdr) {
static const uint8_t g_scf_long[8][23] = {
{ 6, 6, 6, 6, 6, 6, 8, 10, 12, 14, 16, 20,
24, 28, 32, 38, 46, 52, 60, 68, 58, 54, 0 },
{ 12, 12, 12, 12, 12, 12, 16, 20, 24, 28, 32, 40,
48, 56, 64, 76, 90, 2, 2, 2, 2, 2, 0 },
{ 6, 6, 6, 6, 6, 6, 8, 10, 12, 14, 16, 20,
24, 28, 32, 38, 46, 52, 60, 68, 58, 54, 0 },
{ 6, 6, 6, 6, 6, 6, 8, 10, 12, 14, 16, 18,
22, 26, 32, 38, 46, 54, 62, 70, 76, 36, 0 },
{ 6, 6, 6, 6, 6, 6, 8, 10, 12, 14, 16, 20,
24, 28, 32, 38, 46, 52, 60, 68, 58, 54, 0 },
{ 4, 4, 4, 4, 4, 4, 6, 6, 8, 8, 10, 12,
16, 20, 24, 28, 34, 42, 50, 54, 76, 158, 0 },
{ 4, 4, 4, 4, 4, 4, 6, 6, 6, 8, 10, 12,
16, 18, 22, 28, 34, 40, 46, 54, 54, 192, 0 },
{ 4, 4, 4, 4, 4, 4, 6, 6, 8, 10, 12, 16,
20, 24, 30, 38, 46, 56, 68, 84, 102, 26, 0 }
};
static const uint8_t g_scf_short[8][40] = {
{ 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 8, 8,
8, 10, 10, 10, 12, 12, 12, 14, 14, 14, 18, 18, 18, 24,
24, 24, 30, 30, 30, 40, 40, 40, 18, 18, 18, 0 },
{ 8, 8, 8, 8, 8, 8, 8, 8, 8, 12, 12, 12, 16, 16,
16, 20, 20, 20, 24, 24, 24, 28, 28, 28, 36, 36, 36, 2,
2, 2, 2, 2, 2, 2, 2, 2, 26, 26, 26, 0 },
{ 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 6, 6,
6, 8, 8, 8, 10, 10, 10, 14, 14, 14, 18, 18, 18, 26,
26, 26, 32, 32, 32, 42, 42, 42, 18, 18, 18, 0 },
{ 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 8, 8,
8, 10, 10, 10, 12, 12, 12, 14, 14, 14, 18, 18, 18, 24,
24, 24, 32, 32, 32, 44, 44, 44, 12, 12, 12, 0 },
{ 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6, 6, 8, 8,
8, 10, 10, 10, 12, 12, 12, 14, 14, 14, 18, 18, 18, 24,
24, 24, 30, 30, 30, 40, 40, 40, 18, 18, 18, 0 },
{ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6,
6, 8, 8, 8, 10, 10, 10, 12, 12, 12, 14, 14, 14, 18,
18, 18, 22, 22, 22, 30, 30, 30, 56, 56, 56, 0 },
{ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6,
6, 6, 6, 6, 10, 10, 10, 12, 12, 12, 14, 14, 14, 16,
16, 16, 20, 20, 20, 26, 26, 26, 66, 66, 66, 0 },
{ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 6, 6,
6, 8, 8, 8, 12, 12, 12, 16, 16, 16, 20, 20, 20, 26,
26, 26, 34, 34, 34, 42, 42, 42, 12, 12, 12, 0 }
};
static const uint8_t g_scf_mixed[8][40] = {
{ 6, 6, 6, 6, 6, 6, 6, 6, 6, 8, 8, 8, 10,
10, 10, 12, 12, 12, 14, 14, 14, 18, 18, 18, 24, 24,
24, 30, 30, 30, 40, 40, 40, 18, 18, 18, 0 },
{ 12, 12, 12, 4, 4, 4, 8, 8, 8, 12, 12, 12, 16, 16,
16, 20, 20, 20, 24, 24, 24, 28, 28, 28, 36, 36, 36, 2,
2, 2, 2, 2, 2, 2, 2, 2, 26, 26, 26, 0 },
{ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 8,
8, 8, 10, 10, 10, 14, 14, 14, 18, 18, 18, 26, 26,
26, 32, 32, 32, 42, 42, 42, 18, 18, 18, 0 },
{ 6, 6, 6, 6, 6, 6, 6, 6, 6, 8, 8, 8, 10,
10, 10, 12, 12, 12, 14, 14, 14, 18, 18, 18, 24, 24,
24, 32, 32, 32, 44, 44, 44, 12, 12, 12, 0 },
{ 6, 6, 6, 6, 6, 6, 6, 6, 6, 8, 8, 8, 10,
10, 10, 12, 12, 12, 14, 14, 14, 18, 18, 18, 24, 24,
24, 30, 30, 30, 40, 40, 40, 18, 18, 18, 0 },
{ 4, 4, 4, 4, 4, 4, 6, 6, 4, 4, 4, 6, 6,
6, 8, 8, 8, 10, 10, 10, 12, 12, 12, 14, 14, 14,
18, 18, 18, 22, 22, 22, 30, 30, 30, 56, 56, 56, 0 },
{ 4, 4, 4, 4, 4, 4, 6, 6, 4, 4, 4, 6, 6,
6, 6, 6, 6, 10, 10, 10, 12, 12, 12, 14, 14, 14,
16, 16, 16, 20, 20, 20, 26, 26, 26, 66, 66, 66, 0 },
{ 4, 4, 4, 4, 4, 4, 6, 6, 4, 4, 4, 6, 6,
6, 8, 8, 8, 12, 12, 12, 16, 16, 16, 20, 20, 20,
26, 26, 26, 34, 34, 34, 42, 42, 42, 12, 12, 12, 0 }
};
unsigned tables, scfsi = 0;
int main_data_begin, part_23_sum = 0;
int sr_idx = HDR_GET_MY_SAMPLE_RATE(hdr);
sr_idx -= (sr_idx != 0);
int gr_count = HDR_IS_MONO(hdr) ? 1 : 2;
if (HDR_TEST_MPEG1(hdr)) {
gr_count *= 2;
main_data_begin = get_bits(bs, 9);
scfsi = get_bits(bs, 7 + gr_count);
} else {
main_data_begin = get_bits(bs, 8 + gr_count) >> gr_count;
}
do {
if (HDR_IS_MONO(hdr)) {
scfsi <<= 4;
}
gr->part_23_length = (uint16_t)get_bits(bs, 12);
part_23_sum += gr->part_23_length;
gr->big_values = (uint16_t)get_bits(bs, 9);
if (gr->big_values > 288) {
return -1;
}
gr->global_gain = (uint8_t)get_bits(bs, 8);
gr->scalefac_compress =
(uint16_t)get_bits(bs, HDR_TEST_MPEG1(hdr) ? 4 : 9);
gr->sfbtab = g_scf_long[sr_idx];
gr->n_long_sfb = 22;
gr->n_short_sfb = 0;
if (get_bits(bs, 1)) {
gr->block_type = (uint8_t)get_bits(bs, 2);
if (!gr->block_type) {
return -1;
}
gr->mixed_block_flag = (uint8_t)get_bits(bs, 1);
gr->region_count[0] = 7;
gr->region_count[1] = 255;
if (gr->block_type == SHORT_BLOCK_TYPE) {
scfsi &= 0x0F0F;
if (!gr->mixed_block_flag) {
gr->region_count[0] = 8;
gr->sfbtab = g_scf_short[sr_idx];
gr->n_long_sfb = 0;
gr->n_short_sfb = 39;
} else {
gr->sfbtab = g_scf_mixed[sr_idx];
gr->n_long_sfb = HDR_TEST_MPEG1(hdr) ? 8 : 6;
gr->n_short_sfb = 30;
}
}
tables = get_bits(bs, 10);
tables <<= 5;
gr->subblock_gain[0] = (uint8_t)get_bits(bs, 3);
gr->subblock_gain[1] = (uint8_t)get_bits(bs, 3);
gr->subblock_gain[2] = (uint8_t)get_bits(bs, 3);
} else {
gr->block_type = 0;
gr->mixed_block_flag = 0;
tables = get_bits(bs, 15);
gr->region_count[0] = (uint8_t)get_bits(bs, 4);
gr->region_count[1] = (uint8_t)get_bits(bs, 3);
gr->region_count[2] = 255;
}
gr->table_select[0] = (uint8_t)(tables >> 10);
gr->table_select[1] = (uint8_t)((tables >> 5) & 31);
gr->table_select[2] = (uint8_t)((tables)&31);
gr->preflag = HDR_TEST_MPEG1(hdr) ? get_bits(bs, 1)
: (gr->scalefac_compress >= 500);
gr->scalefac_scale = (uint8_t)get_bits(bs, 1);
gr->count1_table = (uint8_t)get_bits(bs, 1);
gr->scfsi = (uint8_t)((scfsi >> 12) & 15);
scfsi <<= 4;
gr++;
} while (--gr_count);
if (part_23_sum + bs->pos > bs->limit + main_data_begin * 8) {
return -1;
}
return main_data_begin;
}
static void L3_read_scalefactors(uint8_t *scf, uint8_t *ist_pos,
const uint8_t *scf_size,
const uint8_t *scf_count, bs_t *bitbuf,
int scfsi) {
int i, k;
for (i = 0; i < 4 && scf_count[i]; i++, scfsi *= 2) {
int cnt = scf_count[i];
if (scfsi & 8) {
memcpy(scf, ist_pos, cnt);
} else {
int bits = scf_size[i];
if (!bits) {
memset(scf, 0, cnt);
memset(ist_pos, 0, cnt);
} else {
int max_scf = (scfsi < 0) ? (1 << bits) - 1 : -1;
for (k = 0; k < cnt; k++) {
int s = get_bits(bitbuf, bits);
ist_pos[k] = (s == max_scf ? -1 : s);
scf[k] = s;
}
}
}
ist_pos += cnt;
scf += cnt;
}
scf[0] = scf[1] = scf[2] = 0;
}
static float L3_ldexp_q2(float y, int exp_q2) {
static const float g_expfrac[4] = { 9.31322575e-10f, 7.83145814e-10f,
6.58544508e-10f, 5.53767716e-10f };
int e;
do {
e = MINIMP3_MIN(30 * 4, exp_q2);
y *= g_expfrac[e & 3] * (1 << 30 >> (e >> 2));
} while ((exp_q2 -= e) > 0);
return y;
}
static void L3_decode_scalefactors(const uint8_t *hdr, uint8_t *ist_pos,
bs_t *bs, const L3_gr_info_t *gr, float *scf,
int ch) {
static const uint8_t g_scf_partitions[3][28] = {
{ 6, 5, 5, 5, 6, 5, 5, 5, 6, 5, 7, 3, 11, 10,
0, 0, 7, 7, 7, 0, 6, 6, 6, 3, 8, 8, 5, 0 },
{ 8, 9, 6, 12, 6, 9, 9, 9, 6, 9, 12, 6, 15, 18,
0, 0, 6, 15, 12, 0, 6, 12, 9, 6, 6, 18, 9, 0 },
{ 9, 9, 6, 12, 9, 9, 9, 9, 9, 9, 12, 6, 18, 18,
0, 0, 12, 12, 12, 0, 12, 9, 9, 6, 15, 12, 9, 0 }
};
const uint8_t *scf_partition =
g_scf_partitions[!!gr->n_short_sfb + !gr->n_long_sfb];
uint8_t scf_size[4], iscf[40];
int i, scf_shift = gr->scalefac_scale + 1, gain_exp, scfsi = gr->scfsi;
float gain;
if (HDR_TEST_MPEG1(hdr)) {
static const uint8_t g_scfc_decode[16] = {
0, 1, 2, 3, 12, 5, 6, 7, 9, 10, 11, 13, 14, 15, 18, 19
};
int part = g_scfc_decode[gr->scalefac_compress];
scf_size[1] = scf_size[0] = (uint8_t)(part >> 2);
scf_size[3] = scf_size[2] = (uint8_t)(part & 3);
} else {
static const uint8_t g_mod[6 * 4] = { 5, 5, 4, 4, 5, 5, 4, 1,
4, 3, 1, 1, 5, 6, 6, 1,
4, 4, 4, 1, 4, 3, 1, 1 };
int k, modprod, sfc, ist = HDR_TEST_I_STEREO(hdr) && ch;
sfc = gr->scalefac_compress >> ist;
for (k = ist * 3 * 4; sfc >= 0; sfc -= modprod, k += 4) {
for (modprod = 1, i = 3; i >= 0; i--) {
scf_size[i] = (uint8_t)(sfc / modprod % g_mod[k + i]);
modprod *= g_mod[k + i];
}
}
scf_partition += k;
scfsi = -16;
}
L3_read_scalefactors(iscf, ist_pos, scf_size, scf_partition, bs, scfsi);
if (gr->n_short_sfb) {
int sh = 3 - scf_shift;
for (i = 0; i < gr->n_short_sfb; i += 3) {
iscf[gr->n_long_sfb + i + 0] += gr->subblock_gain[0] << sh;
iscf[gr->n_long_sfb + i + 1] += gr->subblock_gain[1] << sh;
iscf[gr->n_long_sfb + i + 2] += gr->subblock_gain[2] << sh;
}
} else if (gr->preflag) {
static const uint8_t g_preamp[10] = { 1, 1, 1, 1, 2, 2, 3, 3, 3, 2 };
for (i = 0; i < 10; i++) {
iscf[11 + i] += g_preamp[i];
}
}
gain_exp = gr->global_gain + BITS_DEQUANTIZER_OUT * 4 - 210 -
(HDR_IS_MS_STEREO(hdr) ? 2 : 0);
gain = L3_ldexp_q2(1 << (MAX_SCFI / 4), MAX_SCFI - gain_exp);
for (i = 0; i < (int)(gr->n_long_sfb + gr->n_short_sfb); i++) {
scf[i] = L3_ldexp_q2(gain, iscf[i] << scf_shift);
}
}
static const float g_pow43[129 + 16] = {
0, -1, -2.519842f, -4.326749f, -6.349604f,
-8.549880f, -10.902724f, -13.390518f, -16.000000f, -18.720754f,
-21.544347f, -24.463781f, -27.473142f, -30.567351f, -33.741992f,
-36.993181f, 0, 1, 2.519842f, 4.326749f,
6.349604f, 8.549880f, 10.902724f, 13.390518f, 16.000000f,
18.720754f, 21.544347f, 24.463781f, 27.473142f, 30.567351f,
33.741992f, 36.993181f, 40.317474f, 43.711787f, 47.173345f,
50.699631f, 54.288352f, 57.937408f, 61.644865f, 65.408941f,
69.227979f, 73.100443f, 77.024898f, 81.000000f, 85.024491f,
89.097188f, 93.216975f, 97.382800f, 101.593667f, 105.848633f,
110.146801f, 114.487321f, 118.869381f, 123.292209f, 127.755065f,
132.257246f, 136.798076f, 141.376907f, 145.993119f, 150.646117f,
155.335327f, 160.060199f, 164.820202f, 169.614826f, 174.443577f,
179.305980f, 184.201575f, 189.129918f, 194.090580f, 199.083145f,
204.107210f, 209.162385f, 214.248292f, 219.364564f, 224.510845f,
229.686789f, 234.892058f, 240.126328f, 245.389280f, 250.680604f,
256.000000f, 261.347174f, 266.721841f, 272.123723f, 277.552547f,
283.008049f, 288.489971f, 293.998060f, 299.532071f, 305.091761f,
310.676898f, 316.287249f, 321.922592f, 327.582707f, 333.267377f,
338.976394f, 344.709550f, 350.466646f, 356.247482f, 362.051866f,
367.879608f, 373.730522f, 379.604427f, 385.501143f, 391.420496f,
397.362314f, 403.326427f, 409.312672f, 415.320884f, 421.350905f,
427.402579f, 433.475750f, 439.570269f, 445.685987f, 451.822757f,
457.980436f, 464.158883f, 470.357960f, 476.577530f, 482.817459f,
489.077615f, 495.357868f, 501.658090f, 507.978156f, 514.317941f,
520.677324f, 527.056184f, 533.454404f, 539.871867f, 546.308458f,
552.764065f, 559.238575f, 565.731879f, 572.243870f, 578.774440f,
585.323483f, 591.890898f, 598.476581f, 605.080431f, 611.702349f,
618.342238f, 625.000000f, 631.675540f, 638.368763f, 645.079578f
};
static float L3_pow_43(int x) {
float frac;
int sign, mult = 256;
if (x < 129) {
return g_pow43[16 + x];
}
if (x < 1024) {
mult = 16;
x <<= 3;
}
sign = 2 * x & 64;
frac = (float)((x & 63) - sign) / ((x & ~63) + sign);
return g_pow43[16 + ((x + sign) >> 6)] *
(1.f + frac * ((4.f / 3) + frac * (2.f / 9))) * mult;
}
static void L3_huffman(float *dst, bs_t *bs, const L3_gr_info_t *gr_info,
const float *scf, int layer3gr_limit) {
static const int16_t tabs[] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 785, 785, 785, 785, 784, 784, 784, 784,
513, 513, 513, 513, 513, 513, 513, 513, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, -255, 1313, 1298, 1282, 785, 785,
785, 785, 784, 784, 784, 784, 769, 769, 769, 769,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 290, 288, -255, 1313,
1298, 1282, 769, 769, 769, 769, 529, 529, 529, 529,
529, 529, 529, 529, 528, 528, 528, 528, 528, 528,
528, 528, 512, 512, 512, 512, 512, 512, 512, 512,
290, 288, -253, -318, -351, -367, 785, 785, 785, 785,
784, 784, 784, 784, 769, 769, 769, 769, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 819, 818, 547, 547, 275, 275,
275, 275, 561, 560, 515, 546, 289, 274, 288, 258,
-254, -287, 1329, 1299, 1314, 1312, 1057, 1057, 1042, 1042,
1026, 1026, 784, 784, 784, 784, 529, 529, 529, 529,
529, 529, 529, 529, 769, 769, 769, 769, 768, 768,
768, 768, 563, 560, 306, 306, 291, 259, -252, -413,
-477, -542, 1298, -575, 1041, 1041, 784, 784, 784, 784,
769, 769, 769, 769, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
-383, -399, 1107, 1092, 1106, 1061, 849, 849, 789, 789,
1104, 1091, 773, 773, 1076, 1075, 341, 340, 325, 309,
834, 804, 577, 577, 532, 532, 516, 516, 832, 818,
803, 816, 561, 561, 531, 531, 515, 546, 289, 289,
288, 258, -252, -429, -493, -559, 1057, 1057, 1042, 1042,
529, 529, 529, 529, 529, 529, 529, 529, 784, 784,
784, 784, 769, 769, 769, 769, 512, 512, 512, 512,
512, 512, 512, 512, -382, 1077, -415, 1106, 1061, 1104,
849, 849, 789, 789, 1091, 1076, 1029, 1075, 834, 834,
597, 581, 340, 340, 339, 324, 804, 833, 532, 532,
832, 772, 818, 803, 817, 787, 816, 771, 290, 290,
290, 290, 288, 258, -253, -349, -414, -447, -463, 1329,
1299, -479, 1314, 1312, 1057, 1057, 1042, 1042, 1026, 1026,
785, 785, 785, 785, 784, 784, 784, 784, 769, 769,
769, 769, 768, 768, 768, 768, -319, 851, 821, -335,
836, 850, 805, 849, 341, 340, 325, 336, 533, 533,
579, 579, 564, 564, 773, 832, 578, 548, 563, 516,
321, 276, 306, 291, 304, 259, -251, -572, -733, -830,
-863, -879, 1041, 1041, 784, 784, 784, 784, 769, 769,
769, 769, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, -511, -527,
-543, 1396, 1351, 1381, 1366, 1395, 1335, 1380, -559, 1334,
1138, 1138, 1063, 1063, 1350, 1392, 1031, 1031, 1062, 1062,
1364, 1363, 1120, 1120, 1333, 1348, 881, 881, 881, 881,
375, 374, 359, 373, 343, 358, 341, 325, 791, 791,
1123, 1122, -703, 1105, 1045, -719, 865, 865, 790, 790,
774, 774, 1104, 1029, 338, 293, 323, 308, -799, -815,
833, 788, 772, 818, 803, 816, 322, 292, 307, 320,
561, 531, 515, 546, 289, 274, 288, 258, -251, -525,
-605, -685, -765, -831, -846, 1298, 1057, 1057, 1312, 1282,
785, 785, 785, 785, 784, 784, 784, 784, 769, 769,
769, 769, 512, 512, 512, 512, 512, 512, 512, 512,
1399, 1398, 1383, 1367, 1382, 1396, 1351, -511, 1381, 1366,
1139, 1139, 1079, 1079, 1124, 1124, 1364, 1349, 1363, 1333,
882, 882, 882, 882, 807, 807, 807, 807, 1094, 1094,
1136, 1136, 373, 341, 535, 535, 881, 775, 867, 822,
774, -591, 324, 338, -671, 849, 550, 550, 866, 864,
609, 609, 293, 336, 534, 534, 789, 835, 773, -751,
834, 804, 308, 307, 833, 788, 832, 772, 562, 562,
547, 547, 305, 275, 560, 515, 290, 290, -252, -397,
-477, -557, -622, -653, -719, -735, -750, 1329, 1299, 1314,
1057, 1057, 1042, 1042, 1312, 1282, 1024, 1024, 785, 785,
785, 785, 784, 784, 784, 784, 769, 769, 769, 769,
-383, 1127, 1141, 1111, 1126, 1140, 1095, 1110, 869, 869,
883, 883, 1079, 1109, 882, 882, 375, 374, 807, 868,
838, 881, 791, -463, 867, 822, 368, 263, 852, 837,
836, -543, 610, 610, 550, 550, 352, 336, 534, 534,
865, 774, 851, 821, 850, 805, 593, 533, 579, 564,
773, 832, 578, 578, 548, 548, 577, 577, 307, 276,
306, 291, 516, 560, 259, 259, -250, -2107, -2507, -2764,
-2909, -2974, -3007, -3023, 1041, 1041, 1040, 1040, 769, 769,
769, 769, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, -767, -1052,
-1213, -1277, -1358, -1405, -1469, -1535, -1550, -1582, -1614, -1647,
-1662, -1694, -1726, -1759, -1774, -1807, -1822, -1854, -1886, 1565,
-1919, -1935, -1951, -1967, 1731, 1730, 1580, 1717, -1983, 1729,
1564, -1999, 1548, -2015, -2031, 1715, 1595, -2047, 1714, -2063,
1610, -2079, 1609, -2095, 1323, 1323, 1457, 1457, 1307, 1307,
1712, 1547, 1641, 1700, 1699, 1594, 1685, 1625, 1442, 1442,
1322, 1322, -780, -973, -910, 1279, 1278, 1277, 1262, 1276,
1261, 1275, 1215, 1260, 1229, -959, 974, 974, 989, 989,
-943, 735, 478, 478, 495, 463, 506, 414, -1039, 1003,
958, 1017, 927, 942, 987, 957, 431, 476, 1272, 1167,
1228, -1183, 1256, -1199, 895, 895, 941, 941, 1242, 1227,
1212, 1135, 1014, 1014, 490, 489, 503, 487, 910, 1013,
985, 925, 863, 894, 970, 955, 1012, 847, -1343, 831,
755, 755, 984, 909, 428, 366, 754, 559, -1391, 752,
486, 457, 924, 997, 698, 698, 983, 893, 740, 740,
908, 877, 739, 739, 667, 667, 953, 938, 497, 287,
271, 271, 683, 606, 590, 712, 726, 574, 302, 302,
738, 736, 481, 286, 526, 725, 605, 711, 636, 724,
696, 651, 589, 681, 666, 710, 364, 467, 573, 695,
466, 466, 301, 465, 379, 379, 709, 604, 665, 679,
316, 316, 634, 633, 436, 436, 464, 269, 424, 394,
452, 332, 438, 363, 347, 408, 393, 448, 331, 422,
362, 407, 392, 421, 346, 406, 391, 376, 375, 359,
1441, 1306, -2367, 1290, -2383, 1337, -2399, -2415, 1426, 1321,
-2431, 1411, 1336, -2447, -2463, -2479, 1169, 1169, 1049, 1049,
1424, 1289, 1412, 1352, 1319, -2495, 1154, 1154, 1064, 1064,
1153, 1153, 416, 390, 360, 404, 403, 389, 344, 374,
373, 343, 358, 372, 327, 357, 342, 311, 356, 326,
1395, 1394, 1137, 1137, 1047, 1047, 1365, 1392, 1287, 1379,
1334, 1364, 1349, 1378, 1318, 1363, 792, 792, 792, 792,
1152, 1152, 1032, 1032, 1121, 1121, 1046, 1046, 1120, 1120,
1030, 1030, -2895, 1106, 1061, 1104, 849, 849, 789, 789,
1091, 1076, 1029, 1090, 1060, 1075, 833, 833, 309, 324,
532, 532, 832, 772, 818, 803, 561, 561, 531, 560,
515, 546, 289, 274, 288, 258, -250, -1179, -1579, -1836,
-1996, -2124, -2253, -2333, -2413, -2477, -2542, -2574, -2607, -2622,
-2655, 1314, 1313, 1298, 1312, 1282, 785, 785, 785, 785,
1040, 1040, 1025, 1025, 768, 768, 768, 768, -766, -798,
-830, -862, -895, -911, -927, -943, -959, -975, -991, -1007,
-1023, -1039, -1055, -1070, 1724, 1647, -1103, -1119, 1631, 1767,
1662, 1738, 1708, 1723, -1135, 1780, 1615, 1779, 1599, 1677,
1646, 1778, 1583, -1151, 1777, 1567, 1737, 1692, 1765, 1722,
1707, 1630, 1751, 1661, 1764, 1614, 1736, 1676, 1763, 1750,
1645, 1598, 1721, 1691, 1762, 1706, 1582, 1761, 1566, -1167,
1749, 1629, 767, 766, 751, 765, 494, 494, 735, 764,
719, 749, 734, 763, 447, 447, 748, 718, 477, 506,
431, 491, 446, 476, 461, 505, 415, 430, 475, 445,
504, 399, 460, 489, 414, 503, 383, 474, 429, 459,
502, 502, 746, 752, 488, 398, 501, 473, 413, 472,
486, 271, 480, 270, -1439, -1455, 1357, -1471, -1487, -1503,
1341, 1325, -1519, 1489, 1463, 1403, 1309, -1535, 1372, 1448,
1418, 1476, 1356, 1462, 1387, -1551, 1475, 1340, 1447, 1402,
1386, -1567, 1068, 1068, 1474, 1461, 455, 380, 468, 440,
395, 425, 410, 454, 364, 467, 466, 464, 453, 269,
409, 448, 268, 432, 1371, 1473, 1432, 1417, 1308, 1460,
1355, 1446, 1459, 1431, 1083, 1083, 1401, 1416, 1458, 1445,
1067, 1067, 1370, 1457, 1051, 1051, 1291, 1430, 1385, 1444,
1354, 1415, 1400, 1443, 1082, 1082, 1173, 1113, 1186, 1066,
1185, 1050, -1967, 1158, 1128, 1172, 1097, 1171, 1081, -1983,
1157, 1112, 416, 266, 375, 400, 1170, 1142, 1127, 1065,
793, 793, 1169, 1033, 1156, 1096, 1141, 1111, 1155, 1080,
1126, 1140, 898, 898, 808, 808, 897, 897, 792, 792,
1095, 1152, 1032, 1125, 1110, 1139, 1079, 1124, 882, 807,
838, 881, 853, 791, -2319, 867, 368, 263, 822, 852,
837, 866, 806, 865, -2399, 851, 352, 262, 534, 534,
821, 836, 594, 594, 549, 549, 593, 593, 533, 533,
848, 773, 579, 579, 564, 578, 548, 563, 276, 276,
577, 576, 306, 291, 516, 560, 305, 305, 275, 259,
-251, -892, -2058, -2620, -2828, -2957, -3023, -3039, 1041, 1041,
1040, 1040, 769, 769, 769, 769, 256, 256, 256, 256,
256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
256, 256, -511, -527, -543, -559, 1530, -575, -591, 1528,
1527, 1407, 1526, 1391, 1023, 1023, 1023, 1023, 1525, 1375,
1268, 1268, 1103, 1103, 1087, 1087, 1039, 1039, 1523, -604,
815, 815, 815, 815, 510, 495, 509, 479, 508, 463,
507, 447, 431, 505, 415, 399, -734, -782, 1262, -815,
1259, 1244, -831, 1258, 1228, -847, -863, 1196, -879, 1253,
987, 987, 748, -767, 493, 493, 462, 477, 414, 414,
686, 669, 478, 446, 461, 445, 474, 429, 487, 458,
412, 471, 1266, 1264, 1009, 1009, 799, 799, -1019, -1276,
-1452, -1581, -1677, -1757, -1821, -1886, -1933, -1997, 1257, 1257,
1483, 1468, 1512, 1422, 1497, 1406, 1467, 1496, 1421, 1510,
1134, 1134, 1225, 1225, 1466, 1451, 1374, 1405, 1252, 1252,
1358, 1480, 1164, 1164, 1251, 1251, 1238, 1238, 1389, 1465,
-1407, 1054, 1101, -1423, 1207, -1439, 830, 830, 1248, 1038,
1237, 1117, 1223, 1148, 1236, 1208, 411, 426, 395, 410,
379, 269, 1193, 1222, 1132, 1235, 1221, 1116, 976, 976,
1192, 1162, 1177, 1220, 1131, 1191, 963, 963, -1647, 961,
780, -1663, 558, 558, 994, 993, 437, 408, 393, 407,
829, 978, 813, 797, 947, -1743, 721, 721, 377, 392,
844, 950, 828, 890, 706, 706, 812, 859, 796, 960,
948, 843, 934, 874, 571, 571, -1919, 690, 555, 689,
421, 346, 539, 539, 944, 779, 918, 873, 932, 842,
903, 888, 570, 570, 931, 917, 674, 674, -2575, 1562,
-2591, 1609, -2607, 1654, 1322, 1322, 1441, 1441, 1696, 1546,
1683, 1593, 1669, 1624, 1426, 1426, 1321, 1321, 1639, 1680,
1425, 1425, 1305, 1305, 1545, 1668, 1608, 1623, 1667, 1592,
1638, 1666, 1320, 1320, 1652, 1607, 1409, 1409, 1304, 1304,
1288, 1288, 1664, 1637, 1395, 1395, 1335, 1335, 1622, 1636,
1394, 1394, 1319, 1319, 1606, 1621, 1392, 1392, 1137, 1137,
1137, 1137, 345, 390, 360, 375, 404, 373, 1047, -2751,
-2767, -2783, 1062, 1121, 1046, -2799, 1077, -2815, 1106, 1061,
789, 789, 1105, 1104, 263, 355, 310, 340, 325, 354,
352, 262, 339, 324, 1091, 1076, 1029, 1090, 1060, 1075,
833, 833, 788, 788, 1088, 1028, 818, 818, 803, 803,
561, 561, 531, 531, 816, 771, 546, 546, 289, 274,
288, 258, -253, -317, -381, -446, -478, -509, 1279, 1279,
-811, -1179, -1451, -1756, -1900, -2028, -2189, -2253, -2333, -2414,
-2445, -2511, -2526, 1313, 1298, -2559, 1041, 1041, 1040, 1040,
1025, 1025, 1024, 1024, 1022, 1007, 1021, 991, 1020, 975,
1019, 959, 687, 687, 1018, 1017, 671, 671, 655, 655,
1016, 1015, 639, 639, 758, 758, 623, 623, 757, 607,
756, 591, 755, 575, 754, 559, 543, 543, 1009, 783,
-575, -621, -685, -749, 496, -590, 750, 749, 734, 748,
974, 989, 1003, 958, 988, 973, 1002, 942, 987, 957,
972, 1001, 926, 986, 941, 971, 956, 1000, 910, 985,
925, 999, 894, 970, -1071, -1087, -1102, 1390, -1135, 1436,
1509, 1451, 1374, -1151, 1405, 1358, 1480, 1420, -1167, 1507,
1494, 1389, 1342, 1465, 1435, 1450, 1326, 1505, 1310, 1493,
1373, 1479, 1404, 1492, 1464, 1419, 428, 443, 472, 397,
736, 526, 464, 464, 486, 457, 442, 471, 484, 482,
1357, 1449, 1434, 1478, 1388, 1491, 1341, 1490, 1325, 1489,
1463, 1403, 1309, 1477, 1372, 1448, 1418, 1433, 1476, 1356,
1462, 1387, -1439, 1475, 1340, 1447, 1402, 1474, 1324, 1461,
1371, 1473, 269, 448, 1432, 1417, 1308, 1460, -1711, 1459,
-1727, 1441, 1099, 1099, 1446, 1386, 1431, 1401, -1743, 1289,
1083, 1083, 1160, 1160, 1458, 1445, 1067, 1067, 1370, 1457,
1307, 1430, 1129, 1129, 1098, 1098, 268, 432, 267, 416,
266, 400, -1887, 1144, 1187, 1082, 1173, 1113, 1186, 1066,
1050, 1158, 1128, 1143, 1172, 1097, 1171, 1081, 420, 391,
1157, 1112, 1170, 1142, 1127, 1065, 1169, 1049, 1156, 1096,
1141, 1111, 1155, 1080, 1126, 1154, 1064, 1153, 1140, 1095,
1048, -2159, 1125, 1110, 1137, -2175, 823, 823, 1139, 1138,
807, 807, 384, 264, 368, 263, 868, 838, 853, 791,
867, 822, 852, 837, 866, 806, 865, 790, -2319, 851,
821, 836, 352, 262, 850, 805, 849, -2399, 533, 533,
835, 820, 336, 261, 578, 548, 563, 577, 532, 532,
832, 772, 562, 562, 547, 547, 305, 275, 560, 515,
290, 290, 288, 258
};
static const uint8_t tab32[] = { 130, 162, 193, 209, 44, 28, 76,
140, 9, 9, 9, 9, 9, 9,
9, 9, 190, 254, 222, 238, 126,
94, 157, 157, 109, 61, 173, 205 };
static const uint8_t tab33[] = { 252, 236, 220, 204, 188, 172, 156, 140,
124, 108, 92, 76, 60, 44, 28, 12 };
static const int16_t tabindex[2 * 16] = {
0, 32, 64, 98, 0, 132, 180, 218, 292, 364, 426,
538, 648, 746, 0, 1126, 1460, 1460, 1460, 1460, 1460, 1460,
1460, 1460, 1842, 1842, 1842, 1842, 1842, 1842, 1842, 1842
};
static const uint8_t g_linbits[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 8,
10, 13, 4, 5, 6, 7, 8, 9, 11, 13 };
#define PEEK_BITS(n) (bs_cache >> (32 - n))
#define FLUSH_BITS(n) \
{ \
bs_cache <<= (n); \
bs_sh += (n); \
}
#define CHECK_BITS \
while (bs_sh >= 0) { \
bs_cache |= (uint32_t)*bs_next_ptr++ << bs_sh; \
bs_sh -= 8; \
}
#define BSPOS ((bs_next_ptr - bs->buf) * 8 - 24 + bs_sh)
float one = 0.0f;
int ireg = 0, big_val_cnt = gr_info->big_values;
const uint8_t *sfb = gr_info->sfbtab;
const uint8_t *bs_next_ptr = bs->buf + bs->pos / 8;
uint32_t bs_cache =
(((bs_next_ptr[0] * 256u + bs_next_ptr[1]) * 256u + bs_next_ptr[2]) *
256u +
bs_next_ptr[3])
<< (bs->pos & 7);
int pairs_to_decode, np, bs_sh = (bs->pos & 7) - 8;
bs_next_ptr += 4;
while (big_val_cnt > 0) {
int tab_num = gr_info->table_select[ireg];
int sfb_cnt = gr_info->region_count[ireg++];
const int16_t *codebook = tabs + tabindex[tab_num];
int linbits = g_linbits[tab_num];
if (linbits) {
do {
np = *sfb++ / 2;
pairs_to_decode = MINIMP3_MIN(big_val_cnt, np);
one = *scf++;
do {
int j, w = 5;
int leaf = codebook[PEEK_BITS(w)];
while (leaf < 0) {
FLUSH_BITS(w);
w = leaf & 7;
leaf = codebook[PEEK_BITS(w) - (leaf >> 3)];
}
FLUSH_BITS(leaf >> 8);
for (j = 0; j < 2; j++, dst++, leaf >>= 4) {
int lsb = leaf & 0x0F;
if (lsb == 15) {
lsb += PEEK_BITS(linbits);
FLUSH_BITS(linbits);
CHECK_BITS;
*dst = one * L3_pow_43(lsb) *
((int32_t)bs_cache < 0 ? -1 : 1);
} else {
*dst =
g_pow43[16 + lsb - 16 * (bs_cache >> 31)] * one;
}
FLUSH_BITS(lsb ? 1 : 0);
}
CHECK_BITS;
} while (--pairs_to_decode);
} while ((big_val_cnt -= np) > 0 && --sfb_cnt >= 0);
} else {
do {
np = *sfb++ / 2;
pairs_to_decode = MINIMP3_MIN(big_val_cnt, np);
one = *scf++;
do {
int j, w = 5;
int leaf = codebook[PEEK_BITS(w)];
while (leaf < 0) {
FLUSH_BITS(w);
w = leaf & 7;
leaf = codebook[PEEK_BITS(w) - (leaf >> 3)];
}
FLUSH_BITS(leaf >> 8);
for (j = 0; j < 2; j++, dst++, leaf >>= 4) {
int lsb = leaf & 0x0F;
*dst = g_pow43[16 + lsb - 16 * (bs_cache >> 31)] * one;
FLUSH_BITS(lsb ? 1 : 0);
}
CHECK_BITS;
} while (--pairs_to_decode);
} while ((big_val_cnt -= np) > 0 && --sfb_cnt >= 0);
}
}
for (np = 1 - big_val_cnt;; dst += 4) {
const uint8_t *codebook_count1 =
(gr_info->count1_table) ? tab33 : tab32;
int leaf = codebook_count1[PEEK_BITS(4)];
if (!(leaf & 8)) {
leaf = codebook_count1[(leaf >> 3) +
(bs_cache << 4 >> (32 - (leaf & 3)))];
}
FLUSH_BITS(leaf & 7);
if (BSPOS > layer3gr_limit) {
break;
}
#define RELOAD_SCALEFACTOR \
if (!--np) { \
np = *sfb++ / 2; \
if (!np) break; \
one = *scf++; \
}
#define DEQ_COUNT1(s) \
if (leaf & (128 >> s)) { \
dst[s] = ((int32_t)bs_cache < 0) ? -one : one; \
FLUSH_BITS(1) \
}
RELOAD_SCALEFACTOR;
DEQ_COUNT1(0);
DEQ_COUNT1(1);
RELOAD_SCALEFACTOR;
DEQ_COUNT1(2);
DEQ_COUNT1(3);
CHECK_BITS;
}
bs->pos = layer3gr_limit;
}
static void L3_midside_stereo(float *left, int n) {
int i = 0;
float *right = left + 576;
#if HAVE_SIMD
if (have_simd()) {
for (; i < n - 3; i += 4) {
f4 vl = VLD(left + i);
f4 vr = VLD(right + i);
VSTORE(left + i, VADD(vl, vr));
VSTORE(right + i, VSUB(vl, vr));
}
#ifdef __GNUC__
/* Workaround for spurious -Waggressive-loop-optimizations warning from
* gcc. For more info see: https://github.com/lieff/minimp3/issues/88
*/
if (__builtin_constant_p(n % 4 == 0) && n % 4 == 0) return;
#endif
}
#endif /* HAVE_SIMD */
for (; i < n; i++) {
float a = left[i];
float b = right[i];
left[i] = a + b;
right[i] = a - b;
}
}
static void L3_intensity_stereo_band(float *left, int n, float kl, float kr) {
int i;
for (i = 0; i < n; i++) {
left[i + 576] = left[i] * kr;
left[i] = left[i] * kl;
}
}
static void L3_stereo_top_band(const float *right, const uint8_t *sfb,
int nbands, int max_band[3]) {
int i, k;
max_band[0] = max_band[1] = max_band[2] = -1;
for (i = 0; i < nbands; i++) {
for (k = 0; k < sfb[i]; k += 2) {
if (right[k] != 0 || right[k + 1] != 0) {
max_band[i % 3] = i;
break;
}
}
right += sfb[i];
}
}
static void L3_stereo_process(float *left, const uint8_t *ist_pos,
const uint8_t *sfb, const uint8_t *hdr,
int max_band[3], int mpeg2_sh) {
static const float g_pan[7 * 2] = {
0, 1, 0.21132487f, 0.78867513f, 0.36602540f, 0.63397460f,
0.5f, 0.5f, 0.63397460f, 0.36602540f, 0.78867513f, 0.21132487f,
1, 0
};
unsigned i, max_pos = HDR_TEST_MPEG1(hdr) ? 7 : 64;
for (i = 0; sfb[i]; i++) {
unsigned ipos = ist_pos[i];
if ((int)i > max_band[i % 3] && ipos < max_pos) {
float kl, kr, s = HDR_TEST_MS_STEREO(hdr) ? 1.41421356f : 1;
if (HDR_TEST_MPEG1(hdr)) {
kl = g_pan[2 * ipos];
kr = g_pan[2 * ipos + 1];
} else {
kl = 1;
kr = L3_ldexp_q2(1, (ipos + 1) >> 1 << mpeg2_sh);
if (ipos & 1) {
kl = kr;
kr = 1;
}
}
L3_intensity_stereo_band(left, sfb[i], kl * s, kr * s);
} else if (HDR_TEST_MS_STEREO(hdr)) {
L3_midside_stereo(left, sfb[i]);
}
left += sfb[i];
}
}
static void L3_intensity_stereo(float *left, uint8_t *ist_pos,
const L3_gr_info_t *gr, const uint8_t *hdr) {
int max_band[3], n_sfb = gr->n_long_sfb + gr->n_short_sfb;
int i, max_blocks = gr->n_short_sfb ? 3 : 1;
L3_stereo_top_band(left + 576, gr->sfbtab, n_sfb, max_band);
if (gr->n_long_sfb) {
max_band[0] = max_band[1] = max_band[2] =
MINIMP3_MAX(MINIMP3_MAX(max_band[0], max_band[1]), max_band[2]);
}
for (i = 0; i < max_blocks; i++) {
int default_pos = HDR_TEST_MPEG1(hdr) ? 3 : 0;
int itop = n_sfb - max_blocks + i;
int prev = itop - max_blocks;
ist_pos[itop] = max_band[i] >= prev ? default_pos : ist_pos[prev];
}
L3_stereo_process(left, ist_pos, gr->sfbtab, hdr, max_band,
gr[1].scalefac_compress & 1);
}
static void L3_reorder(float *grbuf, float *scratch, const uint8_t *sfb) {
int i, len;
float *src = grbuf, *dst = scratch;
for (; 0 != (len = *sfb); sfb += 3, src += 2 * len) {
for (i = 0; i < len; i++, src++) {
*dst++ = src[0 * len];
*dst++ = src[1 * len];
*dst++ = src[2 * len];
}
}
memcpy(grbuf, scratch, (dst - scratch) * sizeof(float));
}
static void L3_antialias(float *grbuf, int nbands) {
static const float g_aa[2][8] = {
{ 0.85749293f, 0.88174200f, 0.94962865f, 0.98331459f, 0.99551782f,
0.99916056f, 0.99989920f, 0.99999316f },
{ 0.51449576f, 0.47173197f, 0.31337745f, 0.18191320f, 0.09457419f,
0.04096558f, 0.01419856f, 0.00369997f }
};
for (; nbands > 0; nbands--, grbuf += 18) {
int i = 0;
#if HAVE_SIMD
if (have_simd())
for (; i < 8; i += 4) {
f4 vu = VLD(grbuf + 18 + i);
f4 vd = VLD(grbuf + 14 - i);
f4 vc0 = VLD(g_aa[0] + i);
f4 vc1 = VLD(g_aa[1] + i);
vd = VREV(vd);
VSTORE(grbuf + 18 + i, VSUB(VMUL(vu, vc0), VMUL(vd, vc1)));
vd = VADD(VMUL(vu, vc1), VMUL(vd, vc0));
VSTORE(grbuf + 14 - i, VREV(vd));
}
#endif /* HAVE_SIMD */
#ifndef MINIMP3_ONLY_SIMD
for (; i < 8; i++) {
float u = grbuf[18 + i];
float d = grbuf[17 - i];
grbuf[18 + i] = u * g_aa[0][i] - d * g_aa[1][i];
grbuf[17 - i] = u * g_aa[1][i] + d * g_aa[0][i];
}
#endif /* MINIMP3_ONLY_SIMD */
}
}
static void L3_dct3_9(float *y) {
float s0, s1, s2, s3, s4, s5, s6, s7, s8, t0, t2, t4;
s0 = y[0];
s2 = y[2];
s4 = y[4];
s6 = y[6];
s8 = y[8];
t0 = s0 + s6 * 0.5f;
s0 -= s6;
t4 = (s4 + s2) * 0.93969262f;
t2 = (s8 + s2) * 0.76604444f;
s6 = (s4 - s8) * 0.17364818f;
s4 += s8 - s2;
s2 = s0 - s4 * 0.5f;
y[4] = s4 + s0;
s8 = t0 - t2 + s6;
s0 = t0 - t4 + t2;
s4 = t0 + t4 - s6;
s1 = y[1];
s3 = y[3];
s5 = y[5];
s7 = y[7];
s3 *= 0.86602540f;
t0 = (s5 + s1) * 0.98480775f;
t4 = (s5 - s7) * 0.34202014f;
t2 = (s1 + s7) * 0.64278761f;
s1 = (s1 - s5 - s7) * 0.86602540f;
s5 = t0 - s3 - t2;
s7 = t4 - s3 - t0;
s3 = t4 + s3 - t2;
y[0] = s4 - s7;
y[1] = s2 + s1;
y[2] = s0 - s3;
y[3] = s8 + s5;
y[5] = s8 - s5;
y[6] = s0 + s3;
y[7] = s2 - s1;
y[8] = s4 + s7;
}
static void L3_imdct36(float *grbuf, float *overlap, const float *window,
int nbands) {
int i, j;
static const float g_twid9[18] = { 0.73727734f, 0.79335334f, 0.84339145f,
0.88701083f, 0.92387953f, 0.95371695f,
0.97629601f, 0.99144486f, 0.99904822f,
0.67559021f, 0.60876143f, 0.53729961f,
0.46174861f, 0.38268343f, 0.30070580f,
0.21643961f, 0.13052619f, 0.04361938f };
for (j = 0; j < nbands; j++, grbuf += 18, overlap += 9) {
float co[9], si[9];
co[0] = -grbuf[0];
si[0] = grbuf[17];
for (i = 0; i < 4; i++) {
si[8 - 2 * i] = grbuf[4 * i + 1] - grbuf[4 * i + 2];
co[1 + 2 * i] = grbuf[4 * i + 1] + grbuf[4 * i + 2];
si[7 - 2 * i] = grbuf[4 * i + 4] - grbuf[4 * i + 3];
co[2 + 2 * i] = -(grbuf[4 * i + 3] + grbuf[4 * i + 4]);
}
L3_dct3_9(co);
L3_dct3_9(si);
si[1] = -si[1];
si[3] = -si[3];
si[5] = -si[5];
si[7] = -si[7];
i = 0;
#if HAVE_SIMD
if (have_simd())
for (; i < 8; i += 4) {
f4 vovl = VLD(overlap + i);
f4 vc = VLD(co + i);
f4 vs = VLD(si + i);
f4 vr0 = VLD(g_twid9 + i);
f4 vr1 = VLD(g_twid9 + 9 + i);
f4 vw0 = VLD(window + i);
f4 vw1 = VLD(window + 9 + i);
f4 vsum = VADD(VMUL(vc, vr1), VMUL(vs, vr0));
VSTORE(overlap + i, VSUB(VMUL(vc, vr0), VMUL(vs, vr1)));
VSTORE(grbuf + i, VSUB(VMUL(vovl, vw0), VMUL(vsum, vw1)));
vsum = VADD(VMUL(vovl, vw1), VMUL(vsum, vw0));
VSTORE(grbuf + 14 - i, VREV(vsum));
}
#endif /* HAVE_SIMD */
for (; i < 9; i++) {
float ovl = overlap[i];
float sum = co[i] * g_twid9[9 + i] + si[i] * g_twid9[0 + i];
overlap[i] = co[i] * g_twid9[0 + i] - si[i] * g_twid9[9 + i];
grbuf[i] = ovl * window[0 + i] - sum * window[9 + i];
grbuf[17 - i] = ovl * window[9 + i] + sum * window[0 + i];
}
}
}
static void L3_idct3(float x0, float x1, float x2, float *dst) {
float m1 = x1 * 0.86602540f;
float a1 = x0 - x2 * 0.5f;
dst[1] = x0 + x2;
dst[0] = a1 + m1;
dst[2] = a1 - m1;
}
static void L3_imdct12(float *x, float *dst, float *overlap) {
static const float g_twid3[6] = { 0.79335334f, 0.92387953f, 0.99144486f,
0.60876143f, 0.38268343f, 0.13052619f };
float co[3], si[3];
int i;
L3_idct3(-x[0], x[6] + x[3], x[12] + x[9], co);
L3_idct3(x[15], x[12] - x[9], x[6] - x[3], si);
si[1] = -si[1];
for (i = 0; i < 3; i++) {
float ovl = overlap[i];
float sum = co[i] * g_twid3[3 + i] + si[i] * g_twid3[0 + i];
overlap[i] = co[i] * g_twid3[0 + i] - si[i] * g_twid3[3 + i];
dst[i] = ovl * g_twid3[2 - i] - sum * g_twid3[5 - i];
dst[5 - i] = ovl * g_twid3[5 - i] + sum * g_twid3[2 - i];
}
}
static void L3_imdct_short(float *grbuf, float *overlap, int nbands) {
for (; nbands > 0; nbands--, overlap += 9, grbuf += 18) {
float tmp[18];
memcpy(tmp, grbuf, sizeof(tmp));
memcpy(grbuf, overlap, 6 * sizeof(float));
L3_imdct12(tmp, grbuf + 6, overlap + 6);
L3_imdct12(tmp + 1, grbuf + 12, overlap + 6);
L3_imdct12(tmp + 2, overlap, overlap + 6);
}
}
static void L3_change_sign(float *grbuf) {
int b, i;
for (b = 0, grbuf += 18; b < 32; b += 2, grbuf += 36)
for (i = 1; i < 18; i += 2) grbuf[i] = -grbuf[i];
}
static void L3_imdct_gr(float *grbuf, float *overlap, unsigned block_type,
unsigned n_long_bands) {
static const float g_mdct_window[2][18] = {
{ 0.99904822f, 0.99144486f, 0.97629601f, 0.95371695f, 0.92387953f,
0.88701083f, 0.84339145f, 0.79335334f, 0.73727734f, 0.04361938f,
0.13052619f, 0.21643961f, 0.30070580f, 0.38268343f, 0.46174861f,
0.53729961f, 0.60876143f, 0.67559021f },
{ 1, 1, 1, 1, 1, 1, 0.99144486f, 0.92387953f, 0.79335334f, 0, 0, 0, 0,
0, 0, 0.13052619f, 0.38268343f, 0.60876143f }
};
if (n_long_bands) {
L3_imdct36(grbuf, overlap, g_mdct_window[0], n_long_bands);
grbuf += 18 * n_long_bands;
overlap += 9 * n_long_bands;
}
if (block_type == SHORT_BLOCK_TYPE)
L3_imdct_short(grbuf, overlap, 32 - n_long_bands);
else
L3_imdct36(grbuf, overlap, g_mdct_window[block_type == STOP_BLOCK_TYPE],
32 - n_long_bands);
}
static void L3_save_reservoir(mp3dec_t *h, mp3dec_scratch_t *s) {
int pos = (s->bs.pos + 7) / 8u;
int remains = s->bs.limit / 8u - pos;
if (remains > MAX_BITRESERVOIR_BYTES) {
pos += remains - MAX_BITRESERVOIR_BYTES;
remains = MAX_BITRESERVOIR_BYTES;
}
if (remains > 0) {
memmove(h->reserv_buf, s->maindata + pos, remains);
}
h->reserv = remains;
}
static int L3_restore_reservoir(mp3dec_t *h, bs_t *bs, mp3dec_scratch_t *s,
int main_data_begin) {
int frame_bytes = (bs->limit - bs->pos) / 8;
int bytes_have = MINIMP3_MIN(h->reserv, main_data_begin);
memcpy(s->maindata,
h->reserv_buf + MINIMP3_MAX(0, h->reserv - main_data_begin),
MINIMP3_MIN(h->reserv, main_data_begin));
memcpy(s->maindata + bytes_have, bs->buf + bs->pos / 8, frame_bytes);
bs_init(&s->bs, s->maindata, bytes_have + frame_bytes);
return h->reserv >= main_data_begin;
}
static void L3_decode(mp3dec_t *h, mp3dec_scratch_t *s, L3_gr_info_t *gr_info,
int nch) {
int ch;
for (ch = 0; ch < nch; ch++) {
int layer3gr_limit = s->bs.pos + gr_info[ch].part_23_length;
L3_decode_scalefactors(h->header, s->ist_pos[ch], &s->bs, gr_info + ch,
s->scf, ch);
L3_huffman(s->grbuf[ch], &s->bs, gr_info + ch, s->scf, layer3gr_limit);
}
if (HDR_TEST_I_STEREO(h->header)) {
L3_intensity_stereo(s->grbuf[0], s->ist_pos[1], gr_info, h->header);
} else if (HDR_IS_MS_STEREO(h->header)) {
L3_midside_stereo(s->grbuf[0], 576);
}
for (ch = 0; ch < nch; ch++, gr_info++) {
int aa_bands = 31;
int n_long_bands = (gr_info->mixed_block_flag ? 2 : 0)
<< (int)(HDR_GET_MY_SAMPLE_RATE(h->header) == 2);
if (gr_info->n_short_sfb) {
aa_bands = n_long_bands - 1;
L3_reorder(s->grbuf[ch] + n_long_bands * 18, s->syn[0],
gr_info->sfbtab + gr_info->n_long_sfb);
}
L3_antialias(s->grbuf[ch], aa_bands);
L3_imdct_gr(s->grbuf[ch], h->mdct_overlap[ch], gr_info->block_type,
n_long_bands);
L3_change_sign(s->grbuf[ch]);
}
}
static void mp3d_DCT_II(float *grbuf, int n) {
static const float g_sec[24] = {
10.19000816f, 0.50060302f, 0.50241929f, 3.40760851f, 0.50547093f,
0.52249861f, 2.05778098f, 0.51544732f, 0.56694406f, 1.48416460f,
0.53104258f, 0.64682180f, 1.16943991f, 0.55310392f, 0.78815460f,
0.97256821f, 0.58293498f, 1.06067765f, 0.83934963f, 0.62250412f,
1.72244716f, 0.74453628f, 0.67480832f, 5.10114861f
};
int i, k = 0;
#if HAVE_SIMD
if (have_simd())
for (; k < n; k += 4) {
f4 t[4][8], *x;
float *y = grbuf + k;
for (x = t[0], i = 0; i < 8; i++, x++) {
f4 x0 = VLD(&y[i * 18]);
f4 x1 = VLD(&y[(15 - i) * 18]);
f4 x2 = VLD(&y[(16 + i) * 18]);
f4 x3 = VLD(&y[(31 - i) * 18]);
f4 t0 = VADD(x0, x3);
f4 t1 = VADD(x1, x2);
f4 t2 = VMUL_S(VSUB(x1, x2), g_sec[3 * i + 0]);
f4 t3 = VMUL_S(VSUB(x0, x3), g_sec[3 * i + 1]);
x[0] = VADD(t0, t1);
x[8] = VMUL_S(VSUB(t0, t1), g_sec[3 * i + 2]);
x[16] = VADD(t3, t2);
x[24] = VMUL_S(VSUB(t3, t2), g_sec[3 * i + 2]);
}
for (x = t[0], i = 0; i < 4; i++, x += 8) {
f4 x0 = x[0], x1 = x[1], x2 = x[2], x3 = x[3], x4 = x[4],
x5 = x[5], x6 = x[6], x7 = x[7], xt;
xt = VSUB(x0, x7);
x0 = VADD(x0, x7);
x7 = VSUB(x1, x6);
x1 = VADD(x1, x6);
x6 = VSUB(x2, x5);
x2 = VADD(x2, x5);
x5 = VSUB(x3, x4);
x3 = VADD(x3, x4);
x4 = VSUB(x0, x3);
x0 = VADD(x0, x3);
x3 = VSUB(x1, x2);
x1 = VADD(x1, x2);
x[0] = VADD(x0, x1);
x[4] = VMUL_S(VSUB(x0, x1), 0.70710677f);
x5 = VADD(x5, x6);
x6 = VMUL_S(VADD(x6, x7), 0.70710677f);
x7 = VADD(x7, xt);
x3 = VMUL_S(VADD(x3, x4), 0.70710677f);
x5 = VSUB(x5, VMUL_S(x7, 0.198912367f)); /* rotate by PI/8 */
x7 = VADD(x7, VMUL_S(x5, 0.382683432f));
x5 = VSUB(x5, VMUL_S(x7, 0.198912367f));
x0 = VSUB(xt, x6);
xt = VADD(xt, x6);
x[1] = VMUL_S(VADD(xt, x7), 0.50979561f);
x[2] = VMUL_S(VADD(x4, x3), 0.54119611f);
x[3] = VMUL_S(VSUB(x0, x5), 0.60134488f);
x[5] = VMUL_S(VADD(x0, x5), 0.89997619f);
x[6] = VMUL_S(VSUB(x4, x3), 1.30656302f);
x[7] = VMUL_S(VSUB(xt, x7), 2.56291556f);
}
if (k > n - 3) {
#if HAVE_SSE
#define VSAVE2(i, v) _mm_storel_pi((__m64 *)(void *)&y[i * 18], v)
#else /* HAVE_SSE */
#define VSAVE2(i, v) vst1_f32((float32_t *)&y[i * 18], vget_low_f32(v))
#endif /* HAVE_SSE */
for (i = 0; i < 7; i++, y += 4 * 18) {
f4 s = VADD(t[3][i], t[3][i + 1]);
VSAVE2(0, t[0][i]);
VSAVE2(1, VADD(t[2][i], s));
VSAVE2(2, VADD(t[1][i], t[1][i + 1]));
VSAVE2(3, VADD(t[2][1 + i], s));
}
VSAVE2(0, t[0][7]);
VSAVE2(1, VADD(t[2][7], t[3][7]));
VSAVE2(2, t[1][7]);
VSAVE2(3, t[3][7]);
} else {
#define VSAVE4(i, v) VSTORE(&y[i * 18], v)
for (i = 0; i < 7; i++, y += 4 * 18) {
f4 s = VADD(t[3][i], t[3][i + 1]);
VSAVE4(0, t[0][i]);
VSAVE4(1, VADD(t[2][i], s));
VSAVE4(2, VADD(t[1][i], t[1][i + 1]));
VSAVE4(3, VADD(t[2][1 + i], s));
}
VSAVE4(0, t[0][7]);
VSAVE4(1, VADD(t[2][7], t[3][7]));
VSAVE4(2, t[1][7]);
VSAVE4(3, t[3][7]);
}
}
else
#endif /* HAVE_SIMD */
#ifdef MINIMP3_ONLY_SIMD
{
} /* for HAVE_SIMD=1, MINIMP3_ONLY_SIMD=1 case we do not need non-intrinsic
"else" branch */
#else /* MINIMP3_ONLY_SIMD */
for (; k < n; k++) {
float t[4][8], *x, *y = grbuf + k;
for (x = t[0], i = 0; i < 8; i++, x++) {
float x0 = y[i * 18];
float x1 = y[(15 - i) * 18];
float x2 = y[(16 + i) * 18];
float x3 = y[(31 - i) * 18];
float t0 = x0 + x3;
float t1 = x1 + x2;
float t2 = (x1 - x2) * g_sec[3 * i + 0];
float t3 = (x0 - x3) * g_sec[3 * i + 1];
x[0] = t0 + t1;
x[8] = (t0 - t1) * g_sec[3 * i + 2];
x[16] = t3 + t2;
x[24] = (t3 - t2) * g_sec[3 * i + 2];
}
for (x = t[0], i = 0; i < 4; i++, x += 8) {
float x0 = x[0], x1 = x[1], x2 = x[2], x3 = x[3], x4 = x[4],
x5 = x[5], x6 = x[6], x7 = x[7], xt;
xt = x0 - x7;
x0 += x7;
x7 = x1 - x6;
x1 += x6;
x6 = x2 - x5;
x2 += x5;
x5 = x3 - x4;
x3 += x4;
x4 = x0 - x3;
x0 += x3;
x3 = x1 - x2;
x1 += x2;
x[0] = x0 + x1;
x[4] = (x0 - x1) * 0.70710677f;
x5 = x5 + x6;
x6 = (x6 + x7) * 0.70710677f;
x7 = x7 + xt;
x3 = (x3 + x4) * 0.70710677f;
x5 -= x7 * 0.198912367f; /* rotate by PI/8 */
x7 += x5 * 0.382683432f;
x5 -= x7 * 0.198912367f;
x0 = xt - x6;
xt += x6;
x[1] = (xt + x7) * 0.50979561f;
x[2] = (x4 + x3) * 0.54119611f;
x[3] = (x0 - x5) * 0.60134488f;
x[5] = (x0 + x5) * 0.89997619f;
x[6] = (x4 - x3) * 1.30656302f;
x[7] = (xt - x7) * 2.56291556f;
}
for (i = 0; i < 7; i++, y += 4 * 18) {
y[0 * 18] = t[0][i];
y[1 * 18] = t[2][i] + t[3][i] + t[3][i + 1];
y[2 * 18] = t[1][i] + t[1][i + 1];
y[3 * 18] = t[2][i + 1] + t[3][i] + t[3][i + 1];
}
y[0 * 18] = t[0][7];
y[1 * 18] = t[2][7] + t[3][7];
y[2 * 18] = t[1][7];
y[3 * 18] = t[3][7];
}
#endif /* MINIMP3_ONLY_SIMD */
}
#ifndef MINIMP3_FLOAT_OUTPUT
static int16_t mp3d_scale_pcm(float sample) {
#if HAVE_ARMV6
int32_t s32 = (int32_t)(sample + .5f);
s32 -= (s32 < 0);
int16_t s = (int16_t)minimp3_clip_int16_arm(s32);
#else
if (sample >= 32766.5) return (int16_t)32767;
if (sample <= -32767.5) return (int16_t)-32768;
int16_t s = (int16_t)(sample + .5f);
s -= (s < 0); /* away from zero, to be compliant */
#endif
return s;
}
#else /* MINIMP3_FLOAT_OUTPUT */
static float mp3d_scale_pcm(float sample) { return sample * (1.f / 32768.f); }
#endif /* MINIMP3_FLOAT_OUTPUT */
static void mp3d_synth_pair(mp3d_sample_t *pcm, int nch, const float *z) {
float a;
a = (z[14 * 64] - z[0]) * 29;
a += (z[1 * 64] + z[13 * 64]) * 213;
a += (z[12 * 64] - z[2 * 64]) * 459;
a += (z[3 * 64] + z[11 * 64]) * 2037;
a += (z[10 * 64] - z[4 * 64]) * 5153;
a += (z[5 * 64] + z[9 * 64]) * 6574;
a += (z[8 * 64] - z[6 * 64]) * 37489;
a += z[7 * 64] * 75038;
pcm[0] = mp3d_scale_pcm(a);
z += 2;
a = z[14 * 64] * 104;
a += z[12 * 64] * 1567;
a += z[10 * 64] * 9727;
a += z[8 * 64] * 64019;
a += z[6 * 64] * -9975;
a += z[4 * 64] * -45;
a += z[2 * 64] * 146;
a += z[0 * 64] * -5;
pcm[16 * nch] = mp3d_scale_pcm(a);
}
static void mp3d_synth(float *xl, mp3d_sample_t *dstl, int nch, float *lins) {
int i;
float *xr = xl + 576 * (nch - 1);
mp3d_sample_t *dstr = dstl + (nch - 1);
static const float g_win[] = {
-1, 26, -31, 208, 218, 401, -519, 2063, 2000,
4788, -5517, 7134, 5959, 35640, -39336, 74992, -1, 24,
-35, 202, 222, 347, -581, 2080, 1952, 4425, -5879,
7640, 5288, 33791, -41176, 74856, -1, 21, -38, 196,
225, 294, -645, 2087, 1893, 4063, -6237, 8092, 4561,
31947, -43006, 74630, -1, 19, -41, 190, 227, 244,
-711, 2085, 1822, 3705, -6589, 8492, 3776, 30112, -44821,
74313, -1, 17, -45, 183, 228, 197, -779, 2075,
1739, 3351, -6935, 8840, 2935, 28289, -46617, 73908, -1,
16, -49, 176, 228, 153, -848, 2057, 1644, 3004,
-7271, 9139, 2037, 26482, -48390, 73415, -2, 14, -53,
169, 227, 111, -919, 2032, 1535, 2663, -7597, 9389,
1082, 24694, -50137, 72835, -2, 13, -58, 161, 224,
72, -991, 2001, 1414, 2330, -7910, 9592, 70, 22929,
-51853, 72169, -2, 11, -63, 154, 221, 36, -1064,
1962, 1280, 2006, -8209, 9750, -998, 21189, -53534, 71420,
-2, 10, -68, 147, 215, 2, -1137, 1919, 1131,
1692, -8491, 9863, -2122, 19478, -55178, 70590, -3, 9,
-73, 139, 208, -29, -1210, 1870, 970, 1388, -8755,
9935, -3300, 17799, -56778, 69679, -3, 8, -79, 132,
200, -57, -1283, 1817, 794, 1095, -8998, 9966, -4533,
16155, -58333, 68692, -4, 7, -85, 125, 189, -83,
-1356, 1759, 605, 814, -9219, 9959, -5818, 14548, -59838,
67629, -4, 7, -91, 117, 177, -106, -1428, 1698,
402, 545, -9416, 9916, -7154, 12980, -61289, 66494, -5,
6, -97, 111, 163, -127, -1498, 1634, 185, 288,
-9585, 9838, -8540, 11455, -62684, 65290
};
float *zlin = lins + 15 * 64;
const float *w = g_win;
zlin[4 * 15] = xl[18 * 16];
zlin[4 * 15 + 1] = xr[18 * 16];
zlin[4 * 15 + 2] = xl[0];
zlin[4 * 15 + 3] = xr[0];
zlin[4 * 31] = xl[1 + 18 * 16];
zlin[4 * 31 + 1] = xr[1 + 18 * 16];
zlin[4 * 31 + 2] = xl[1];
zlin[4 * 31 + 3] = xr[1];
mp3d_synth_pair(dstr, nch, lins + 4 * 15 + 1);
mp3d_synth_pair(dstr + 32 * nch, nch, lins + 4 * 15 + 64 + 1);
mp3d_synth_pair(dstl, nch, lins + 4 * 15);
mp3d_synth_pair(dstl + 32 * nch, nch, lins + 4 * 15 + 64);
#if HAVE_SIMD
if (have_simd())
for (i = 14; i >= 0; i--) {
#define VLOAD(k) \
f4 w0 = VSET(*w++); \
f4 w1 = VSET(*w++); \
f4 vz = VLD(&zlin[4 * i - 64 * k]); \
f4 vy = VLD(&zlin[4 * i - 64 * (15 - k)]);
#define V0(k) \
{ \
VLOAD(k) b = VADD(VMUL(vz, w1), VMUL(vy, w0)); \
a = VSUB(VMUL(vz, w0), VMUL(vy, w1)); \
}
#define V1(k) \
{ \
VLOAD(k) b = VADD(b, VADD(VMUL(vz, w1), VMUL(vy, w0))); \
a = VADD(a, VSUB(VMUL(vz, w0), VMUL(vy, w1))); \
}
#define V2(k) \
{ \
VLOAD(k) b = VADD(b, VADD(VMUL(vz, w1), VMUL(vy, w0))); \
a = VADD(a, VSUB(VMUL(vy, w1), VMUL(vz, w0))); \
}
f4 a, b;
zlin[4 * i] = xl[18 * (31 - i)];
zlin[4 * i + 1] = xr[18 * (31 - i)];
zlin[4 * i + 2] = xl[1 + 18 * (31 - i)];
zlin[4 * i + 3] = xr[1 + 18 * (31 - i)];
zlin[4 * i + 64] = xl[1 + 18 * (1 + i)];
zlin[4 * i + 64 + 1] = xr[1 + 18 * (1 + i)];
zlin[4 * i - 64 + 2] = xl[18 * (1 + i)];
zlin[4 * i - 64 + 3] = xr[18 * (1 + i)];
V0(0)
V2(1)
V1(2)
V2(3)
V1(4)
V2(5)
V1(6)
V2(7)
{
#ifndef MINIMP3_FLOAT_OUTPUT
#if HAVE_SSE
static const f4 g_max = { 32767.0f, 32767.0f, 32767.0f,
32767.0f };
static const f4 g_min = { -32768.0f, -32768.0f, -32768.0f,
-32768.0f };
__m128i pcm8 = _mm_packs_epi32(
_mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(a, g_max), g_min)),
_mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(b, g_max), g_min)));
dstr[(15 - i) * nch] = _mm_extract_epi16(pcm8, 1);
dstr[(17 + i) * nch] = _mm_extract_epi16(pcm8, 5);
dstl[(15 - i) * nch] = _mm_extract_epi16(pcm8, 0);
dstl[(17 + i) * nch] = _mm_extract_epi16(pcm8, 4);
dstr[(47 - i) * nch] = _mm_extract_epi16(pcm8, 3);
dstr[(49 + i) * nch] = _mm_extract_epi16(pcm8, 7);
dstl[(47 - i) * nch] = _mm_extract_epi16(pcm8, 2);
dstl[(49 + i) * nch] = _mm_extract_epi16(pcm8, 6);
#else /* HAVE_SSE */
int16x4_t pcma, pcmb;
a = VADD(a, VSET(0.5f));
b = VADD(b, VSET(0.5f));
pcma = vqmovn_s32(
vqaddq_s32(vcvtq_s32_f32(a),
vreinterpretq_s32_u32(vcltq_f32(a, VSET(0)))));
pcmb = vqmovn_s32(
vqaddq_s32(vcvtq_s32_f32(b),
vreinterpretq_s32_u32(vcltq_f32(b, VSET(0)))));
vst1_lane_s16(dstr + (15 - i) * nch, pcma, 1);
vst1_lane_s16(dstr + (17 + i) * nch, pcmb, 1);
vst1_lane_s16(dstl + (15 - i) * nch, pcma, 0);
vst1_lane_s16(dstl + (17 + i) * nch, pcmb, 0);
vst1_lane_s16(dstr + (47 - i) * nch, pcma, 3);
vst1_lane_s16(dstr + (49 + i) * nch, pcmb, 3);
vst1_lane_s16(dstl + (47 - i) * nch, pcma, 2);
vst1_lane_s16(dstl + (49 + i) * nch, pcmb, 2);
#endif /* HAVE_SSE */
#else /* MINIMP3_FLOAT_OUTPUT */
static const f4 g_scale = { 1.0f / 32768.0f, 1.0f / 32768.0f,
1.0f / 32768.0f, 1.0f / 32768.0f };
a = VMUL(a, g_scale);
b = VMUL(b, g_scale);
#if HAVE_SSE
_mm_store_ss(dstr + (15 - i) * nch,
_mm_shuffle_ps(a, a, _MM_SHUFFLE(1, 1, 1, 1)));
_mm_store_ss(dstr + (17 + i) * nch,
_mm_shuffle_ps(b, b, _MM_SHUFFLE(1, 1, 1, 1)));
_mm_store_ss(dstl + (15 - i) * nch,
_mm_shuffle_ps(a, a, _MM_SHUFFLE(0, 0, 0, 0)));
_mm_store_ss(dstl + (17 + i) * nch,
_mm_shuffle_ps(b, b, _MM_SHUFFLE(0, 0, 0, 0)));
_mm_store_ss(dstr + (47 - i) * nch,
_mm_shuffle_ps(a, a, _MM_SHUFFLE(3, 3, 3, 3)));
_mm_store_ss(dstr + (49 + i) * nch,
_mm_shuffle_ps(b, b, _MM_SHUFFLE(3, 3, 3, 3)));
_mm_store_ss(dstl + (47 - i) * nch,
_mm_shuffle_ps(a, a, _MM_SHUFFLE(2, 2, 2, 2)));
_mm_store_ss(dstl + (49 + i) * nch,
_mm_shuffle_ps(b, b, _MM_SHUFFLE(2, 2, 2, 2)));
#else /* HAVE_SSE */
vst1q_lane_f32(dstr + (15 - i) * nch, a, 1);
vst1q_lane_f32(dstr + (17 + i) * nch, b, 1);
vst1q_lane_f32(dstl + (15 - i) * nch, a, 0);
vst1q_lane_f32(dstl + (17 + i) * nch, b, 0);
vst1q_lane_f32(dstr + (47 - i) * nch, a, 3);
vst1q_lane_f32(dstr + (49 + i) * nch, b, 3);
vst1q_lane_f32(dstl + (47 - i) * nch, a, 2);
vst1q_lane_f32(dstl + (49 + i) * nch, b, 2);
#endif /* HAVE_SSE */
#endif /* MINIMP3_FLOAT_OUTPUT */
}
}
else
#endif /* HAVE_SIMD */
#ifdef MINIMP3_ONLY_SIMD
{
} /* for HAVE_SIMD=1, MINIMP3_ONLY_SIMD=1 case we do not need non-intrinsic
"else" branch */
#else /* MINIMP3_ONLY_SIMD */
for (i = 14; i >= 0; i--) {
#define LOAD(k) \
float w0 = *w++; \
float w1 = *w++; \
float *vz = &zlin[4 * i - k * 64]; \
float *vy = &zlin[4 * i - (15 - k) * 64];
#define S0(k) \
{ \
int j; \
LOAD(k); \
for (j = 0; j < 4; j++) \
b[j] = vz[j] * w1 + vy[j] * w0, a[j] = vz[j] * w0 - vy[j] * w1; \
}
#define S1(k) \
{ \
int j; \
LOAD(k); \
for (j = 0; j < 4; j++) \
b[j] += vz[j] * w1 + vy[j] * w0, a[j] += vz[j] * w0 - vy[j] * w1; \
}
#define S2(k) \
{ \
int j; \
LOAD(k); \
for (j = 0; j < 4; j++) \
b[j] += vz[j] * w1 + vy[j] * w0, a[j] += vy[j] * w1 - vz[j] * w0; \
}
float a[4], b[4];
zlin[4 * i] = xl[18 * (31 - i)];
zlin[4 * i + 1] = xr[18 * (31 - i)];
zlin[4 * i + 2] = xl[1 + 18 * (31 - i)];
zlin[4 * i + 3] = xr[1 + 18 * (31 - i)];
zlin[4 * (i + 16)] = xl[1 + 18 * (1 + i)];
zlin[4 * (i + 16) + 1] = xr[1 + 18 * (1 + i)];
zlin[4 * (i - 16) + 2] = xl[18 * (1 + i)];
zlin[4 * (i - 16) + 3] = xr[18 * (1 + i)];
S0(0)
S2(1)
S1(2)
S2(3)
S1(4)
S2(5)
S1(6)
S2(7)
dstr[(15 - i) * nch] = mp3d_scale_pcm(a[1]);
dstr[(17 + i) * nch] = mp3d_scale_pcm(b[1]);
dstl[(15 - i) * nch] = mp3d_scale_pcm(a[0]);
dstl[(17 + i) * nch] = mp3d_scale_pcm(b[0]);
dstr[(47 - i) * nch] = mp3d_scale_pcm(a[3]);
dstr[(49 + i) * nch] = mp3d_scale_pcm(b[3]);
dstl[(47 - i) * nch] = mp3d_scale_pcm(a[2]);
dstl[(49 + i) * nch] = mp3d_scale_pcm(b[2]);
}
#endif /* MINIMP3_ONLY_SIMD */
}
static void mp3d_synth_granule(float *qmf_state, float *grbuf, int nbands,
int nch, mp3d_sample_t *pcm, float *lins) {
int i;
for (i = 0; i < nch; i++) {
mp3d_DCT_II(grbuf + 576 * i, nbands);
}
memcpy(lins, qmf_state, sizeof(float) * 15 * 64);
for (i = 0; i < nbands; i += 2) {
mp3d_synth(grbuf + i, pcm + 32 * nch * i, nch, lins + i * 64);
}
#ifndef MINIMP3_NONSTANDARD_BUT_LOGICAL
if (nch == 1) {
for (i = 0; i < 15 * 64; i += 2) {
qmf_state[i] = lins[nbands * 64 + i];
}
} else
#endif /* MINIMP3_NONSTANDARD_BUT_LOGICAL */
{
memcpy(qmf_state, lins + nbands * 64, sizeof(float) * 15 * 64);
}
}
static int mp3d_match_frame(const uint8_t *hdr, int mp3_bytes,
int frame_bytes) {
int i, nmatch;
for (i = 0, nmatch = 0; nmatch < MAX_FRAME_SYNC_MATCHES; nmatch++) {
i += hdr_frame_bytes(hdr + i, frame_bytes) + hdr_padding(hdr + i);
if (i + HDR_SIZE > mp3_bytes) return nmatch > 0;
if (!hdr_compare(hdr, hdr + i)) return 0;
}
return 1;
}
static int mp3d_find_frame(const uint8_t *mp3, int mp3_bytes,
int *free_format_bytes, int *ptr_frame_bytes) {
int i, k;
for (i = 0; i < mp3_bytes - HDR_SIZE; i++, mp3++) {
if (hdr_valid(mp3)) {
int frame_bytes = hdr_frame_bytes(mp3, *free_format_bytes);
int frame_and_padding = frame_bytes + hdr_padding(mp3);
for (k = HDR_SIZE; !frame_bytes && k < MAX_FREE_FORMAT_FRAME_SIZE &&
i + 2 * k < mp3_bytes - HDR_SIZE;
k++) {
if (hdr_compare(mp3, mp3 + k)) {
int fb = k - hdr_padding(mp3);
int nextfb = fb + hdr_padding(mp3 + k);
if (i + k + nextfb + HDR_SIZE > mp3_bytes ||
!hdr_compare(mp3, mp3 + k + nextfb))
continue;
frame_and_padding = k;
frame_bytes = fb;
*free_format_bytes = fb;
}
}
if ((frame_bytes && i + frame_and_padding <= mp3_bytes &&
mp3d_match_frame(mp3, mp3_bytes - i, frame_bytes)) ||
(!i && frame_and_padding == mp3_bytes)) {
*ptr_frame_bytes = frame_and_padding;
return i;
}
*free_format_bytes = 0;
}
}
*ptr_frame_bytes = 0;
return mp3_bytes;
}
void mp3dec_init(mp3dec_t *dec) { dec->header[0] = 0; }
int mp3dec_decode_frame(mp3dec_t *dec, const uint8_t *mp3, int mp3_bytes,
mp3d_sample_t *pcm, mp3dec_frame_info_t *info) {
int i = 0, igr, frame_size = 0, success = 1;
const uint8_t *hdr;
bs_t bs_frame[1];
if (mp3_bytes > 4 && dec->header[0] == 0xff &&
hdr_compare(dec->header, mp3)) {
frame_size =
hdr_frame_bytes(mp3, dec->free_format_bytes) + hdr_padding(mp3);
if (frame_size != mp3_bytes && (frame_size + HDR_SIZE > mp3_bytes ||
!hdr_compare(mp3, mp3 + frame_size))) {
frame_size = 0;
}
}
if (!frame_size) {
memset(dec, 0, sizeof(mp3dec_t));
i = mp3d_find_frame(mp3, mp3_bytes, &dec->free_format_bytes,
&frame_size);
if (!frame_size || i + frame_size > mp3_bytes) {
info->frame_bytes = i;
return 0;
}
}
hdr = mp3 + i;
memcpy(dec->header, hdr, HDR_SIZE);
info->frame_bytes = i + frame_size;
info->frame_offset = i;
info->channels = HDR_IS_MONO(hdr) ? 1 : 2;
info->hz = hdr_sample_rate_hz(hdr);
info->layer = 4 - HDR_GET_LAYER(hdr);
info->bitrate_kbps = hdr_bitrate_kbps(hdr);
if (!pcm) {
return hdr_frame_samples(hdr);
}
bs_init(bs_frame, hdr + HDR_SIZE, frame_size - HDR_SIZE);
if (HDR_IS_CRC(hdr)) {
get_bits(bs_frame, 16);
}
if (info->layer == 3) {
int main_data_begin =
L3_read_side_info(bs_frame, dec->scratch.gr_info, hdr);
if (main_data_begin < 0 || bs_frame->pos > bs_frame->limit) {
mp3dec_init(dec);
return 0;
}
success =
L3_restore_reservoir(dec, bs_frame, &dec->scratch, main_data_begin);
if (success) {
for (igr = 0; igr < (HDR_TEST_MPEG1(hdr) ? 2 : 1);
igr++, pcm += 576 * info->channels) {
memset(dec->scratch.grbuf[0], 0, 576 * 2 * sizeof(float));
L3_decode(dec, &dec->scratch,
dec->scratch.gr_info + igr * info->channels,
info->channels);
mp3d_synth_granule(dec->qmf_state, dec->scratch.grbuf[0], 18,
info->channels, pcm, dec->scratch.syn[0]);
}
}
L3_save_reservoir(dec, &dec->scratch);
} else {
#ifdef MINIMP3_ONLY_MP3
return 0;
#else /* MINIMP3_ONLY_MP3 */
L12_read_scale_info(hdr, bs_frame, &dec->sci);
memset(dec->scratch.grbuf[0], 0, 576 * 2 * sizeof(float));
for (i = 0, igr = 0; igr < 3; igr++) {
if (12 == (i += L12_dequantize_granule(dec->scratch.grbuf[0] + i,
bs_frame, &dec->sci,
info->layer | 1))) {
i = 0;
L12_apply_scf_384(&dec->sci, dec->sci.scf + igr,
dec->scratch.grbuf[0]);
mp3d_synth_granule(dec->qmf_state, dec->scratch.grbuf[0], 12,
info->channels, pcm, dec->scratch.syn[0]);
memset(dec->scratch.grbuf[0], 0, 576 * 2 * sizeof(float));
pcm += 384 * info->channels;
}
if (bs_frame->pos > bs_frame->limit) {
mp3dec_init(dec);
return 0;
}
}
#endif /* MINIMP3_ONLY_MP3 */
}
return success * hdr_frame_samples(dec->header);
}
#ifdef MINIMP3_FLOAT_OUTPUT
void mp3dec_f32_to_s16(const float *in, int16_t *out, int num_samples) {
int i = 0;
#if HAVE_SIMD
int aligned_count = num_samples & ~7;
for (; i < aligned_count; i += 8) {
static const f4 g_scale = { 32768.0f, 32768.0f, 32768.0f, 32768.0f };
f4 a = VMUL(VLD(&in[i]), g_scale);
f4 b = VMUL(VLD(&in[i + 4]), g_scale);
#if HAVE_SSE
static const f4 g_max = { 32767.0f, 32767.0f, 32767.0f, 32767.0f };
static const f4 g_min = { -32768.0f, -32768.0f, -32768.0f, -32768.0f };
__m128i pcm8 = _mm_packs_epi32(
_mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(a, g_max), g_min)),
_mm_cvtps_epi32(_mm_max_ps(_mm_min_ps(b, g_max), g_min)));
out[i] = _mm_extract_epi16(pcm8, 0);
out[i + 1] = _mm_extract_epi16(pcm8, 1);
out[i + 2] = _mm_extract_epi16(pcm8, 2);
out[i + 3] = _mm_extract_epi16(pcm8, 3);
out[i + 4] = _mm_extract_epi16(pcm8, 4);
out[i + 5] = _mm_extract_epi16(pcm8, 5);
out[i + 6] = _mm_extract_epi16(pcm8, 6);
out[i + 7] = _mm_extract_epi16(pcm8, 7);
#else /* HAVE_SSE */
int16x4_t pcma, pcmb;
a = VADD(a, VSET(0.5f));
b = VADD(b, VSET(0.5f));
pcma = vqmovn_s32(vqaddq_s32(
vcvtq_s32_f32(a), vreinterpretq_s32_u32(vcltq_f32(a, VSET(0)))));
pcmb = vqmovn_s32(vqaddq_s32(
vcvtq_s32_f32(b), vreinterpretq_s32_u32(vcltq_f32(b, VSET(0)))));
vst1_lane_s16(out + i, pcma, 0);
vst1_lane_s16(out + i + 1, pcma, 1);
vst1_lane_s16(out + i + 2, pcma, 2);
vst1_lane_s16(out + i + 3, pcma, 3);
vst1_lane_s16(out + i + 4, pcmb, 0);
vst1_lane_s16(out + i + 5, pcmb, 1);
vst1_lane_s16(out + i + 6, pcmb, 2);
vst1_lane_s16(out + i + 7, pcmb, 3);
#endif /* HAVE_SSE */
}
#endif /* HAVE_SIMD */
for (; i < num_samples; i++) {
float sample = in[i] * 32768.0f;
if (sample >= 32766.5)
out[i] = (int16_t)32767;
else if (sample <= -32767.5)
out[i] = (int16_t)-32768;
else {
int16_t s = (int16_t)(sample + .5f);
s -= (s < 0); /* away from zero, to be compliant */
out[i] = s;
}
}
}
#endif /* MINIMP3_FLOAT_OUTPUT */
#endif /* MINIMP3_IMPLEMENTATION && !_MINIMP3_IMPLEMENTATION_GUARD */
......@@ -16,6 +16,7 @@ target_sources(usermod_badge23 INTERFACE
${CMAKE_CURRENT_LIST_DIR}/mp_sys_display.c
${CMAKE_CURRENT_LIST_DIR}/mp_sys_kernel.c
${CMAKE_CURRENT_LIST_DIR}/mp_uctx.c
${CMAKE_CURRENT_LIST_DIR}/mp_media.c
)
target_include_directories(usermod_badge23 INTERFACE
......
#include <st3m_media.h>
#include "py/builtin.h"
#include "py/runtime.h"
typedef struct _mp_ctx_obj_t {
mp_obj_base_t base;
Ctx *ctx;
mp_obj_t user_data;
} mp_ctx_obj_t;
STATIC mp_obj_t mp_load(mp_obj_t path) {
return mp_obj_new_int(st3m_media_load(mp_obj_str_get_str(path)));
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_load_obj, mp_load);
STATIC mp_obj_t mp_draw(mp_obj_t uctx_mp) {
mp_ctx_obj_t *uctx = MP_OBJ_TO_PTR(uctx_mp);
st3m_media_draw(uctx->ctx);
return 0;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_draw_obj, mp_draw);
STATIC mp_obj_t mp_think(mp_obj_t ms_in) {
st3m_media_think(mp_obj_get_float(ms_in));
return 0;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_think_obj, mp_think);
STATIC mp_obj_t mp_stop(void) {
st3m_media_stop();
return 0;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_stop_obj, mp_stop);
STATIC const mp_rom_map_elem_t globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_draw), MP_ROM_PTR(&mp_draw_obj) },
{ MP_ROM_QSTR(MP_QSTR_think), MP_ROM_PTR(&mp_think_obj) },
{ MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&mp_stop_obj) },
{ MP_ROM_QSTR(MP_QSTR_load), MP_ROM_PTR(&mp_load_obj) },
};
STATIC MP_DEFINE_CONST_DICT(globals, globals_table);
const mp_obj_module_t mp_module_media = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t *)&globals,
};
MP_REGISTER_MODULE(MP_QSTR_media, mp_module_media);
......@@ -19,6 +19,7 @@ idf_component_register(
st3m_usb_msc.c
st3m_usb.c
st3m_console.c
st3m_media.c
st3m_mode.c
st3m_captouch.c
st3m_ringbuffer.c
......@@ -40,6 +41,9 @@ idf_component_register(
esp_timer
esp_netif
usb
audio_mod
audio_mp3
video_mpeg
)
idf_component_get_property(tusb_lib tinyusb COMPONENT_LIB)
......
#include "st3m_media.h"
#include "st3m_audio.h"
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#ifdef CONFIG_FLOW3R_CTX_FLAVOUR_FULL
static st3m_media *audio_media = NULL;
static int16_t *audio_buffer = NULL;
void st3m_media_audio_render(int16_t *rx, int16_t *tx, uint16_t len) {
if (!audio_media) return;
for (int i = 0; i < len; i++) {
if ((audio_media->audio_r + 1 != audio_media->audio_w) &&
(audio_media->audio_r + 1 - AUDIO_BUF_SIZE !=
audio_media->audio_w)) {
tx[i] = audio_media->audio_buffer[audio_media->audio_r++];
if (audio_media->audio_r >= AUDIO_BUF_SIZE)
audio_media->audio_r = 0;
} else
tx[i] = 0;
}
}
int st3m_media_samples_queued(void) {
if (!audio_media) return 0;
if (audio_media->audio_r > audio_media->audio_w)
return (AUDIO_BUF_SIZE - audio_media->audio_r) + audio_media->audio_w;
return audio_media->audio_w - audio_media->audio_r;
}
// XXX : it would be better to be able to push and pop the
// st3m_audio_player_function
void bl00mbox_audio_render(int16_t *rx, int16_t *tx, uint16_t len);
void st3m_media_stop(void) {
if (audio_media && audio_media->destroy) audio_media->destroy(audio_media);
audio_media = 0;
st3m_audio_set_player_function(bl00mbox_audio_render);
if (audio_buffer) {
free(audio_buffer);
audio_buffer = NULL;
}
}
void st3m_media_pause(void) {
if (!audio_media) return;
audio_media->paused = 1;
}
void st3m_media_play(void) {
if (!audio_media) return;
audio_media->paused = 0;
}
int st3m_media_is_playing(void) {
if (!audio_media) return 0;
return !audio_media->paused;
}
float st3m_media_get_duration(void) {
if (!audio_media) return 0;
return audio_media->duration;
}
float st3m_media_get_position(void) {
if (!audio_media) return 0;
return audio_media->position;
}
float st3m_media_get_time(void) {
if (!audio_media) return 0;
return audio_media->time;
}
void st3m_media_seek(float position) {
if (!audio_media) return;
audio_media->seek = position;
}
void st3m_media_seek_relative(float time) {
if (!audio_media) return;
st3m_media_seek((audio_media->position * audio_media->duration) + time);
}
void st3m_media_draw(Ctx *ctx) {
if (audio_media && audio_media->draw) audio_media->draw(audio_media, ctx);
}
void st3m_media_think(float ms) {
if (audio_media && audio_media->think) audio_media->think(audio_media, ms);
}
char *st3m_media_get_string(const char *key) {
if (!audio_media) return NULL;
if (!audio_media->get_string) return NULL;
return audio_media->get_string(audio_media, key);
}
float st3m_media_get(const char *key) {
if (!audio_media || !audio_media->get_string) return -1.0f;
return audio_media->get(audio_media, key);
}
void st3m_media_set(const char *key, float value) {
if (!audio_media || !audio_media->set) return;
return audio_media->set(audio_media, key, value);
}
st3m_media *st3m_media_load_mpg1(const char *path);
st3m_media *st3m_media_load_mod(const char *path);
st3m_media *st3m_media_load_mp3(const char *path);
st3m_media *st3m_media_load_txt(const char *path);
st3m_media *st3m_media_load_bin(const char *path);
static int file_get_contents(const char *path, uint8_t **contents,
size_t *length) {
FILE *file;
long size;
long remaining;
uint8_t *buffer;
file = fopen(path, "rb");
if (!file) {
return -1;
}
fseek(file, 0, SEEK_END);
size = remaining = ftell(file);
if (length) {
*length = size;
}
rewind(file);
buffer = malloc(size + 2);
if (!buffer) {
fclose(file);
return -1;
}
remaining -= fread(buffer, 1, remaining, file);
if (remaining) {
fclose(file);
free(buffer);
return -1;
}
fclose(file);
*contents = (unsigned char *)buffer;
buffer[size] = 0;
return 0;
}
int st3m_media_load(const char *path) {
struct stat statbuf;
#if 1
if (!strncmp(path, "http://", 7)) {
st3m_media_stop();
audio_media = st3m_media_load_mp3(path);
} else if (stat(path, &statbuf)) {
st3m_media_stop();
audio_media = st3m_media_load_txt(path);
} else if (strstr(path, ".mp3") == strrchr(path, '.')) {
st3m_media_stop();
audio_media = st3m_media_load_mp3(path);
} else
#endif
#if 1
if (strstr(path, ".mpg")) {
st3m_media_stop();
audio_media = st3m_media_load_mpg1(path);
} else
#endif
#if 1
if ((strstr(path, ".mod") == strrchr(path, '.'))) {
st3m_media_stop();
audio_media = st3m_media_load_mod(path);
} else
#endif
if ((strstr(path, ".json") == strrchr(path, '.')) ||
(strstr(path, ".txt") == strrchr(path, '.')) ||
(strstr(path, "/README") == strrchr(path, '/')) ||
(strstr(path, ".toml") == strrchr(path, '.')) ||
(strstr(path, ".py") == strrchr(path, '.'))) {
st3m_media_stop();
audio_media = st3m_media_load_txt(path);
}
if (!audio_media) {
st3m_media_stop();
audio_media = st3m_media_load_txt(path);
}
if (!audio_buffer)
audio_buffer = heap_caps_malloc(AUDIO_BUF_SIZE * 2, MALLOC_CAP_DMA);
st3m_audio_set_player_function(st3m_media_audio_render);
audio_media->audio_buffer = audio_buffer;
audio_media->audio_r = 0;
audio_media->audio_w = 1;
return 1;
}
typedef struct {
st3m_media control;
char *data;
size_t size;
float scroll_pos;
char *path;
} txt_state;
static void txt_destroy(st3m_media *media) {
txt_state *self = (void *)media;
if (self->data) free(self->data);
if (self->path) free(self->path);
free(self);
}
static void txt_draw(st3m_media *media, Ctx *ctx) {
txt_state *self = (void *)media;
ctx_rectangle(ctx, -120, -120, 240, 240);
ctx_gray(ctx, 0);
ctx_fill(ctx);
ctx_gray(ctx, 1.0);
ctx_move_to(ctx, -85, -70);
ctx_font(ctx, "mono");
ctx_font_size(ctx, 13.0);
// ctx_text (ctx, self->path);
ctx_text(ctx, self->data);
}
static void txt_think(st3m_media *media, float ms_elapsed) {
// txt_state *self = (void*)media;
}
st3m_media *st3m_media_load_txt(const char *path) {
txt_state *self = (txt_state *)malloc(sizeof(txt_state));
memset(self, 0, sizeof(txt_state));
self->control.draw = txt_draw;
self->control.think = txt_think;
self->control.destroy = txt_destroy;
file_get_contents(path, (void *)&self->data, &self->size);
if (!self->data) {
self->data = malloc(strlen(path) + 64);
sprintf(self->data, "40x - %s", path);
self->size = strlen((char *)self->data);
}
self->path = strdup(path);
return (void *)self;
}
st3m_media *st3m_media_load_bin(const char *path) {
txt_state *self = (txt_state *)malloc(sizeof(txt_state));
memset(self, 0, sizeof(txt_state));
self->control.draw = txt_draw;
self->control.destroy = txt_destroy;
file_get_contents(path, (void *)&self->data, &self->size);
if (!self->data) {
self->data = malloc(strlen(path) + 64);
sprintf(self->data, "40x - %s", path);
self->size = strlen(self->data);
}
self->path = strdup(path);
return (void *)self;
}
#endif
#pragma once
#include <ctx.h>
#include <stdbool.h>
#include <stdint.h>
#define AUDIO_BUF_SIZE (8192)
typedef struct _st3m_media st3m_media;
struct _st3m_media {
// set a tunable, to a numeric value - available tunables depends on
// decoder
void (*set)(st3m_media *media, const char *key, float value);
// get a property/or tunable, defaulting to -1 for nonexisting keys
float (*get)(st3m_media *media, const char *key);
// get a string property/metadata, NULL if not existing or a string
// to be freed
char *(*get_string)(st3m_media *media, const char *key);
// free resources used by this media instance
void (*destroy)(st3m_media *media);
// draw the current frame / visualization / metadata screen
void (*draw)(st3m_media *media, Ctx *ctx);
// do decoding work corresponding to passed time
void (*think)(st3m_media *media, float ms);
// pointer to global pcm output buffer
int16_t *audio_buffer;
// playback head
int audio_r;
// queuing/writing head
int audio_w;
// Duration of media in seconds or -1 for infinite/streaming media
// at worst approximation of some unit, set by decoder.
float duration;
// currently played back position - set by decoder
float position;
// currently played back position, in seconds - set by decoder
float time; // time might be precise even when duration is not
// until first play through of some media, when time
// duration should also be set exact.
// decoder should seek to this relative if not -1, and set it to -1
float seek;
// if set to 1 playback is momentarily stopped but can be resumed,
// this is toggled by st3m_media_play | st3m_media_pause
int paused;
};
// stops the currently playing media item
void st3m_media_stop(void);
// set a new media item
int st3m_media_load(const char *path_or_uri);
// decode current media item ms ahead (unless paused)
void st3m_media_think(float ms);
// draw the codecs own view of itself / its meta data - progress
// for video or animations formats this should draw the media-content
// other codecs can find mixes of debug visualizations.
void st3m_media_draw(Ctx *ctx);
// controls whether we are playing
void st3m_media_pause(void);
void st3m_media_play(void);
int st3m_media_is_playing(void);
// get duration in seconds
float st3m_media_get_duration(void);
// get current playback time in seconds
float st3m_media_get_time(void);
// get current playback position relative to overall duration
float st3m_media_get_position(void);
// seek to a position relative to overall duration
void st3m_media_seek(float position);
// seek a relative amount of seconds forward or with negative values back
void st3m_media_seek_relative(float seconds_jump);
// get decoder specific string or NULL if not existing, free returned value
// common values:
// "title" "artist"
char *st3m_media_get_string(const char *key);
// get a decoder specific numeric value, defaulting to -1 for nonexisting values
float st3m_media_get(const char *key);
// set a decoder specific floating point value
// example posible/or already used values:
// "scale" 0.0 - 1.0 - how large part of the screen to take up
// "grayscale" 0 or 1 - drop color bits for performance
// "smoothing" 0 or 1 - enable smooth texture scaling
void st3m_media_set(const char *key, float value);
// API for use in implementations
// query how manu audio samples have been queued in the pcm output buffer
int st3m_media_samples_queued(void);
Checks: '-clang-analyzer-core.UndefinedBinaryOperatorResult,-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling, -clang-analyzer-optin.portability.UnixAPI'
idf_component_register(
SRCS
video_mpeg.c
INCLUDE_DIRS
.
../ctx
../st3m
)
/*
PL_MPEG - MPEG1 Video decoder, MP2 Audio decoder, MPEG-PS demuxer
Dominic Szablewski - https://phoboslab.org
-- LICENSE: The MIT License(MIT)
Copyright(c) 2019 Dominic Szablewski
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files(the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-- Synopsis
// Define `PL_MPEG_IMPLEMENTATION` in *one* C/C++ file before including this
// library to create the implementation.
#define PL_MPEG_IMPLEMENTATION
#include "plmpeg.h"
// This function gets called for each decoded video frame
void my_video_callback(plm_t *plm, plm_frame_t *frame, void *user) {
// Do something with frame->y.data, frame->cr.data, frame->cb.data
}
// This function gets called for each decoded audio frame
void my_audio_callback(plm_t *plm, plm_samples_t *frame, void *user) {
// Do something with samples->interleaved
}
// Load a .mpg (MPEG Program Stream) file
plm_t *plm = plm_create_with_filename("some-file.mpg");
// Install the video & audio decode callbacks
plm_set_video_decode_callback(plm, my_video_callback, my_data);
plm_set_audio_decode_callback(plm, my_audio_callback, my_data);
// Decode
do {
plm_decode(plm, time_since_last_call);
} while (!plm_has_ended(plm));
// All done
plm_destroy(plm);
-- Documentation
This library provides several interfaces to load, demux and decode MPEG video
and audio data. A high-level API combines the demuxer, video & audio decoders
in an easy to use wrapper.
Lower-level APIs for accessing the demuxer, video decoder and audio decoder,
as well as providing different data sources are also available.
Interfaces are written in an object 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
With the high-level interface you have two options to decode video & audio:
1. Use plm_decode() and just hand over the delta time since the last call.
It will decode everything needed and call your callbacks (specified through
plm_set_{video|audio}_decode_callback()) any number of times.
2. Use plm_decode_video() and plm_decode_audio() to decode exactly one
frame of video or audio data at a time. How you handle the synchronization
of both streams is up to you.
If you only want to decode video *or* audio through these functions, you should
disable the other stream (plm_set_{video|audio}_enabled(FALSE))
Video data is decoded into a struct with all 3 planes (Y, Cr, Cb) stored in
separate buffers. You can either convert this to RGB on the CPU (slow) via the
plm_frame_to_rgb() function or do it on the GPU with the following matrix:
mat4 bt601 = mat4(
1.16438, 0.00000, 1.59603, -0.87079,
1.16438, -0.39176, -0.81297, 0.52959,
1.16438, 2.01723, 0.00000, -1.08139,
0, 0, 0, 1
);
gl_FragColor = vec4(y, cb, cr, 1.0) * bt601;
Audio data is decoded into a struct with either one single float array with the
samples for the left and right channel interleaved, or if the
PLM_AUDIO_SEPARATE_CHANNELS is defined *before* including this library, into
two separate float arrays - one for each channel.
Data can be supplied to the high level interface, the demuxer and the decoders
in three different ways:
1. Using plm_create_from_filename() or with a file handle with
plm_create_from_file().
2. Using plm_create_with_memory() and supplying a pointer to memory that
contains the whole file.
3. Using plm_create_with_buffer(), supplying your own plm_buffer_t instance and
periodically writing to this buffer.
When using your own plm_buffer_t instance, you can fill this buffer using
plm_buffer_write(). You can either monitor plm_buffer_get_remaining() and push
data when appropriate, or install a callback on the buffer with
plm_buffer_set_load_callback() that gets called whenever the buffer needs more
data.
A buffer created with plm_buffer_create_with_capacity() is treated as a ring
buffer, meaning that data that has already been read, will be discarded. In
contrast, a buffer created with plm_buffer_create_for_appending() will keep all
data written to it in memory. This enables seeking in the already loaded data.
There should be no need to use the lower level plm_demux_*, plm_video_* and
plm_audio_* functions, if all you want to do is read/decode an MPEG-PS file.
However, if you get raw mpeg1video data or raw mp2 audio data from a different
source, these functions can be used to decode the raw data directly. Similarly,
if you only want to analyze an MPEG-PS file or extract raw video or audio
packets from it, you can use the plm_demux_* functions.
This library uses malloc(), realloc() and free() to manage memory. Typically
all allocation happens up-front when creating the interface. However, the
default buffer size may be too small for certain inputs. In these cases plmpeg
will realloc() the buffer with a larger size whenever needed. You can configure
the default buffer size by defining PLM_BUFFER_DEFAULT_SIZE *before*
including this library.
See below for detailed the API documentation.
*/
#ifndef PL_MPEG_H
#define PL_MPEG_H
#include <stdint.h>
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
// -----------------------------------------------------------------------------
// Public Data Types
// Object types for the various interfaces
typedef struct plm_t plm_t;
typedef struct plm_buffer_t plm_buffer_t;
typedef struct plm_demux_t plm_demux_t;
typedef struct plm_video_t plm_video_t;
typedef struct plm_audio_t plm_audio_t;
// Demuxed MPEG PS packet
// The type maps directly to the various MPEG-PES start codes. PTS is the
// presentation time stamp of the packet in seconds. Note that not all packets
// have a PTS value, indicated by PLM_PACKET_INVALID_TS.
#define PLM_PACKET_INVALID_TS -1
typedef struct {
int type;
float pts;
size_t length;
uint8_t *data;
} plm_packet_t;
// Decoded Video Plane
// The byte length of the data is width * height. Note that different planes
// have different sizes: the Luma plane (Y) is double the size of each of
// the two Chroma planes (Cr, Cb) - i.e. 4 times the byte length.
// Also note that the size of the plane does *not* denote the size of the
// displayed frame. The sizes of planes are always rounded up to the nearest
// macroblock (16px).
typedef struct {
unsigned int width;
unsigned int height;
uint8_t *data;
} plm_plane_t;
// Decoded Video Frame
// width and height denote the desired display size of the frame. This may be
// different from the internal size of the 3 planes.
typedef struct {
float time;
unsigned int width;
unsigned int height;
plm_plane_t y;
plm_plane_t cr;
plm_plane_t cb;
} plm_frame_t;
// Callback function type for decoded video frames used by the high-level
// plm_* interface
typedef void (*plm_video_decode_callback)(plm_t *self, plm_frame_t *frame,
void *user);
// Decoded Audio Samples
// Samples are stored as normalized (-1, 1) float either interleaved, or if
// PLM_AUDIO_SEPARATE_CHANNELS is defined, in two separate arrays.
// The `count` is always PLM_AUDIO_SAMPLES_PER_FRAME and just there for
// convenience.
#define PLM_AUDIO_SAMPLES_PER_FRAME 1152
typedef struct {
float time;
unsigned int count;
#ifdef PLM_AUDIO_SEPARATE_CHANNELS
float left[PLM_AUDIO_SAMPLES_PER_FRAME];
float right[PLM_AUDIO_SAMPLES_PER_FRAME];
#else
float interleaved[PLM_AUDIO_SAMPLES_PER_FRAME * 2];
#endif
} plm_samples_t;
// Callback function type for decoded audio samples used by the high-level
// plm_* interface
typedef void (*plm_audio_decode_callback)(plm_t *self, plm_samples_t *samples,
void *user);
// Callback function for plm_buffer when it needs more data
typedef void (*plm_buffer_load_callback)(plm_buffer_t *self, void *user);
// -----------------------------------------------------------------------------
// plm_* public API
// High-Level API for loading/demuxing/decoding MPEG-PS data
// Create a plmpeg instance with a filename. Returns NULL if the file could not
// be opened.
plm_t *plm_create_with_filename(const char *filename);
// Create a plmpeg instance with a file handle. Pass TRUE to close_when_done to
// let plmpeg call fclose() on the handle when plm_destroy() is called.
plm_t *plm_create_with_file(FILE *fh, int close_when_done);
// Create a plmpeg instance with a pointer to memory as source. This assumes the
// whole file is in memory. The memory is not copied. Pass TRUE to
// free_when_done to let plmpeg call free() on the pointer when plm_destroy()
// is called.
plm_t *plm_create_with_memory(uint8_t *bytes, size_t length,
int free_when_done);
// Create a plmpeg instance with a plm_buffer as source. Pass TRUE to
// destroy_when_done to let plmpeg call plm_buffer_destroy() on the buffer when
// plm_destroy() is called.
plm_t *plm_create_with_buffer(plm_buffer_t *buffer, int destroy_when_done);
// Destroy a plmpeg instance and free all data.
void plm_destroy(plm_t *self);
// Get whether we have headers on all available streams and we can accurately
// report the number of video/audio streams, video dimensions, framerate and
// audio samplerate.
// This returns FALSE if the file is not an MPEG-PS file or - when not using a
// file as source - when not enough data is available yet.
int plm_has_headers(plm_t *self);
// Get or set whether video decoding is enabled. Default TRUE.
int plm_get_video_enabled(plm_t *self);
void plm_set_video_enabled(plm_t *self, int enabled);
// Get the number of video streams (0--1) reported in the system header.
int plm_get_num_video_streams(plm_t *self);
// Get the display width/height of the video stream.
int plm_get_width(plm_t *self);
int plm_get_height(plm_t *self);
// Get the framerate of the video stream in frames per second.
float plm_get_framerate(plm_t *self);
// Get or set whether audio decoding is enabled. Default TRUE.
int plm_get_audio_enabled(plm_t *self);
void plm_set_audio_enabled(plm_t *self, int enabled);
// Get the number of audio streams (0--4) reported in the system header.
int plm_get_num_audio_streams(plm_t *self);
// Set the desired audio stream (0--3). Default 0.
void plm_set_audio_stream(plm_t *self, int stream_index);
// Get the samplerate of the audio stream in samples per second.
int plm_get_samplerate(plm_t *self);
// Get or set the audio lead time in seconds - the time in which audio samples
// are decoded in advance (or behind) the video decode time. Typically this
// should be set to the duration of the buffer of the audio API that you use
// for output. E.g. for SDL2: (SDL_AudioSpec.samples / samplerate)
float plm_get_audio_lead_time(plm_t *self);
void plm_set_audio_lead_time(plm_t *self, float lead_time);
// Get the current internal time in seconds.
float plm_get_time(plm_t *self);
// Get the video duration of the underlying source in seconds.
float plm_get_duration(plm_t *self);
// Rewind all buffers back to the beginning.
void plm_rewind(plm_t *self);
// Get or set looping. Default FALSE.
int plm_get_loop(plm_t *self);
void plm_set_loop(plm_t *self, int loop);
// Get whether the file has ended. If looping is enabled, this will always
// return FALSE.
int plm_has_ended(plm_t *self);
// Set the callback for decoded video frames used with plm_decode(). If no
// callback is set, video data will be ignored and not be decoded. The *user
// Parameter will be passed to your callback.
void plm_set_video_decode_callback(plm_t *self, plm_video_decode_callback fp,
void *user);
// Set the callback for decoded audio samples used with plm_decode(). If no
// callback is set, audio data will be ignored and not be decoded. The *user
// Parameter will be passed to your callback.
void plm_set_audio_decode_callback(plm_t *self, plm_audio_decode_callback fp,
void *user);
// Advance the internal timer by seconds and decode video/audio up to this time.
// This will call the video_decode_callback and audio_decode_callback any number
// of times. A frame-skip is not implemented, i.e. everything up to current time
// will be decoded.
void plm_decode(plm_t *self, float seconds);
// Decode and return one video frame. Returns NULL if no frame could be decoded
// (either because the source ended or data is corrupt). If you only want to
// decode video, you should disable audio via plm_set_audio_enabled().
// The returned plm_frame_t is valid until the next call to plm_decode_video()
// or until plm_destroy() is called.
plm_frame_t *plm_decode_video(plm_t *self);
// Decode and return one audio frame. Returns NULL if no frame could be decoded
// (either because the source ended or data is corrupt). If you only want to
// decode audio, you should disable video via plm_set_video_enabled().
// The returned plm_samples_t is valid until the next call to plm_decode_audio()
// or until plm_destroy() is called.
plm_samples_t *plm_decode_audio(plm_t *self);
// Seek to the specified time, clamped between 0 -- duration. This can only be
// used when the underlying plm_buffer is seekable, i.e. for files, fixed
// memory buffers or _for_appending buffers.
// If seek_exact is TRUE this will seek to the exact time, otherwise it will
// seek to the last intra frame just before the desired time. Exact seeking can
// be slow, because all frames up to the seeked one have to be decoded on top of
// the previous intra frame.
// If seeking succeeds, this function will call the video_decode_callback
// exactly once with the target frame. If audio is enabled, it will also call
// the audio_decode_callback any number of times, until the audio_lead_time is
// satisfied.
// Returns TRUE if seeking succeeded or FALSE if no frame could be found.
int plm_seek(plm_t *self, float time, int seek_exact);
// Similar to plm_seek(), but will not call the video_decode_callback,
// audio_decode_callback or make any attempts to sync audio.
// Returns the found frame or NULL if no frame could be found.
plm_frame_t *plm_seek_frame(plm_t *self, float time, int seek_exact);
// -----------------------------------------------------------------------------
// plm_buffer public API
// Provides the data source for all other plm_* interfaces
// The default size for buffers created from files or by the high-level API
#ifndef PLM_BUFFER_DEFAULT_SIZE
#define PLM_BUFFER_DEFAULT_SIZE (64 * 1024)
#endif
// Create a buffer instance with a filename. Returns NULL if the file could not
// be opened.
plm_buffer_t *plm_buffer_create_with_filename(const char *filename);
// Create a buffer instance with a file handle. Pass TRUE to close_when_done
// to let plmpeg call fclose() on the handle when plm_destroy() is called.
plm_buffer_t *plm_buffer_create_with_file(FILE *fh, int close_when_done);
// Create a buffer instance with a pointer to memory as source. This assumes
// the whole file is in memory. The bytes are not copied. Pass 1 to
// free_when_done to let plmpeg call free() on the pointer when plm_destroy()
// is called.
plm_buffer_t *plm_buffer_create_with_memory(uint8_t *bytes, size_t length,
int free_when_done);
// Create an empty buffer with an initial capacity. The buffer will grow
// as needed. Data that has already been read, will be discarded.
plm_buffer_t *plm_buffer_create_with_capacity(size_t capacity);
// Create an empty buffer with an initial capacity. The buffer will grow
// as needed. Decoded data will *not* be discarded. This can be used when
// loading a file over the network, without needing to throttle the download.
// It also allows for seeking in the already loaded data.
plm_buffer_t *plm_buffer_create_for_appending(size_t initial_capacity);
// Destroy a buffer instance and free all data
void plm_buffer_destroy(plm_buffer_t *self);
// Copy data into the buffer. If the data to be written is larger than the
// available space, the buffer will realloc() with a larger capacity.
// Returns the number of bytes written. This will always be the same as the
// passed in length, except when the buffer was created _with_memory() for
// which _write() is forbidden.
size_t plm_buffer_write(plm_buffer_t *self, uint8_t *bytes, size_t length);
// Mark the current byte length as the end of this buffer and signal that no
// more data is expected to be written to it. This function should be called
// just after the last plm_buffer_write().
// For _with_capacity buffers, this is cleared on a plm_buffer_rewind().
void plm_buffer_signal_end(plm_buffer_t *self);
// Set a callback that is called whenever the buffer needs more data
void plm_buffer_set_load_callback(plm_buffer_t *self,
plm_buffer_load_callback fp, void *user);
// Rewind the buffer back to the beginning. When loading from a file handle,
// this also seeks to the beginning of the file.
void plm_buffer_rewind(plm_buffer_t *self);
// Get the total size. For files, this returns the file size. For all other
// types it returns the number of bytes currently in the buffer.
size_t plm_buffer_get_size(plm_buffer_t *self);
// Get the number of remaining (yet unread) bytes in the buffer. This can be
// useful to throttle writing.
size_t plm_buffer_get_remaining(plm_buffer_t *self);
// Get whether the read position of the buffer is at the end and no more data
// is expected.
int plm_buffer_has_ended(plm_buffer_t *self);
// -----------------------------------------------------------------------------
// plm_demux public API
// Demux an MPEG Program Stream (PS) data into separate packages
// Various Packet Types
static const int PLM_DEMUX_PACKET_PRIVATE = 0xBD;
static const int PLM_DEMUX_PACKET_AUDIO_1 = 0xC0;
static const int PLM_DEMUX_PACKET_AUDIO_2 = 0xC1;
static const int PLM_DEMUX_PACKET_AUDIO_3 = 0xC2;
static const int PLM_DEMUX_PACKET_AUDIO_4 = 0xC2;
static const int PLM_DEMUX_PACKET_VIDEO_1 = 0xE0;
// Create a demuxer with a plm_buffer as source. This will also attempt to read
// the pack and system headers from the buffer.
plm_demux_t *plm_demux_create(plm_buffer_t *buffer, int destroy_when_done);
// Destroy a demuxer and free all data.
void plm_demux_destroy(plm_demux_t *self);
// Returns TRUE/FALSE whether pack and system headers have been found. This will
// attempt to read the headers if non are present yet.
int plm_demux_has_headers(plm_demux_t *self);
// Returns the number of video streams found in the system header. This will
// attempt to read the system header if non is present yet.
int plm_demux_get_num_video_streams(plm_demux_t *self);
// Returns the number of audio streams found in the system header. This will
// attempt to read the system header if non is present yet.
int plm_demux_get_num_audio_streams(plm_demux_t *self);
// Rewind the internal buffer. See plm_buffer_rewind().
void plm_demux_rewind(plm_demux_t *self);
// Get whether the file has ended. This will be cleared on seeking or rewind.
int plm_demux_has_ended(plm_demux_t *self);
// Seek to a packet of the specified type with a PTS just before specified time.
// If force_intra is TRUE, only packets containing an intra frame will be
// considered - this only makes sense when the type is PLM_DEMUX_PACKET_VIDEO_1.
// Note that the specified time is considered 0-based, regardless of the first
// PTS in the data source.
plm_packet_t *plm_demux_seek(plm_demux_t *self, float time, int type,
int force_intra);
// Get the PTS of the first packet of this type. Returns PLM_PACKET_INVALID_TS
// if not packet of this packet type can be found.
float plm_demux_get_start_time(plm_demux_t *self, int type);
// Get the duration for the specified packet type - i.e. the span between the
// the first PTS and the last PTS in the data source. This only makes sense when
// the underlying data source is a file or fixed memory.
float plm_demux_get_duration(plm_demux_t *self, int type);
// Decode and return the next packet. The returned packet_t is valid until
// the next call to plm_demux_decode() or until the demuxer is destroyed.
plm_packet_t *plm_demux_decode(plm_demux_t *self);
// -----------------------------------------------------------------------------
// plm_video public API
// Decode MPEG1 Video ("mpeg1") data into raw YCrCb frames
// Create a video decoder with a plm_buffer as source.
plm_video_t *plm_video_create_with_buffer(plm_buffer_t *buffer,
int destroy_when_done);
// Destroy a video decoder and free all data.
void plm_video_destroy(plm_video_t *self);
// Get whether a sequence header was found and we can accurately report on
// dimensions and framerate.
int plm_video_has_header(plm_video_t *self);
// Get the framerate in frames per second.
float plm_video_get_framerate(plm_video_t *self);
// Get the display width/height.
int plm_video_get_width(plm_video_t *self);
int plm_video_get_height(plm_video_t *self);
// Set "no delay" mode. When enabled, the decoder assumes that the video does
// *not* contain any B-Frames. This is useful for reducing lag when streaming.
// The default is FALSE.
void plm_video_set_no_delay(plm_video_t *self, int no_delay);
// Get the current internal time in seconds.
float plm_video_get_time(plm_video_t *self);
// Set the current internal time in seconds. This is only useful when you
// manipulate the underlying video buffer and want to enforce a correct
// timestamps.
void plm_video_set_time(plm_video_t *self, float time);
// Rewind the internal buffer. See plm_buffer_rewind().
void plm_video_rewind(plm_video_t *self);
// Get whether the file has ended. This will be cleared on rewind.
int plm_video_has_ended(plm_video_t *self);
// Decode and return one frame of video and advance the internal time by
// 1/framerate seconds. The returned frame_t is valid until the next call of
// plm_video_decode() or until the video decoder is destroyed.
plm_frame_t *plm_video_decode(plm_video_t *self);
// Convert the YCrCb data of a frame into interleaved R G B data. The stride
// specifies the width in bytes of the destination buffer. I.e. the number of
// bytes from one line to the next. The stride must be at least
// (frame->width * bytes_per_pixel). The buffer pointed to by *dest must have a
// size of at least (stride * frame->height).
// Note that the alpha component of the dest buffer is always left untouched.
void plm_frame_to_rgb(plm_frame_t *frame, uint8_t *dest, int stride);
void plm_frame_to_bgr(plm_frame_t *frame, uint8_t *dest, int stride);
void plm_frame_to_rgba(plm_frame_t *frame, uint8_t *dest, int stride);
void plm_frame_to_bgra(plm_frame_t *frame, uint8_t *dest, int stride);
void plm_frame_to_argb(plm_frame_t *frame, uint8_t *dest, int stride);
void plm_frame_to_abgr(plm_frame_t *frame, uint8_t *dest, int stride);
// -----------------------------------------------------------------------------
// plm_audio public API
// Decode MPEG-1 Audio Layer II ("mp2") data into raw samples
// Create an audio decoder with a plm_buffer as source.
plm_audio_t *plm_audio_create_with_buffer(plm_buffer_t *buffer,
int destroy_when_done);
// Destroy an audio decoder and free all data.
void plm_audio_destroy(plm_audio_t *self);
// Get whether a frame header was found and we can accurately report on
// samplerate.
int plm_audio_has_header(plm_audio_t *self);
// Get the samplerate in samples per second.
int plm_audio_get_samplerate(plm_audio_t *self);
// Get the current internal time in seconds.
float plm_audio_get_time(plm_audio_t *self);
// Set the current internal time in seconds. This is only useful when you
// manipulate the underlying video buffer and want to enforce a correct
// timestamps.
void plm_audio_set_time(plm_audio_t *self, float time);
// Rewind the internal buffer. See plm_buffer_rewind().
void plm_audio_rewind(plm_audio_t *self);
// Get whether the file has ended. This will be cleared on rewind.
int plm_audio_has_ended(plm_audio_t *self);
// Decode and return one "frame" of audio and advance the internal time by
// (PLM_AUDIO_SAMPLES_PER_FRAME/samplerate) seconds. The returned samples_t
// is valid until the next call of plm_audio_decode() or until the audio
// decoder is destroyed.
plm_samples_t *plm_audio_decode(plm_audio_t *self);
#ifdef __cplusplus
}
#endif
#endif // PL_MPEG_H
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// IMPLEMENTATION
#ifdef PL_MPEG_IMPLEMENTATION
#include <stdlib.h>
#include <string.h>
#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif
#define PLM_UNUSED(expr) (void)(expr)
// -----------------------------------------------------------------------------
// plm (high-level interface) implementation
typedef struct plm_t {
plm_demux_t *demux;
float time;
int has_ended;
int loop;
int has_decoders;
int video_enabled;
int video_packet_type;
plm_buffer_t *video_buffer;
plm_video_t *video_decoder;
int audio_enabled;
int audio_stream_index;
int audio_packet_type;
float 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;
int plm_init_decoders(plm_t *self);
void plm_handle_end(plm_t *self);
void plm_read_video_packet(plm_buffer_t *buffer, void *user);
void plm_read_audio_packet(plm_buffer_t *buffer, void *user);
void plm_read_packets(plm_t *self, int requested_type);
plm_t *plm_create_with_filename(const char *filename) {
plm_buffer_t *buffer = plm_buffer_create_with_filename(filename);
if (!buffer) {
return NULL;
}
return plm_create_with_buffer(buffer, TRUE);
}
plm_t *plm_create_with_file(FILE *fh, int close_when_done) {
plm_buffer_t *buffer = plm_buffer_create_with_file(fh, close_when_done);
return plm_create_with_buffer(buffer, TRUE);
}
plm_t *plm_create_with_memory(uint8_t *bytes, size_t length,
int free_when_done) {
plm_buffer_t *buffer =
plm_buffer_create_with_memory(bytes, length, free_when_done);
return plm_create_with_buffer(buffer, TRUE);
}
plm_t *plm_create_with_buffer(plm_buffer_t *buffer, int destroy_when_done) {
plm_t *self = (plm_t *)malloc(sizeof(plm_t));
memset(self, 0, sizeof(plm_t));
self->demux = plm_demux_create(buffer, destroy_when_done);
self->video_enabled = TRUE;
self->audio_enabled = TRUE;
plm_init_decoders(self);
return self;
}
int plm_init_decoders(plm_t *self) {
if (self->has_decoders) {
return TRUE;
}
if (!plm_demux_has_headers(self->demux)) {
return FALSE;
}
if (plm_demux_get_num_video_streams(self->demux) > 0) {
if (self->video_enabled) {
self->video_packet_type = PLM_DEMUX_PACKET_VIDEO_1;
}
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);
}
if (plm_demux_get_num_audio_streams(self->demux) > 0) {
if (self->audio_enabled) {
self->audio_packet_type =
PLM_DEMUX_PACKET_AUDIO_1 + self->audio_stream_index;
}
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);
}
if (self->video_buffer) {
self->video_decoder =
plm_video_create_with_buffer(self->video_buffer, TRUE);
}
if (self->audio_buffer) {
self->audio_decoder =
plm_audio_create_with_buffer(self->audio_buffer, TRUE);
}
self->has_decoders = TRUE;
return TRUE;
}
void plm_destroy(plm_t *self) {
if (self->video_decoder) {
plm_video_destroy(self->video_decoder);
}
if (self->audio_decoder) {
plm_audio_destroy(self->audio_decoder);
}
plm_demux_destroy(self->demux);
free(self);
}
int plm_get_audio_enabled(plm_t *self) { return self->audio_enabled; }
int plm_has_headers(plm_t *self) {
if (!plm_demux_has_headers(self->demux)) {
return FALSE;
}
if (!plm_init_decoders(self)) {
return FALSE;
}
if ((self->video_decoder && !plm_video_has_header(self->video_decoder)) ||
(self->audio_decoder && !plm_audio_has_header(self->audio_decoder))) {
return FALSE;
}
return TRUE;
}
void plm_set_audio_enabled(plm_t *self, int enabled) {
self->audio_enabled = enabled;
if (!enabled) {
self->audio_packet_type = 0;
return;
}
self->audio_packet_type =
(plm_init_decoders(self) && self->audio_decoder)
? PLM_DEMUX_PACKET_AUDIO_1 + self->audio_stream_index
: 0;
}
void plm_set_audio_stream(plm_t *self, int stream_index) {
if (stream_index < 0 || stream_index > 3) {
return;
}
self->audio_stream_index = stream_index;
// Set the correct audio_packet_type
plm_set_audio_enabled(self, self->audio_enabled);
}
int plm_get_video_enabled(plm_t *self) { return self->video_enabled; }
void plm_set_video_enabled(plm_t *self, int enabled) {
self->video_enabled = enabled;
if (!enabled) {
self->video_packet_type = 0;
return;
}
self->video_packet_type = (plm_init_decoders(self) && self->video_decoder)
? PLM_DEMUX_PACKET_VIDEO_1
: 0;
}
int plm_get_num_video_streams(plm_t *self) {
return plm_demux_get_num_video_streams(self->demux);
}
int plm_get_width(plm_t *self) {
return (plm_init_decoders(self) && self->video_decoder)
? plm_video_get_width(self->video_decoder)
: 0;
}
int plm_get_height(plm_t *self) {
return (plm_init_decoders(self) && self->video_decoder)
? plm_video_get_height(self->video_decoder)
: 0;
}
float plm_get_framerate(plm_t *self) {
return (plm_init_decoders(self) && self->video_decoder)
? plm_video_get_framerate(self->video_decoder)
: 0;
}
int plm_get_num_audio_streams(plm_t *self) {
return plm_demux_get_num_audio_streams(self->demux);
}
int plm_get_samplerate(plm_t *self) {
return (plm_init_decoders(self) && self->audio_decoder)
? plm_audio_get_samplerate(self->audio_decoder)
: 0;
}
float plm_get_audio_lead_time(plm_t *self) { return self->audio_lead_time; }
void plm_set_audio_lead_time(plm_t *self, float lead_time) {
self->audio_lead_time = lead_time;
}
float plm_get_time(plm_t *self) { return self->time; }
float plm_get_duration(plm_t *self) {
return plm_demux_get_duration(self->demux, PLM_DEMUX_PACKET_VIDEO_1);
}
void plm_rewind(plm_t *self) {
if (self->video_decoder) {
plm_video_rewind(self->video_decoder);
}
if (self->audio_decoder) {
plm_audio_rewind(self->audio_decoder);
}
plm_demux_rewind(self->demux);
self->time = 0;
}
int plm_get_loop(plm_t *self) { return self->loop; }
void plm_set_loop(plm_t *self, int loop) { self->loop = loop; }
int plm_has_ended(plm_t *self) { return self->has_ended; }
void plm_set_video_decode_callback(plm_t *self, plm_video_decode_callback fp,
void *user) {
self->video_decode_callback = fp;
self->video_decode_callback_user_data = user;
}
void plm_set_audio_decode_callback(plm_t *self, plm_audio_decode_callback fp,
void *user) {
self->audio_decode_callback = fp;
self->audio_decode_callback_user_data = user;
}
void plm_decode(plm_t *self, float tick) {
if (!plm_init_decoders(self)) {
return;
}
int decode_video = (self->video_decode_callback && self->video_packet_type);
int decode_audio = (self->audio_decode_callback && self->audio_packet_type);
if (!decode_video && !decode_audio) {
// Nothing to do here
return;
}
int did_decode = FALSE;
int decode_video_failed = FALSE;
int decode_audio_failed = FALSE;
float video_target_time = self->time + tick;
float audio_target_time = self->time + tick + self->audio_lead_time;
do {
did_decode = FALSE;
if (decode_video &&
plm_video_get_time(self->video_decoder) < video_target_time) {
plm_frame_t *frame = plm_video_decode(self->video_decoder);
if (frame) {
self->video_decode_callback(
self, frame, self->video_decode_callback_user_data);
did_decode = TRUE;
} else {
decode_video_failed = TRUE;
}
}
if (decode_audio &&
plm_audio_get_time(self->audio_decoder) < audio_target_time) {
plm_samples_t *samples = plm_audio_decode(self->audio_decoder);
if (samples) {
self->audio_decode_callback(
self, samples, self->audio_decode_callback_user_data);
did_decode = TRUE;
} else {
decode_audio_failed = TRUE;
}
}
} while (did_decode);
// Did all sources we wanted to decode fail and the demuxer is at the end?
if ((!decode_video || decode_video_failed) &&
(!decode_audio || decode_audio_failed) &&
plm_demux_has_ended(self->demux)) {
plm_handle_end(self);
return;
}
self->time += tick;
}
plm_frame_t *plm_decode_video(plm_t *self) {
if (!plm_init_decoders(self)) {
return NULL;
}
if (!self->video_packet_type) {
return NULL;
}
plm_frame_t *frame = plm_video_decode(self->video_decoder);
if (frame) {
self->time = frame->time;
} else if (plm_demux_has_ended(self->demux)) {
plm_handle_end(self);
}
return frame;
}
plm_samples_t *plm_decode_audio(plm_t *self) {
if (!plm_init_decoders(self)) {
return NULL;
}
if (!self->audio_packet_type) {
return NULL;
}
plm_samples_t *samples = plm_audio_decode(self->audio_decoder);
if (samples) {
self->time = samples->time;
} else if (plm_demux_has_ended(self->demux)) {
plm_handle_end(self);
}
return samples;
}
void plm_handle_end(plm_t *self) {
if (self->loop) {
plm_rewind(self);
} else {
self->has_ended = TRUE;
}
}
void plm_read_video_packet(plm_buffer_t *buffer, void *user) {
PLM_UNUSED(buffer);
plm_t *self = (plm_t *)user;
plm_read_packets(self, self->video_packet_type);
}
void plm_read_audio_packet(plm_buffer_t *buffer, void *user) {
PLM_UNUSED(buffer);
plm_t *self = (plm_t *)user;
plm_read_packets(self, self->audio_packet_type);
}
void plm_read_packets(plm_t *self, int requested_type) {
plm_packet_t *packet;
while ((packet = plm_demux_decode(self->demux))) {
if (packet->type == self->video_packet_type) {
plm_buffer_write(self->video_buffer, packet->data, packet->length);
} else if (packet->type == self->audio_packet_type) {
plm_buffer_write(self->audio_buffer, packet->data, packet->length);
}
if (packet->type == requested_type) {
return;
}
}
if (plm_demux_has_ended(self->demux)) {
if (self->video_buffer) {
plm_buffer_signal_end(self->video_buffer);
}
if (self->audio_buffer) {
plm_buffer_signal_end(self->audio_buffer);
}
}
}
plm_frame_t *plm_seek_frame(plm_t *self, float time, int seek_exact) {
if (!plm_init_decoders(self)) {
return NULL;
}
if (!self->video_packet_type) {
return NULL;
}
int type = self->video_packet_type;
float start_time = plm_demux_get_start_time(self->demux, type);
float duration = plm_demux_get_duration(self->demux, type);
if (time < 0) {
time = 0;
} else if (time > duration) {
time = duration;
}
plm_packet_t *packet = plm_demux_seek(self->demux, time, type, TRUE);
if (!packet) {
return NULL;
}
// Disable writing to the audio buffer while decoding video
int previous_audio_packet_type = self->audio_packet_type;
self->audio_packet_type = 0;
// Clear video buffer and decode the found packet
plm_video_rewind(self->video_decoder);
plm_video_set_time(self->video_decoder, packet->pts - start_time);
plm_buffer_write(self->video_buffer, packet->data, packet->length);
plm_frame_t *frame = plm_video_decode(self->video_decoder);
// If we want to seek to an exact frame, we have to decode all frames
// on top of the intra frame we just jumped to.
if (seek_exact) {
while (frame && frame->time < time) {
frame = plm_video_decode(self->video_decoder);
}
}
// Enable writing to the audio buffer again?
self->audio_packet_type = previous_audio_packet_type;
if (frame) {
self->time = frame->time;
}
self->has_ended = FALSE;
return frame;
}
int plm_seek(plm_t *self, float time, int seek_exact) {
plm_frame_t *frame = plm_seek_frame(self, time, seek_exact);
if (!frame) {
return FALSE;
}
if (self->video_decode_callback) {
self->video_decode_callback(self, frame,
self->video_decode_callback_user_data);
}
// If audio is not enabled we are done here.
if (!self->audio_packet_type) {
return TRUE;
}
// Sync up Audio. This demuxes more packets until the first audio packet
// with a PTS greater than the current time is found. plm_decode() is then
// called to decode enough audio data to satisfy the audio_lead_time.
float start_time =
plm_demux_get_start_time(self->demux, self->video_packet_type);
plm_audio_rewind(self->audio_decoder);
plm_packet_t *packet = NULL;
while ((packet = plm_demux_decode(self->demux))) {
if (packet->type == self->video_packet_type) {
plm_buffer_write(self->video_buffer, packet->data, packet->length);
} else if (packet->type == self->audio_packet_type &&
packet->pts - start_time > self->time) {
plm_audio_set_time(self->audio_decoder, packet->pts - start_time);
plm_buffer_write(self->audio_buffer, packet->data, packet->length);
plm_decode(self, 0);
break;
}
}
return TRUE;
}
// -----------------------------------------------------------------------------
// plm_buffer implementation
enum plm_buffer_mode {
PLM_BUFFER_MODE_FILE,
PLM_BUFFER_MODE_FIXED_MEM,
PLM_BUFFER_MODE_RING,
PLM_BUFFER_MODE_APPEND
};
typedef struct plm_buffer_t {
size_t bit_index;
size_t capacity;
size_t length;
size_t total_size;
int discard_read_bytes;
int has_ended;
int free_when_done;
int close_when_done;
FILE *fh;
plm_buffer_load_callback load_callback;
void *load_callback_user_data;
uint8_t *bytes;
enum plm_buffer_mode mode;
} 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;
void plm_buffer_seek(plm_buffer_t *self, size_t pos);
size_t plm_buffer_tell(plm_buffer_t *self);
void plm_buffer_discard_read_bytes(plm_buffer_t *self);
void plm_buffer_load_file_callback(plm_buffer_t *self, void *user);
int plm_buffer_has(plm_buffer_t *self, size_t count);
int plm_buffer_read(plm_buffer_t *self, int count);
void plm_buffer_align(plm_buffer_t *self);
void plm_buffer_skip(plm_buffer_t *self, size_t count);
int plm_buffer_skip_bytes(plm_buffer_t *self, uint8_t v);
int plm_buffer_next_start_code(plm_buffer_t *self);
int plm_buffer_find_start_code(plm_buffer_t *self, int code);
int plm_buffer_no_start_code(plm_buffer_t *self);
int16_t plm_buffer_read_vlc(plm_buffer_t *self, const plm_vlc_t *table);
uint16_t plm_buffer_read_vlc_uint(plm_buffer_t *self,
const plm_vlc_uint_t *table);
plm_buffer_t *plm_buffer_create_with_filename(const char *filename) {
FILE *fh = fopen(filename, "rb");
if (!fh) {
return NULL;
}
return plm_buffer_create_with_file(fh, TRUE);
}
plm_buffer_t *plm_buffer_create_with_file(FILE *fh, int close_when_done) {
plm_buffer_t *self =
plm_buffer_create_with_capacity(PLM_BUFFER_DEFAULT_SIZE);
self->fh = fh;
self->close_when_done = close_when_done;
self->mode = PLM_BUFFER_MODE_FILE;
self->discard_read_bytes = TRUE;
fseek(self->fh, 0, SEEK_END);
self->total_size = ftell(self->fh);
fseek(self->fh, 0, SEEK_SET);
plm_buffer_set_load_callback(self, plm_buffer_load_file_callback, NULL);
return self;
}
plm_buffer_t *plm_buffer_create_with_memory(uint8_t *bytes, size_t length,
int free_when_done) {
plm_buffer_t *self = (plm_buffer_t *)malloc(sizeof(plm_buffer_t));
memset(self, 0, sizeof(plm_buffer_t));
self->capacity = length;
self->length = length;
self->total_size = length;
self->free_when_done = free_when_done;
self->bytes = bytes;
self->mode = PLM_BUFFER_MODE_FIXED_MEM;
self->discard_read_bytes = FALSE;
return self;
}
plm_buffer_t *plm_buffer_create_with_capacity(size_t capacity) {
plm_buffer_t *self = (plm_buffer_t *)malloc(sizeof(plm_buffer_t));
memset(self, 0, sizeof(plm_buffer_t));
self->capacity = capacity;
self->free_when_done = TRUE;
self->bytes = (uint8_t *)malloc(capacity);
self->mode = PLM_BUFFER_MODE_RING;
self->discard_read_bytes = TRUE;
return self;
}
plm_buffer_t *plm_buffer_create_for_appending(size_t initial_capacity) {
plm_buffer_t *self = plm_buffer_create_with_capacity(initial_capacity);
self->mode = PLM_BUFFER_MODE_APPEND;
self->discard_read_bytes = FALSE;
return self;
}
void plm_buffer_destroy(plm_buffer_t *self) {
if (self->fh && self->close_when_done) {
fclose(self->fh);
}
if (self->free_when_done) {
free(self->bytes);
}
free(self);
}
size_t plm_buffer_get_size(plm_buffer_t *self) {
return (self->mode == PLM_BUFFER_MODE_FILE) ? self->total_size
: self->length;
}
size_t plm_buffer_get_remaining(plm_buffer_t *self) {
return self->length - (self->bit_index >> 3);
}
size_t plm_buffer_write(plm_buffer_t *self, uint8_t *bytes, size_t length) {
if (self->mode == PLM_BUFFER_MODE_FIXED_MEM) {
return 0;
}
if (self->discard_read_bytes) {
// This should be a ring buffer, but instead it just shifts all unread
// data to the beginning of the buffer and appends new data at the end.
// Seems to be good enough.
plm_buffer_discard_read_bytes(self);
if (self->mode == PLM_BUFFER_MODE_RING) {
self->total_size = 0;
}
}
// Do we have to resize to fit the new data?
size_t bytes_available = self->capacity - self->length;
if (bytes_available < length) {
size_t new_size = self->capacity;
do {
new_size *= 2;
} while (new_size - self->length < length);
self->bytes = (uint8_t *)realloc(self->bytes, new_size);
self->capacity = new_size;
}
memcpy(self->bytes + self->length, bytes, length);
self->length += length;
self->has_ended = FALSE;
return length;
}
void plm_buffer_signal_end(plm_buffer_t *self) {
self->total_size = self->length;
}
void plm_buffer_set_load_callback(plm_buffer_t *self,
plm_buffer_load_callback fp, void *user) {
self->load_callback = fp;
self->load_callback_user_data = user;
}
void plm_buffer_rewind(plm_buffer_t *self) { plm_buffer_seek(self, 0); }
void plm_buffer_seek(plm_buffer_t *self, size_t pos) {
self->has_ended = FALSE;
if (self->mode == PLM_BUFFER_MODE_FILE) {
fseek(self->fh, pos, SEEK_SET);
self->bit_index = 0;
self->length = 0;
} else if (self->mode == PLM_BUFFER_MODE_RING) {
if (pos != 0) {
// Seeking to non-0 is forbidden for dynamic-mem buffers
return;
}
self->bit_index = 0;
self->length = 0;
self->total_size = 0;
} else if (pos < self->length) {
self->bit_index = pos << 3;
}
}
size_t plm_buffer_tell(plm_buffer_t *self) {
return self->mode == PLM_BUFFER_MODE_FILE
? ftell(self->fh) + (self->bit_index >> 3) - self->length
: self->bit_index >> 3;
}
void plm_buffer_discard_read_bytes(plm_buffer_t *self) {
size_t byte_pos = self->bit_index >> 3;
if (byte_pos == self->length) {
self->bit_index = 0;
self->length = 0;
} else if (byte_pos > 0) {
memmove(self->bytes, self->bytes + byte_pos, self->length - byte_pos);
self->bit_index -= byte_pos << 3;
self->length -= byte_pos;
}
}
void plm_buffer_load_file_callback(plm_buffer_t *self, void *user) {
PLM_UNUSED(user);
if (self->discard_read_bytes) {
plm_buffer_discard_read_bytes(self);
}
size_t bytes_available = self->capacity - self->length;
size_t bytes_read =
fread(self->bytes + self->length, 1, bytes_available, self->fh);
self->length += bytes_read;
if (bytes_read == 0) {
self->has_ended = TRUE;
}
}
int plm_buffer_has_ended(plm_buffer_t *self) { return self->has_ended; }
int plm_buffer_has(plm_buffer_t *self, size_t count) {
if (((self->length << 3) - self->bit_index) >= count) {
return TRUE;
}
if (self->load_callback) {
self->load_callback(self, self->load_callback_user_data);
}
if (((self->length << 3) - self->bit_index) >= count) {
return TRUE;
}
if (self->total_size != 0 && self->length == self->total_size) {
self->has_ended = TRUE;
}
return FALSE;
}
int plm_buffer_read(plm_buffer_t *self, int count) {
if (!plm_buffer_has(self, count)) {
return 0;
}
int value = 0;
while (count) {
int current_byte = self->bytes[self->bit_index >> 3];
int remaining = 8 - (self->bit_index & 7); // Remaining bits in byte
int read = remaining < count ? remaining : count; // Bits in self run
int shift = remaining - read;
int mask = (0xff >> (8 - read));
value = (value << read) | ((current_byte & (mask << shift)) >> shift);
self->bit_index += read;
count -= read;
}
return value;
}
void plm_buffer_align(plm_buffer_t *self) {
self->bit_index = ((self->bit_index + 7) >> 3) << 3; // Align to next byte
}
void plm_buffer_skip(plm_buffer_t *self, size_t count) {
if (plm_buffer_has(self, count)) {
self->bit_index += count;
}
}
int plm_buffer_skip_bytes(plm_buffer_t *self, uint8_t v) {
plm_buffer_align(self);
int skipped = 0;
while (plm_buffer_has(self, 8) && self->bytes[self->bit_index >> 3] == v) {
self->bit_index += 8;
skipped++;
}
return skipped;
}
int plm_buffer_next_start_code(plm_buffer_t *self) {
plm_buffer_align(self);
while (plm_buffer_has(self, (5 << 3))) {
size_t byte_index = (self->bit_index) >> 3;
if (self->bytes[byte_index] == 0x00 &&
self->bytes[byte_index + 1] == 0x00 &&
self->bytes[byte_index + 2] == 0x01) {
self->bit_index = (byte_index + 4) << 3;
return self->bytes[byte_index + 3];
}
self->bit_index += 8;
}
return -1;
}
int plm_buffer_find_start_code(plm_buffer_t *self, int code) {
int current = 0;
while (TRUE) {
current = plm_buffer_next_start_code(self);
if (current == code || current == -1) {
return current;
}
}
return -1;
}
int plm_buffer_has_start_code(plm_buffer_t *self, int code) {
size_t previous_bit_index = self->bit_index;
int previous_discard_read_bytes = self->discard_read_bytes;
self->discard_read_bytes = FALSE;
int current = plm_buffer_find_start_code(self, code);
self->bit_index = previous_bit_index;
self->discard_read_bytes = previous_discard_read_bytes;
return current;
}
int plm_buffer_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);
}
int16_t plm_buffer_read_vlc(plm_buffer_t *self, const plm_vlc_t *table) {
plm_vlc_t state = { 0, 0 };
do {
state = table[state.index + plm_buffer_read(self, 1)];
} while (state.index > 0);
return state.value;
}
uint16_t plm_buffer_read_vlc_uint(plm_buffer_t *self,
const plm_vlc_uint_t *table) {
return (uint16_t)plm_buffer_read_vlc(self, (const plm_vlc_t *)table);
}
// ----------------------------------------------------------------------------
// plm_demux implementation
static const int PLM_START_PACK = 0xBA;
static const int PLM_START_END = 0xB9;
static const int PLM_START_SYSTEM = 0xBB;
typedef struct plm_demux_t {
plm_buffer_t *buffer;
int destroy_buffer_when_done;
float system_clock_ref;
size_t last_file_size;
float last_decoded_pts;
float start_time;
float duration;
int start_code;
int has_pack_header;
int has_system_header;
int has_headers;
int num_audio_streams;
int num_video_streams;
plm_packet_t current_packet;
plm_packet_t next_packet;
} plm_demux_t;
void plm_demux_buffer_seek(plm_demux_t *self, size_t pos);
float plm_demux_decode_time(plm_demux_t *self);
plm_packet_t *plm_demux_decode_packet(plm_demux_t *self, int type);
plm_packet_t *plm_demux_get_packet(plm_demux_t *self);
plm_demux_t *plm_demux_create(plm_buffer_t *buffer, int destroy_when_done) {
plm_demux_t *self = (plm_demux_t *)malloc(sizeof(plm_demux_t));
memset(self, 0, sizeof(plm_demux_t));
self->buffer = buffer;
self->destroy_buffer_when_done = destroy_when_done;
self->start_time = PLM_PACKET_INVALID_TS;
self->duration = PLM_PACKET_INVALID_TS;
self->start_code = -1;
plm_demux_has_headers(self);
return self;
}
void plm_demux_destroy(plm_demux_t *self) {
if (self->destroy_buffer_when_done) {
plm_buffer_destroy(self->buffer);
}
free(self);
}
int plm_demux_has_headers(plm_demux_t *self) {
if (self->has_headers) {
return TRUE;
}
// Decode pack header
if (!self->has_pack_header) {
if (self->start_code != PLM_START_PACK &&
plm_buffer_find_start_code(self->buffer, PLM_START_PACK) == -1) {
return FALSE;
}
self->start_code = PLM_START_PACK;
if (!plm_buffer_has(self->buffer, 64)) {
return FALSE;
}
self->start_code = -1;
if (plm_buffer_read(self->buffer, 4) != 0x02) {
return FALSE;
}
self->system_clock_ref = plm_demux_decode_time(self);
plm_buffer_skip(self->buffer, 1);
plm_buffer_skip(self->buffer, 22); // mux_rate * 50
plm_buffer_skip(self->buffer, 1);
self->has_pack_header = TRUE;
}
// Decode system header
if (!self->has_system_header) {
if (self->start_code != PLM_START_SYSTEM &&
plm_buffer_find_start_code(self->buffer, PLM_START_SYSTEM) == -1) {
return FALSE;
}
self->start_code = PLM_START_SYSTEM;
if (!plm_buffer_has(self->buffer, 56)) {
return FALSE;
}
self->start_code = -1;
plm_buffer_skip(self->buffer, 16); // header_length
plm_buffer_skip(self->buffer, 24); // rate bound
self->num_audio_streams = plm_buffer_read(self->buffer, 6);
plm_buffer_skip(self->buffer, 5); // misc flags
self->num_video_streams = plm_buffer_read(self->buffer, 5);
self->has_system_header = TRUE;
}
self->has_headers = TRUE;
return TRUE;
}
int plm_demux_get_num_video_streams(plm_demux_t *self) {
return plm_demux_has_headers(self) ? self->num_video_streams : 0;
}
int plm_demux_get_num_audio_streams(plm_demux_t *self) {
return plm_demux_has_headers(self) ? self->num_audio_streams : 0;
}
void plm_demux_rewind(plm_demux_t *self) {
plm_buffer_rewind(self->buffer);
self->current_packet.length = 0;
self->next_packet.length = 0;
self->start_code = -1;
}
int plm_demux_has_ended(plm_demux_t *self) {
return plm_buffer_has_ended(self->buffer);
}
void plm_demux_buffer_seek(plm_demux_t *self, size_t pos) {
plm_buffer_seek(self->buffer, pos);
self->current_packet.length = 0;
self->next_packet.length = 0;
self->start_code = -1;
}
float plm_demux_get_start_time(plm_demux_t *self, int type) {
if (self->start_time != PLM_PACKET_INVALID_TS) {
return self->start_time;
}
int previous_pos = plm_buffer_tell(self->buffer);
int previous_start_code = self->start_code;
// Find first video PTS
plm_demux_rewind(self);
do {
plm_packet_t *packet = plm_demux_decode(self);
if (!packet) {
break;
}
if (packet->type == type) {
self->start_time = packet->pts;
}
} while (self->start_time == PLM_PACKET_INVALID_TS);
plm_demux_buffer_seek(self, previous_pos);
self->start_code = previous_start_code;
return self->start_time;
}
float plm_demux_get_duration(plm_demux_t *self, int type) {
size_t file_size = plm_buffer_get_size(self->buffer);
if (self->duration != PLM_PACKET_INVALID_TS &&
self->last_file_size == file_size) {
return self->duration;
}
size_t previous_pos = plm_buffer_tell(self->buffer);
int previous_start_code = self->start_code;
// Find last video PTS. Start searching 64kb from the end and go further
// back if needed.
long start_range = 64 * 1024;
long max_range = 4096 * 1024;
for (long range = start_range; range <= max_range; range *= 2) {
long seek_pos = file_size - range;
if (seek_pos < 0) {
seek_pos = 0;
range = max_range; // Make sure to bail after this round
}
plm_demux_buffer_seek(self, seek_pos);
self->current_packet.length = 0;
float last_pts = PLM_PACKET_INVALID_TS;
plm_packet_t *packet = NULL;
while ((packet = plm_demux_decode(self))) {
if (packet->pts != PLM_PACKET_INVALID_TS && packet->type == type) {
last_pts = packet->pts;
}
}
if (last_pts != PLM_PACKET_INVALID_TS) {
self->duration = last_pts - plm_demux_get_start_time(self, type);
break;
}
}
plm_demux_buffer_seek(self, previous_pos);
self->start_code = previous_start_code;
self->last_file_size = file_size;
return self->duration;
}
plm_packet_t *plm_demux_seek(plm_demux_t *self, float seek_time, int type,
int force_intra) {
if (!plm_demux_has_headers(self)) {
return NULL;
}
// Using the current time, current byte position and the average bytes per
// second for this file, try to jump to a byte position that hopefully has
// packets containing timestamps within one second before to the desired
// seek_time.
// If we hit close to the seek_time scan through all packets to find the
// last one (just before the seek_time) containing an intra frame.
// Otherwise we should at least be closer than before. Calculate the bytes
// per second for the jumped range and jump again.
// The number of retries here is hard-limited to a generous amount. Usually
// the correct range is found after 1--5 jumps, even for files with very
// variable bitrates. If significantly more jumps are needed, there's
// probably something wrong with the file and we just avoid getting into an
// infinite loop. 32 retries should be enough for anybody.
float duration = plm_demux_get_duration(self, type);
long file_size = plm_buffer_get_size(self->buffer);
long byterate = file_size / duration;
float cur_time = self->last_decoded_pts;
float scan_span = 1;
if (seek_time > duration) {
seek_time = duration;
} else if (seek_time < 0) {
seek_time = 0;
}
seek_time += self->start_time;
for (int retry = 0; retry < 32; retry++) {
int found_packet_with_pts = FALSE;
int found_packet_in_range = FALSE;
long last_valid_packet_start = -1;
float first_packet_time = PLM_PACKET_INVALID_TS;
long cur_pos = plm_buffer_tell(self->buffer);
// Estimate byte offset and jump to it.
long offset = (seek_time - cur_time - scan_span) * byterate;
long seek_pos = cur_pos + offset;
if (seek_pos < 0) {
seek_pos = 0;
} else if (seek_pos > file_size - 256) {
seek_pos = file_size - 256;
}
plm_demux_buffer_seek(self, seek_pos);
// Scan through all packets up to the seek_time to find the last packet
// containing an intra frame.
while (plm_buffer_find_start_code(self->buffer, type) != -1) {
long packet_start = plm_buffer_tell(self->buffer);
plm_packet_t *packet = plm_demux_decode_packet(self, type);
// Skip packet if it has no PTS
if (!packet || packet->pts == PLM_PACKET_INVALID_TS) {
continue;
}
// Bail scanning through packets if we hit one that is outside
// seek_time - scan_span.
// We also adjust the cur_time and byterate values here so the next
// iteration can be a bit more precise.
if (packet->pts > seek_time ||
packet->pts < seek_time - scan_span) {
found_packet_with_pts = TRUE;
byterate = (seek_pos - cur_pos) / (packet->pts - cur_time);
cur_time = packet->pts;
break;
}
// If we are still here, it means this packet is in close range to
// the seek_time. If this is the first packet for this jump position
// record the PTS. If we later have to back off, when there was no
// intra frame in this range, we can lower the seek_time to not scan
// this range again.
if (!found_packet_in_range) {
found_packet_in_range = TRUE;
first_packet_time = packet->pts;
}
// Check if this is an intra frame packet. If so, record the buffer
// position of the start of this packet. We want to jump back to it
// later, when we know it's the last intra frame before desired
// seek time.
if (force_intra) {
for (size_t i = 0; i < packet->length - 6; i++) {
// Find the START_PICTURE code
if (packet->data[i] == 0x00 &&
packet->data[i + 1] == 0x00 &&
packet->data[i + 2] == 0x01 &&
packet->data[i + 3] == 0x00) {
// Bits 11--13 in the picture header contain the frame
// type, where 1=Intra
if ((packet->data[i + 5] & 0x38) == 8) {
last_valid_packet_start = packet_start;
}
break;
}
}
}
// If we don't want intra frames, just use the last PTS found.
else {
last_valid_packet_start = packet_start;
}
}
// If there was at least one intra frame in the range scanned above,
// our search is over. Jump back to the packet and decode it again.
if (last_valid_packet_start != -1) {
plm_demux_buffer_seek(self, last_valid_packet_start);
return plm_demux_decode_packet(self, type);
}
// If we hit the right range, but still found no intra frame, we have
// to increases the scan_span. This is done exponentially to also handle
// video files with very few intra frames.
else if (found_packet_in_range) {
scan_span *= 2;
seek_time = first_packet_time;
}
// If we didn't find any packet with a PTS, it probably means we reached
// the end of the file. Estimate byterate and cur_time accordingly.
else if (!found_packet_with_pts) {
byterate = (seek_pos - cur_pos) / (duration - cur_time);
cur_time = duration;
}
}
return NULL;
}
plm_packet_t *plm_demux_decode(plm_demux_t *self) {
if (!plm_demux_has_headers(self)) {
return NULL;
}
if (self->current_packet.length) {
size_t bits_till_next_packet = self->current_packet.length << 3;
if (!plm_buffer_has(self->buffer, bits_till_next_packet)) {
return NULL;
}
plm_buffer_skip(self->buffer, bits_till_next_packet);
self->current_packet.length = 0;
}
// Pending packet waiting for data?
if (self->next_packet.length) {
return plm_demux_get_packet(self);
}
// Pending packet waiting for header?
if (self->start_code != -1) {
return plm_demux_decode_packet(self, self->start_code);
}
do {
self->start_code = plm_buffer_next_start_code(self->buffer);
if (self->start_code == PLM_DEMUX_PACKET_VIDEO_1 ||
self->start_code == PLM_DEMUX_PACKET_PRIVATE ||
(self->start_code >= PLM_DEMUX_PACKET_AUDIO_1 &&
self->start_code <= PLM_DEMUX_PACKET_AUDIO_4)) {
return plm_demux_decode_packet(self, self->start_code);
}
} while (self->start_code != -1);
return NULL;
}
float plm_demux_decode_time(plm_demux_t *self) {
int64_t clock = plm_buffer_read(self->buffer, 3) << 30;
plm_buffer_skip(self->buffer, 1);
clock |= plm_buffer_read(self->buffer, 15) << 15;
plm_buffer_skip(self->buffer, 1);
clock |= plm_buffer_read(self->buffer, 15);
plm_buffer_skip(self->buffer, 1);
return (float)clock / 90000.0f;
}
plm_packet_t *plm_demux_decode_packet(plm_demux_t *self, int type) {
if (!plm_buffer_has(self->buffer, 16 << 3)) {
return NULL;
}
self->start_code = -1;
self->next_packet.type = type;
self->next_packet.length = plm_buffer_read(self->buffer, 16);
self->next_packet.length -=
plm_buffer_skip_bytes(self->buffer, 0xff); // stuffing
// skip P-STD
if (plm_buffer_read(self->buffer, 2) == 0x01) {
plm_buffer_skip(self->buffer, 16);
self->next_packet.length -= 2;
}
int pts_dts_marker = plm_buffer_read(self->buffer, 2);
if (pts_dts_marker == 0x03) {
self->next_packet.pts = plm_demux_decode_time(self);
self->last_decoded_pts = self->next_packet.pts;
plm_buffer_skip(self->buffer, 40); // skip dts
self->next_packet.length -= 10;
} else if (pts_dts_marker == 0x02) {
self->next_packet.pts = plm_demux_decode_time(self);
self->last_decoded_pts = self->next_packet.pts;
self->next_packet.length -= 5;
} else if (pts_dts_marker == 0x00) {
self->next_packet.pts = PLM_PACKET_INVALID_TS;
plm_buffer_skip(self->buffer, 4);
self->next_packet.length -= 1;
} else {
return NULL; // invalid
}
return plm_demux_get_packet(self);
}
plm_packet_t *plm_demux_get_packet(plm_demux_t *self) {
if (!plm_buffer_has(self->buffer, self->next_packet.length << 3)) {
return NULL;
}
self->current_packet.data =
self->buffer->bytes + (self->buffer->bit_index >> 3);
self->current_packet.length = self->next_packet.length;
self->current_packet.type = self->next_packet.type;
self->current_packet.pts = self->next_packet.pts;
self->next_packet.length = 0;
return &self->current_packet;
}
// -----------------------------------------------------------------------------
// plm_video implementation
// Inspired by Java MPEG-1 Video Decoder and Player by Zoltan Korandi
// https://sourceforge.net/projects/javampeg1video/
static const int PLM_VIDEO_PICTURE_TYPE_INTRA = 1;
static const int PLM_VIDEO_PICTURE_TYPE_PREDICTIVE = 2;
static const int PLM_VIDEO_PICTURE_TYPE_B = 3;
static const int PLM_START_SEQUENCE = 0xB3;
static const int PLM_START_SLICE_FIRST = 0x01;
static const int PLM_START_SLICE_LAST = 0xAF;
static const int PLM_START_PICTURE = 0x00;
static const int PLM_START_EXTENSION = 0xB5;
static const int PLM_START_USER_DATA = 0xB2;
#define PLM_START_IS_SLICE(c) \
(c >= PLM_START_SLICE_FIRST && c <= PLM_START_SLICE_LAST)
static const float PLM_VIDEO_PICTURE_RATE[] = {
0.000f, 23.976f, 24.000f, 25.000f, 29.970f, 30.000f, 50.000f, 59.940f,
60.000f, 0.000f, 0.000f, 0.000f, 0.000f, 0.000f, 0.000f, 0.000f
};
static const uint8_t PLM_VIDEO_ZIG_ZAG[] = {
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5,
12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28,
35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,
58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63
};
static const uint8_t PLM_VIDEO_INTRA_QUANT_MATRIX[] = {
8, 16, 19, 22, 26, 27, 29, 34, 16, 16, 22, 24, 27, 29, 34, 37,
19, 22, 26, 27, 29, 34, 34, 38, 22, 22, 26, 27, 29, 34, 37, 40,
22, 26, 27, 29, 32, 35, 40, 48, 26, 27, 29, 32, 35, 40, 48, 58,
26, 27, 29, 34, 38, 46, 56, 69, 27, 29, 35, 38, 46, 56, 69, 83
};
static const uint8_t PLM_VIDEO_NON_INTRA_QUANT_MATRIX[] = {
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16
};
static const uint8_t PLM_VIDEO_PREMULTIPLIER_MATRIX[] = {
32, 44, 42, 38, 32, 25, 17, 9, 44, 62, 58, 52, 44, 35, 24, 12,
42, 58, 55, 49, 42, 33, 23, 12, 38, 52, 49, 44, 38, 30, 20, 10,
32, 44, 42, 38, 32, 25, 17, 9, 25, 35, 33, 30, 25, 20, 14, 7,
17, 24, 23, 20, 17, 14, 9, 5, 9, 12, 12, 10, 9, 7, 5, 2
};
static const plm_vlc_t PLM_VIDEO_MACROBLOCK_ADDRESS_INCREMENT[] = {
{ 1 << 1, 0 }, { 0, 1 }, // 0: x
{ 2 << 1, 0 }, { 3 << 1, 0 }, // 1: 0x
{ 4 << 1, 0 }, { 5 << 1, 0 }, // 2: 00x
{ 0, 3 }, { 0, 2 }, // 3: 01x
{ 6 << 1, 0 }, { 7 << 1, 0 }, // 4: 000x
{ 0, 5 }, { 0, 4 }, // 5: 001x
{ 8 << 1, 0 }, { 9 << 1, 0 }, // 6: 0000x
{ 0, 7 }, { 0, 6 }, // 7: 0001x
{ 10 << 1, 0 }, { 11 << 1, 0 }, // 8: 0000 0x
{ 12 << 1, 0 }, { 13 << 1, 0 }, // 9: 0000 1x
{ 14 << 1, 0 }, { 15 << 1, 0 }, // 10: 0000 00x
{ 16 << 1, 0 }, { 17 << 1, 0 }, // 11: 0000 01x
{ 18 << 1, 0 }, { 19 << 1, 0 }, // 12: 0000 10x
{ 0, 9 }, { 0, 8 }, // 13: 0000 11x
{ -1, 0 }, { 20 << 1, 0 }, // 14: 0000 000x
{ -1, 0 }, { 21 << 1, 0 }, // 15: 0000 001x
{ 22 << 1, 0 }, { 23 << 1, 0 }, // 16: 0000 010x
{ 0, 15 }, { 0, 14 }, // 17: 0000 011x
{ 0, 13 }, { 0, 12 }, // 18: 0000 100x
{ 0, 11 }, { 0, 10 }, // 19: 0000 101x
{ 24 << 1, 0 }, { 25 << 1, 0 }, // 20: 0000 0001x
{ 26 << 1, 0 }, { 27 << 1, 0 }, // 21: 0000 0011x
{ 28 << 1, 0 }, { 29 << 1, 0 }, // 22: 0000 0100x
{ 30 << 1, 0 }, { 31 << 1, 0 }, // 23: 0000 0101x
{ 32 << 1, 0 }, { -1, 0 }, // 24: 0000 0001 0x
{ -1, 0 }, { 33 << 1, 0 }, // 25: 0000 0001 1x
{ 34 << 1, 0 }, { 35 << 1, 0 }, // 26: 0000 0011 0x
{ 36 << 1, 0 }, { 37 << 1, 0 }, // 27: 0000 0011 1x
{ 38 << 1, 0 }, { 39 << 1, 0 }, // 28: 0000 0100 0x
{ 0, 21 }, { 0, 20 }, // 29: 0000 0100 1x
{ 0, 19 }, { 0, 18 }, // 30: 0000 0101 0x
{ 0, 17 }, { 0, 16 }, // 31: 0000 0101 1x
{ 0, 35 }, { -1, 0 }, // 32: 0000 0001 00x
{ -1, 0 }, { 0, 34 }, // 33: 0000 0001 11x
{ 0, 33 }, { 0, 32 }, // 34: 0000 0011 00x
{ 0, 31 }, { 0, 30 }, // 35: 0000 0011 01x
{ 0, 29 }, { 0, 28 }, // 36: 0000 0011 10x
{ 0, 27 }, { 0, 26 }, // 37: 0000 0011 11x
{ 0, 25 }, { 0, 24 }, // 38: 0000 0100 00x
{ 0, 23 }, { 0, 22 }, // 39: 0000 0100 01x
};
static const plm_vlc_t PLM_VIDEO_MACROBLOCK_TYPE_INTRA[] = {
{ 1 << 1, 0 },
{ 0, 0x01 }, // 0: x
{ -1, 0 },
{ 0, 0x11 }, // 1: 0x
};
static const plm_vlc_t PLM_VIDEO_MACROBLOCK_TYPE_PREDICTIVE[] = {
{ 1 << 1, 0 }, { 0, 0x0a }, // 0: x
{ 2 << 1, 0 }, { 0, 0x02 }, // 1: 0x
{ 3 << 1, 0 }, { 0, 0x08 }, // 2: 00x
{ 4 << 1, 0 }, { 5 << 1, 0 }, // 3: 000x
{ 6 << 1, 0 }, { 0, 0x12 }, // 4: 0000x
{ 0, 0x1a }, { 0, 0x01 }, // 5: 0001x
{ -1, 0 }, { 0, 0x11 }, // 6: 0000 0x
};
static const plm_vlc_t PLM_VIDEO_MACROBLOCK_TYPE_B[] = {
{ 1 << 1, 0 }, { 2 << 1, 0 }, // 0: x
{ 3 << 1, 0 }, { 4 << 1, 0 }, // 1: 0x
{ 0, 0x0c }, { 0, 0x0e }, // 2: 1x
{ 5 << 1, 0 }, { 6 << 1, 0 }, // 3: 00x
{ 0, 0x04 }, { 0, 0x06 }, // 4: 01x
{ 7 << 1, 0 }, { 8 << 1, 0 }, // 5: 000x
{ 0, 0x08 }, { 0, 0x0a }, // 6: 001x
{ 9 << 1, 0 }, { 10 << 1, 0 }, // 7: 0000x
{ 0, 0x1e }, { 0, 0x01 }, // 8: 0001x
{ -1, 0 }, { 0, 0x11 }, // 9: 0000 0x
{ 0, 0x16 }, { 0, 0x1a }, // 10: 0000 1x
};
static const plm_vlc_t *PLM_VIDEO_MACROBLOCK_TYPE[] = {
NULL, PLM_VIDEO_MACROBLOCK_TYPE_INTRA, PLM_VIDEO_MACROBLOCK_TYPE_PREDICTIVE,
PLM_VIDEO_MACROBLOCK_TYPE_B
};
static const plm_vlc_t PLM_VIDEO_CODE_BLOCK_PATTERN[] = {
{ 1 << 1, 0 }, { 2 << 1, 0 }, // 0: x
{ 3 << 1, 0 }, { 4 << 1, 0 }, // 1: 0x
{ 5 << 1, 0 }, { 6 << 1, 0 }, // 2: 1x
{ 7 << 1, 0 }, { 8 << 1, 0 }, // 3: 00x
{ 9 << 1, 0 }, { 10 << 1, 0 }, // 4: 01x
{ 11 << 1, 0 }, { 12 << 1, 0 }, // 5: 10x
{ 13 << 1, 0 }, { 0, 60 }, // 6: 11x
{ 14 << 1, 0 }, { 15 << 1, 0 }, // 7: 000x
{ 16 << 1, 0 }, { 17 << 1, 0 }, // 8: 001x
{ 18 << 1, 0 }, { 19 << 1, 0 }, // 9: 010x
{ 20 << 1, 0 }, { 21 << 1, 0 }, // 10: 011x
{ 22 << 1, 0 }, { 23 << 1, 0 }, // 11: 100x
{ 0, 32 }, { 0, 16 }, // 12: 101x
{ 0, 8 }, { 0, 4 }, // 13: 110x
{ 24 << 1, 0 }, { 25 << 1, 0 }, // 14: 0000x
{ 26 << 1, 0 }, { 27 << 1, 0 }, // 15: 0001x
{ 28 << 1, 0 }, { 29 << 1, 0 }, // 16: 0010x
{ 30 << 1, 0 }, { 31 << 1, 0 }, // 17: 0011x
{ 0, 62 }, { 0, 2 }, // 18: 0100x
{ 0, 61 }, { 0, 1 }, // 19: 0101x
{ 0, 56 }, { 0, 52 }, // 20: 0110x
{ 0, 44 }, { 0, 28 }, // 21: 0111x
{ 0, 40 }, { 0, 20 }, // 22: 1000x
{ 0, 48 }, { 0, 12 }, // 23: 1001x
{ 32 << 1, 0 }, { 33 << 1, 0 }, // 24: 0000 0x
{ 34 << 1, 0 }, { 35 << 1, 0 }, // 25: 0000 1x
{ 36 << 1, 0 }, { 37 << 1, 0 }, // 26: 0001 0x
{ 38 << 1, 0 }, { 39 << 1, 0 }, // 27: 0001 1x
{ 40 << 1, 0 }, { 41 << 1, 0 }, // 28: 0010 0x
{ 42 << 1, 0 }, { 43 << 1, 0 }, // 29: 0010 1x
{ 0, 63 }, { 0, 3 }, // 30: 0011 0x
{ 0, 36 }, { 0, 24 }, // 31: 0011 1x
{ 44 << 1, 0 }, { 45 << 1, 0 }, // 32: 0000 00x
{ 46 << 1, 0 }, { 47 << 1, 0 }, // 33: 0000 01x
{ 48 << 1, 0 }, { 49 << 1, 0 }, // 34: 0000 10x
{ 50 << 1, 0 }, { 51 << 1, 0 }, // 35: 0000 11x
{ 52 << 1, 0 }, { 53 << 1, 0 }, // 36: 0001 00x
{ 54 << 1, 0 }, { 55 << 1, 0 }, // 37: 0001 01x
{ 56 << 1, 0 }, { 57 << 1, 0 }, // 38: 0001 10x
{ 58 << 1, 0 }, { 59 << 1, 0 }, // 39: 0001 11x
{ 0, 34 }, { 0, 18 }, // 40: 0010 00x
{ 0, 10 }, { 0, 6 }, // 41: 0010 01x
{ 0, 33 }, { 0, 17 }, // 42: 0010 10x
{ 0, 9 }, { 0, 5 }, // 43: 0010 11x
{ -1, 0 }, { 60 << 1, 0 }, // 44: 0000 000x
{ 61 << 1, 0 }, { 62 << 1, 0 }, // 45: 0000 001x
{ 0, 58 }, { 0, 54 }, // 46: 0000 010x
{ 0, 46 }, { 0, 30 }, // 47: 0000 011x
{ 0, 57 }, { 0, 53 }, // 48: 0000 100x
{ 0, 45 }, { 0, 29 }, // 49: 0000 101x
{ 0, 38 }, { 0, 26 }, // 50: 0000 110x
{ 0, 37 }, { 0, 25 }, // 51: 0000 111x
{ 0, 43 }, { 0, 23 }, // 52: 0001 000x
{ 0, 51 }, { 0, 15 }, // 53: 0001 001x
{ 0, 42 }, { 0, 22 }, // 54: 0001 010x
{ 0, 50 }, { 0, 14 }, // 55: 0001 011x
{ 0, 41 }, { 0, 21 }, // 56: 0001 100x
{ 0, 49 }, { 0, 13 }, // 57: 0001 101x
{ 0, 35 }, { 0, 19 }, // 58: 0001 110x
{ 0, 11 }, { 0, 7 }, // 59: 0001 111x
{ 0, 39 }, { 0, 27 }, // 60: 0000 0001x
{ 0, 59 }, { 0, 55 }, // 61: 0000 0010x
{ 0, 47 }, { 0, 31 }, // 62: 0000 0011x
};
static const plm_vlc_t PLM_VIDEO_MOTION[] = {
{ 1 << 1, 0 }, { 0, 0 }, // 0: x
{ 2 << 1, 0 }, { 3 << 1, 0 }, // 1: 0x
{ 4 << 1, 0 }, { 5 << 1, 0 }, // 2: 00x
{ 0, 1 }, { 0, -1 }, // 3: 01x
{ 6 << 1, 0 }, { 7 << 1, 0 }, // 4: 000x
{ 0, 2 }, { 0, -2 }, // 5: 001x
{ 8 << 1, 0 }, { 9 << 1, 0 }, // 6: 0000x
{ 0, 3 }, { 0, -3 }, // 7: 0001x
{ 10 << 1, 0 }, { 11 << 1, 0 }, // 8: 0000 0x
{ 12 << 1, 0 }, { 13 << 1, 0 }, // 9: 0000 1x
{ -1, 0 }, { 14 << 1, 0 }, // 10: 0000 00x
{ 15 << 1, 0 }, { 16 << 1, 0 }, // 11: 0000 01x
{ 17 << 1, 0 }, { 18 << 1, 0 }, // 12: 0000 10x
{ 0, 4 }, { 0, -4 }, // 13: 0000 11x
{ -1, 0 }, { 19 << 1, 0 }, // 14: 0000 001x
{ 20 << 1, 0 }, { 21 << 1, 0 }, // 15: 0000 010x
{ 0, 7 }, { 0, -7 }, // 16: 0000 011x
{ 0, 6 }, { 0, -6 }, // 17: 0000 100x
{ 0, 5 }, { 0, -5 }, // 18: 0000 101x
{ 22 << 1, 0 }, { 23 << 1, 0 }, // 19: 0000 0011x
{ 24 << 1, 0 }, { 25 << 1, 0 }, // 20: 0000 0100x
{ 26 << 1, 0 }, { 27 << 1, 0 }, // 21: 0000 0101x
{ 28 << 1, 0 }, { 29 << 1, 0 }, // 22: 0000 0011 0x
{ 30 << 1, 0 }, { 31 << 1, 0 }, // 23: 0000 0011 1x
{ 32 << 1, 0 }, { 33 << 1, 0 }, // 24: 0000 0100 0x
{ 0, 10 }, { 0, -10 }, // 25: 0000 0100 1x
{ 0, 9 }, { 0, -9 }, // 26: 0000 0101 0x
{ 0, 8 }, { 0, -8 }, // 27: 0000 0101 1x
{ 0, 16 }, { 0, -16 }, // 28: 0000 0011 00x
{ 0, 15 }, { 0, -15 }, // 29: 0000 0011 01x
{ 0, 14 }, { 0, -14 }, // 30: 0000 0011 10x
{ 0, 13 }, { 0, -13 }, // 31: 0000 0011 11x
{ 0, 12 }, { 0, -12 }, // 32: 0000 0100 00x
{ 0, 11 }, { 0, -11 }, // 33: 0000 0100 01x
};
static const plm_vlc_t PLM_VIDEO_DCT_SIZE_LUMINANCE[] = {
{ 1 << 1, 0 }, { 2 << 1, 0 }, // 0: x
{ 0, 1 }, { 0, 2 }, // 1: 0x
{ 3 << 1, 0 }, { 4 << 1, 0 }, // 2: 1x
{ 0, 0 }, { 0, 3 }, // 3: 10x
{ 0, 4 }, { 5 << 1, 0 }, // 4: 11x
{ 0, 5 }, { 6 << 1, 0 }, // 5: 111x
{ 0, 6 }, { 7 << 1, 0 }, // 6: 1111x
{ 0, 7 }, { 8 << 1, 0 }, // 7: 1111 1x
{ 0, 8 }, { -1, 0 }, // 8: 1111 11x
};
static const plm_vlc_t PLM_VIDEO_DCT_SIZE_CHROMINANCE[] = {
{ 1 << 1, 0 }, { 2 << 1, 0 }, // 0: x
{ 0, 0 }, { 0, 1 }, // 1: 0x
{ 0, 2 }, { 3 << 1, 0 }, // 2: 1x
{ 0, 3 }, { 4 << 1, 0 }, // 3: 11x
{ 0, 4 }, { 5 << 1, 0 }, // 4: 111x
{ 0, 5 }, { 6 << 1, 0 }, // 5: 1111x
{ 0, 6 }, { 7 << 1, 0 }, // 6: 1111 1x
{ 0, 7 }, { 8 << 1, 0 }, // 7: 1111 11x
{ 0, 8 }, { -1, 0 }, // 8: 1111 111x
};
static const plm_vlc_t *PLM_VIDEO_DCT_SIZE[] = {
PLM_VIDEO_DCT_SIZE_LUMINANCE, PLM_VIDEO_DCT_SIZE_CHROMINANCE,
PLM_VIDEO_DCT_SIZE_CHROMINANCE
};
// dct_coeff bitmap:
// 0xff00 run
// 0x00ff level
// Decoded values are unsigned. Sign bit follows in the stream.
static const plm_vlc_uint_t PLM_VIDEO_DCT_COEFF[] = {
{ 1 << 1, 0 }, { 0, 0x0001 }, // 0: x
{ 2 << 1, 0 }, { 3 << 1, 0 }, // 1: 0x
{ 4 << 1, 0 }, { 5 << 1, 0 }, // 2: 00x
{ 6 << 1, 0 }, { 0, 0x0101 }, // 3: 01x
{ 7 << 1, 0 }, { 8 << 1, 0 }, // 4: 000x
{ 9 << 1, 0 }, { 10 << 1, 0 }, // 5: 001x
{ 0, 0x0002 }, { 0, 0x0201 }, // 6: 010x
{ 11 << 1, 0 }, { 12 << 1, 0 }, // 7: 0000x
{ 13 << 1, 0 }, { 14 << 1, 0 }, // 8: 0001x
{ 15 << 1, 0 }, { 0, 0x0003 }, // 9: 0010x
{ 0, 0x0401 }, { 0, 0x0301 }, // 10: 0011x
{ 16 << 1, 0 }, { 0, 0xffff }, // 11: 0000 0x
{ 17 << 1, 0 }, { 18 << 1, 0 }, // 12: 0000 1x
{ 0, 0x0701 }, { 0, 0x0601 }, // 13: 0001 0x
{ 0, 0x0102 }, { 0, 0x0501 }, // 14: 0001 1x
{ 19 << 1, 0 }, { 20 << 1, 0 }, // 15: 0010 0x
{ 21 << 1, 0 }, { 22 << 1, 0 }, // 16: 0000 00x
{ 0, 0x0202 }, { 0, 0x0901 }, // 17: 0000 10x
{ 0, 0x0004 }, { 0, 0x0801 }, // 18: 0000 11x
{ 23 << 1, 0 }, { 24 << 1, 0 }, // 19: 0010 00x
{ 25 << 1, 0 }, { 26 << 1, 0 }, // 20: 0010 01x
{ 27 << 1, 0 }, { 28 << 1, 0 }, // 21: 0000 000x
{ 29 << 1, 0 }, { 30 << 1, 0 }, // 22: 0000 001x
{ 0, 0x0d01 }, { 0, 0x0006 }, // 23: 0010 000x
{ 0, 0x0c01 }, { 0, 0x0b01 }, // 24: 0010 001x
{ 0, 0x0302 }, { 0, 0x0103 }, // 25: 0010 010x
{ 0, 0x0005 }, { 0, 0x0a01 }, // 26: 0010 011x
{ 31 << 1, 0 }, { 32 << 1, 0 }, // 27: 0000 0000x
{ 33 << 1, 0 }, { 34 << 1, 0 }, // 28: 0000 0001x
{ 35 << 1, 0 }, { 36 << 1, 0 }, // 29: 0000 0010x
{ 37 << 1, 0 }, { 38 << 1, 0 }, // 30: 0000 0011x
{ 39 << 1, 0 }, { 40 << 1, 0 }, // 31: 0000 0000 0x
{ 41 << 1, 0 }, { 42 << 1, 0 }, // 32: 0000 0000 1x
{ 43 << 1, 0 }, { 44 << 1, 0 }, // 33: 0000 0001 0x
{ 45 << 1, 0 }, { 46 << 1, 0 }, // 34: 0000 0001 1x
{ 0, 0x1001 }, { 0, 0x0502 }, // 35: 0000 0010 0x
{ 0, 0x0007 }, { 0, 0x0203 }, // 36: 0000 0010 1x
{ 0, 0x0104 }, { 0, 0x0f01 }, // 37: 0000 0011 0x
{ 0, 0x0e01 }, { 0, 0x0402 }, // 38: 0000 0011 1x
{ 47 << 1, 0 }, { 48 << 1, 0 }, // 39: 0000 0000 00x
{ 49 << 1, 0 }, { 50 << 1, 0 }, // 40: 0000 0000 01x
{ 51 << 1, 0 }, { 52 << 1, 0 }, // 41: 0000 0000 10x
{ 53 << 1, 0 }, { 54 << 1, 0 }, // 42: 0000 0000 11x
{ 55 << 1, 0 }, { 56 << 1, 0 }, // 43: 0000 0001 00x
{ 57 << 1, 0 }, { 58 << 1, 0 }, // 44: 0000 0001 01x
{ 59 << 1, 0 }, { 60 << 1, 0 }, // 45: 0000 0001 10x
{ 61 << 1, 0 }, { 62 << 1, 0 }, // 46: 0000 0001 11x
{ -1, 0 }, { 63 << 1, 0 }, // 47: 0000 0000 000x
{ 64 << 1, 0 }, { 65 << 1, 0 }, // 48: 0000 0000 001x
{ 66 << 1, 0 }, { 67 << 1, 0 }, // 49: 0000 0000 010x
{ 68 << 1, 0 }, { 69 << 1, 0 }, // 50: 0000 0000 011x
{ 70 << 1, 0 }, { 71 << 1, 0 }, // 51: 0000 0000 100x
{ 72 << 1, 0 }, { 73 << 1, 0 }, // 52: 0000 0000 101x
{ 74 << 1, 0 }, { 75 << 1, 0 }, // 53: 0000 0000 110x
{ 76 << 1, 0 }, { 77 << 1, 0 }, // 54: 0000 0000 111x
{ 0, 0x000b }, { 0, 0x0802 }, // 55: 0000 0001 000x
{ 0, 0x0403 }, { 0, 0x000a }, // 56: 0000 0001 001x
{ 0, 0x0204 }, { 0, 0x0702 }, // 57: 0000 0001 010x
{ 0, 0x1501 }, { 0, 0x1401 }, // 58: 0000 0001 011x
{ 0, 0x0009 }, { 0, 0x1301 }, // 59: 0000 0001 100x
{ 0, 0x1201 }, { 0, 0x0105 }, // 60: 0000 0001 101x
{ 0, 0x0303 }, { 0, 0x0008 }, // 61: 0000 0001 110x
{ 0, 0x0602 }, { 0, 0x1101 }, // 62: 0000 0001 111x
{ 78 << 1, 0 }, { 79 << 1, 0 }, // 63: 0000 0000 0001x
{ 80 << 1, 0 }, { 81 << 1, 0 }, // 64: 0000 0000 0010x
{ 82 << 1, 0 }, { 83 << 1, 0 }, // 65: 0000 0000 0011x
{ 84 << 1, 0 }, { 85 << 1, 0 }, // 66: 0000 0000 0100x
{ 86 << 1, 0 }, { 87 << 1, 0 }, // 67: 0000 0000 0101x
{ 88 << 1, 0 }, { 89 << 1, 0 }, // 68: 0000 0000 0110x
{ 90 << 1, 0 }, { 91 << 1, 0 }, // 69: 0000 0000 0111x
{ 0, 0x0a02 }, { 0, 0x0902 }, // 70: 0000 0000 1000x
{ 0, 0x0503 }, { 0, 0x0304 }, // 71: 0000 0000 1001x
{ 0, 0x0205 }, { 0, 0x0107 }, // 72: 0000 0000 1010x
{ 0, 0x0106 }, { 0, 0x000f }, // 73: 0000 0000 1011x
{ 0, 0x000e }, { 0, 0x000d }, // 74: 0000 0000 1100x
{ 0, 0x000c }, { 0, 0x1a01 }, // 75: 0000 0000 1101x
{ 0, 0x1901 }, { 0, 0x1801 }, // 76: 0000 0000 1110x
{ 0, 0x1701 }, { 0, 0x1601 }, // 77: 0000 0000 1111x
{ 92 << 1, 0 }, { 93 << 1, 0 }, // 78: 0000 0000 0001 0x
{ 94 << 1, 0 }, { 95 << 1, 0 }, // 79: 0000 0000 0001 1x
{ 96 << 1, 0 }, { 97 << 1, 0 }, // 80: 0000 0000 0010 0x
{ 98 << 1, 0 }, { 99 << 1, 0 }, // 81: 0000 0000 0010 1x
{ 100 << 1, 0 }, { 101 << 1, 0 }, // 82: 0000 0000 0011 0x
{ 102 << 1, 0 }, { 103 << 1, 0 }, // 83: 0000 0000 0011 1x
{ 0, 0x001f }, { 0, 0x001e }, // 84: 0000 0000 0100 0x
{ 0, 0x001d }, { 0, 0x001c }, // 85: 0000 0000 0100 1x
{ 0, 0x001b }, { 0, 0x001a }, // 86: 0000 0000 0101 0x
{ 0, 0x0019 }, { 0, 0x0018 }, // 87: 0000 0000 0101 1x
{ 0, 0x0017 }, { 0, 0x0016 }, // 88: 0000 0000 0110 0x
{ 0, 0x0015 }, { 0, 0x0014 }, // 89: 0000 0000 0110 1x
{ 0, 0x0013 }, { 0, 0x0012 }, // 90: 0000 0000 0111 0x
{ 0, 0x0011 }, { 0, 0x0010 }, // 91: 0000 0000 0111 1x
{ 104 << 1, 0 }, { 105 << 1, 0 }, // 92: 0000 0000 0001 00x
{ 106 << 1, 0 }, { 107 << 1, 0 }, // 93: 0000 0000 0001 01x
{ 108 << 1, 0 }, { 109 << 1, 0 }, // 94: 0000 0000 0001 10x
{ 110 << 1, 0 }, { 111 << 1, 0 }, // 95: 0000 0000 0001 11x
{ 0, 0x0028 }, { 0, 0x0027 }, // 96: 0000 0000 0010 00x
{ 0, 0x0026 }, { 0, 0x0025 }, // 97: 0000 0000 0010 01x
{ 0, 0x0024 }, { 0, 0x0023 }, // 98: 0000 0000 0010 10x
{ 0, 0x0022 }, { 0, 0x0021 }, // 99: 0000 0000 0010 11x
{ 0, 0x0020 }, { 0, 0x010e }, // 100: 0000 0000 0011 00x
{ 0, 0x010d }, { 0, 0x010c }, // 101: 0000 0000 0011 01x
{ 0, 0x010b }, { 0, 0x010a }, // 102: 0000 0000 0011 10x
{ 0, 0x0109 }, { 0, 0x0108 }, // 103: 0000 0000 0011 11x
{ 0, 0x0112 }, { 0, 0x0111 }, // 104: 0000 0000 0001 000x
{ 0, 0x0110 }, { 0, 0x010f }, // 105: 0000 0000 0001 001x
{ 0, 0x0603 }, { 0, 0x1002 }, // 106: 0000 0000 0001 010x
{ 0, 0x0f02 }, { 0, 0x0e02 }, // 107: 0000 0000 0001 011x
{ 0, 0x0d02 }, { 0, 0x0c02 }, // 108: 0000 0000 0001 100x
{ 0, 0x0b02 }, { 0, 0x1f01 }, // 109: 0000 0000 0001 101x
{ 0, 0x1e01 }, { 0, 0x1d01 }, // 110: 0000 0000 0001 110x
{ 0, 0x1c01 }, { 0, 0x1b01 }, // 111: 0000 0000 0001 111x
};
typedef struct {
int full_px;
int is_set;
int r_size;
int h;
int v;
} plm_video_motion_t;
typedef struct plm_video_t {
float framerate;
float time;
int frames_decoded;
int width;
int height;
int mb_width;
int mb_height;
int mb_size;
int luma_width;
int luma_height;
int chroma_width;
int chroma_height;
int start_code;
int picture_type;
plm_video_motion_t motion_forward;
plm_video_motion_t motion_backward;
int has_sequence_header;
int quantizer_scale;
int slice_begin;
int macroblock_address;
int mb_row;
int mb_col;
int macroblock_type;
int macroblock_intra;
int dc_predictor[3];
plm_buffer_t *buffer;
int destroy_buffer_when_done;
plm_frame_t frame_current;
plm_frame_t frame_forward;
plm_frame_t frame_backward;
uint8_t *frames_data;
int block_data[64];
uint8_t intra_quant_matrix[64];
uint8_t non_intra_quant_matrix[64];
int has_reference_frame;
int assume_no_b_frames;
} plm_video_t;
static inline uint8_t plm_clamp(int n) {
if (n > 255) {
n = 255;
} else if (n < 0) {
n = 0;
}
return n;
}
int plm_video_decode_sequence_header(plm_video_t *self);
void plm_video_init_frame(plm_video_t *self, plm_frame_t *frame, uint8_t *base);
void plm_video_decode_picture(plm_video_t *self);
void plm_video_decode_slice(plm_video_t *self, int slice);
void plm_video_decode_macroblock(plm_video_t *self);
void plm_video_decode_motion_vectors(plm_video_t *self);
int plm_video_decode_motion_vector(plm_video_t *self, int r_size, int motion);
void plm_video_predict_macroblock(plm_video_t *self);
void plm_video_copy_macroblock(plm_video_t *self, int motion_h, int motion_v,
plm_frame_t *d);
void plm_video_interpolate_macroblock(plm_video_t *self, int motion_h,
int motion_v, plm_frame_t *d);
void plm_video_process_macroblock(plm_video_t *self, uint8_t *d, uint8_t *s,
int mh, int mb, int bs, int interp);
void plm_video_decode_block(plm_video_t *self, int block);
void plm_video_idct(int *block);
plm_video_t *plm_video_create_with_buffer(plm_buffer_t *buffer,
int destroy_when_done) {
plm_video_t *self = (plm_video_t *)malloc(sizeof(plm_video_t));
memset(self, 0, sizeof(plm_video_t));
self->buffer = buffer;
self->destroy_buffer_when_done = destroy_when_done;
// Attempt to decode the sequence header
self->start_code =
plm_buffer_find_start_code(self->buffer, PLM_START_SEQUENCE);
if (self->start_code != -1) {
plm_video_decode_sequence_header(self);
}
return self;
}
void plm_video_destroy(plm_video_t *self) {
if (self->destroy_buffer_when_done) {
plm_buffer_destroy(self->buffer);
}
if (self->has_sequence_header) {
free(self->frames_data);
}
free(self);
}
float plm_video_get_framerate(plm_video_t *self) {
return plm_video_has_header(self) ? self->framerate : 0;
}
int plm_video_get_width(plm_video_t *self) {
return plm_video_has_header(self) ? self->width : 0;
}
int plm_video_get_height(plm_video_t *self) {
return plm_video_has_header(self) ? self->height : 0;
}
void plm_video_set_no_delay(plm_video_t *self, int no_delay) {
self->assume_no_b_frames = no_delay;
}
float plm_video_get_time(plm_video_t *self) { return self->time; }
void plm_video_set_time(plm_video_t *self, float time) {
self->frames_decoded = self->framerate * time;
self->time = time;
}
void plm_video_rewind(plm_video_t *self) {
plm_buffer_rewind(self->buffer);
self->time = 0;
self->frames_decoded = 0;
self->has_reference_frame = FALSE;
self->start_code = -1;
}
int plm_video_has_ended(plm_video_t *self) {
return plm_buffer_has_ended(self->buffer);
}
plm_frame_t *plm_video_decode(plm_video_t *self) {
if (!plm_video_has_header(self)) {
return NULL;
}
plm_frame_t *frame = NULL;
do {
if (self->start_code != PLM_START_PICTURE) {
self->start_code =
plm_buffer_find_start_code(self->buffer, PLM_START_PICTURE);
if (self->start_code == -1) {
// If we reached the end of the file and the previously decoded
// frame was a reference frame, we still have to return it.
if (self->has_reference_frame && !self->assume_no_b_frames &&
plm_buffer_has_ended(self->buffer) &&
(self->picture_type == PLM_VIDEO_PICTURE_TYPE_INTRA ||
self->picture_type == PLM_VIDEO_PICTURE_TYPE_PREDICTIVE)) {
self->has_reference_frame = FALSE;
frame = &self->frame_backward;
break;
}
return NULL;
}
}
// Make sure we have a full picture in the buffer before attempting to
// decode it. Sadly, this can only be done by seeking for the start code
// of the next picture. Also, if we didn't find the start code for the
// next picture, but the source has ended, we assume that this last
// picture is in the buffer.
if (plm_buffer_has_start_code(self->buffer, PLM_START_PICTURE) == -1 &&
!plm_buffer_has_ended(self->buffer)) {
return NULL;
}
plm_video_decode_picture(self);
if (self->assume_no_b_frames) {
frame = &self->frame_backward;
} else if (self->picture_type == PLM_VIDEO_PICTURE_TYPE_B) {
frame = &self->frame_current;
} else if (self->has_reference_frame) {
frame = &self->frame_forward;
} else {
self->has_reference_frame = TRUE;
}
} while (!frame);
frame->time = self->time;
self->frames_decoded++;
self->time = (float)self->frames_decoded / self->framerate;
return frame;
}
int plm_video_has_header(plm_video_t *self) {
if (self->has_sequence_header) {
return TRUE;
}
if (self->start_code != PLM_START_SEQUENCE) {
self->start_code =
plm_buffer_find_start_code(self->buffer, PLM_START_SEQUENCE);
}
if (self->start_code == -1) {
return FALSE;
}
if (!plm_video_decode_sequence_header(self)) {
return FALSE;
}
return TRUE;
}
int plm_video_decode_sequence_header(plm_video_t *self) {
int max_header_size = 64 + 2 * 64 * 8; // 64 bit header + 2x 64 byte matrix
if (!plm_buffer_has(self->buffer, max_header_size)) {
return FALSE;
}
self->width = plm_buffer_read(self->buffer, 12);
self->height = plm_buffer_read(self->buffer, 12);
if (self->width <= 0 || self->height <= 0) {
return FALSE;
}
// Skip pixel aspect ratio
plm_buffer_skip(self->buffer, 4);
self->framerate = PLM_VIDEO_PICTURE_RATE[plm_buffer_read(self->buffer, 4)];
// Skip bit_rate, marker, buffer_size and constrained bit
plm_buffer_skip(self->buffer, 18 + 1 + 10 + 1);
// Load custom intra quant matrix?
if (plm_buffer_read(self->buffer, 1)) {
for (int i = 0; i < 64; i++) {
int idx = PLM_VIDEO_ZIG_ZAG[i];
self->intra_quant_matrix[idx] = plm_buffer_read(self->buffer, 8);
}
} else {
memcpy(self->intra_quant_matrix, PLM_VIDEO_INTRA_QUANT_MATRIX, 64);
}
// Load custom non intra quant matrix?
if (plm_buffer_read(self->buffer, 1)) {
for (int i = 0; i < 64; i++) {
int idx = PLM_VIDEO_ZIG_ZAG[i];
self->non_intra_quant_matrix[idx] =
plm_buffer_read(self->buffer, 8);
}
} else {
memcpy(self->non_intra_quant_matrix, PLM_VIDEO_NON_INTRA_QUANT_MATRIX,
64);
}
self->mb_width = (self->width + 15) >> 4;
self->mb_height = (self->height + 15) >> 4;
self->mb_size = self->mb_width * self->mb_height;
self->luma_width = self->mb_width << 4;
self->luma_height = self->mb_height << 4;
self->chroma_width = self->mb_width << 3;
self->chroma_height = self->mb_height << 3;
// Allocate one big chunk of data for all 3 frames = 9 planes
size_t luma_plane_size = self->luma_width * self->luma_height;
size_t chroma_plane_size = self->chroma_width * self->chroma_height;
size_t frame_data_size = (luma_plane_size + 2 * chroma_plane_size);
self->frames_data = (uint8_t *)malloc(frame_data_size * 3);
plm_video_init_frame(self, &self->frame_current,
self->frames_data + frame_data_size * 0);
plm_video_init_frame(self, &self->frame_forward,
self->frames_data + frame_data_size * 1);
plm_video_init_frame(self, &self->frame_backward,
self->frames_data + frame_data_size * 2);
self->has_sequence_header = TRUE;
return TRUE;
}
void plm_video_init_frame(plm_video_t *self, plm_frame_t *frame,
uint8_t *base) {
size_t luma_plane_size = self->luma_width * self->luma_height;
size_t chroma_plane_size = self->chroma_width * self->chroma_height;
frame->width = self->width;
frame->height = self->height;
frame->y.width = self->luma_width;
frame->y.height = self->luma_height;
frame->y.data = base;
frame->cr.width = self->chroma_width;
frame->cr.height = self->chroma_height;
frame->cr.data = base + luma_plane_size;
frame->cb.width = self->chroma_width;
frame->cb.height = self->chroma_height;
frame->cb.data = base + luma_plane_size + chroma_plane_size;
}
void plm_video_decode_picture(plm_video_t *self) {
plm_buffer_skip(self->buffer, 10); // skip temporalReference
self->picture_type = plm_buffer_read(self->buffer, 3);
plm_buffer_skip(self->buffer, 16); // skip vbv_delay
// D frames or unknown coding type
if (self->picture_type <= 0 ||
self->picture_type > PLM_VIDEO_PICTURE_TYPE_B) {
return;
}
// Forward full_px, f_code
if (self->picture_type == PLM_VIDEO_PICTURE_TYPE_PREDICTIVE ||
self->picture_type == PLM_VIDEO_PICTURE_TYPE_B) {
self->motion_forward.full_px = plm_buffer_read(self->buffer, 1);
int f_code = plm_buffer_read(self->buffer, 3);
if (f_code == 0) {
// Ignore picture with zero f_code
return;
}
self->motion_forward.r_size = f_code - 1;
}
// Backward full_px, f_code
if (self->picture_type == PLM_VIDEO_PICTURE_TYPE_B) {
self->motion_backward.full_px = plm_buffer_read(self->buffer, 1);
int f_code = plm_buffer_read(self->buffer, 3);
if (f_code == 0) {
// Ignore picture with zero f_code
return;
}
self->motion_backward.r_size = f_code - 1;
}
plm_frame_t frame_temp = self->frame_forward;
if (self->picture_type == PLM_VIDEO_PICTURE_TYPE_INTRA ||
self->picture_type == PLM_VIDEO_PICTURE_TYPE_PREDICTIVE) {
self->frame_forward = self->frame_backward;
}
// Find the first slice; this skips extension and user data
do {
self->start_code = plm_buffer_next_start_code(self->buffer);
} while (!PLM_START_IS_SLICE(self->start_code));
// Decode all slices
do {
plm_video_decode_slice(self, self->start_code & 0x000000FF);
if (self->macroblock_address == self->mb_size - 1) {
break;
}
self->start_code = plm_buffer_next_start_code(self->buffer);
} while (PLM_START_IS_SLICE(self->start_code));
// If this is a reference picture rotate the prediction pointers
if (self->picture_type == PLM_VIDEO_PICTURE_TYPE_INTRA ||
self->picture_type == PLM_VIDEO_PICTURE_TYPE_PREDICTIVE) {
self->frame_backward = self->frame_current;
self->frame_current = frame_temp;
}
}
void plm_video_decode_slice(plm_video_t *self, int slice) {
self->slice_begin = TRUE;
self->macroblock_address = (slice - 1) * self->mb_width - 1;
// Reset motion vectors and DC predictors
self->motion_backward.h = self->motion_forward.h = 0;
self->motion_backward.v = self->motion_forward.v = 0;
self->dc_predictor[0] = 128;
self->dc_predictor[1] = 128;
self->dc_predictor[2] = 128;
self->quantizer_scale = plm_buffer_read(self->buffer, 5);
// Skip extra
while (plm_buffer_read(self->buffer, 1)) {
plm_buffer_skip(self->buffer, 8);
}
do {
plm_video_decode_macroblock(self);
} while (self->macroblock_address < self->mb_size - 1 &&
plm_buffer_no_start_code(self->buffer));
}
void plm_video_decode_macroblock(plm_video_t *self) {
// Decode self->macroblock_address_increment
int increment = 0;
int t = plm_buffer_read_vlc(self->buffer,
PLM_VIDEO_MACROBLOCK_ADDRESS_INCREMENT);
while (t == 34) {
// macroblock_stuffing
t = plm_buffer_read_vlc(self->buffer,
PLM_VIDEO_MACROBLOCK_ADDRESS_INCREMENT);
}
while (t == 35) {
// macroblock_escape
increment += 33;
t = plm_buffer_read_vlc(self->buffer,
PLM_VIDEO_MACROBLOCK_ADDRESS_INCREMENT);
}
increment += t;
// Process any skipped macroblocks
if (self->slice_begin) {
// The first self->macroblock_address_increment of each slice is
// relative to beginning of the preverious row, not the preverious
// macroblock
self->slice_begin = FALSE;
self->macroblock_address += increment;
} else {
if (self->macroblock_address + increment >= self->mb_size) {
return; // invalid
}
if (increment > 1) {
// Skipped macroblocks reset DC predictors
self->dc_predictor[0] = 128;
self->dc_predictor[1] = 128;
self->dc_predictor[2] = 128;
// Skipped macroblocks in P-pictures reset motion vectors
if (self->picture_type == PLM_VIDEO_PICTURE_TYPE_PREDICTIVE) {
self->motion_forward.h = 0;
self->motion_forward.v = 0;
}
}
// Predict skipped macroblocks
while (increment > 1) {
self->macroblock_address++;
self->mb_row = self->macroblock_address / self->mb_width;
self->mb_col = self->macroblock_address % self->mb_width;
plm_video_predict_macroblock(self);
increment--;
}
self->macroblock_address++;
}
self->mb_row = self->macroblock_address / self->mb_width;
self->mb_col = self->macroblock_address % self->mb_width;
if (self->mb_col >= self->mb_width || self->mb_row >= self->mb_height) {
return; // corrupt stream;
}
// Process the current macroblock
const plm_vlc_t *table = PLM_VIDEO_MACROBLOCK_TYPE[self->picture_type];
self->macroblock_type = plm_buffer_read_vlc(self->buffer, table);
self->macroblock_intra = (self->macroblock_type & 0x01);
self->motion_forward.is_set = (self->macroblock_type & 0x08);
self->motion_backward.is_set = (self->macroblock_type & 0x04);
// Quantizer scale
if ((self->macroblock_type & 0x10) != 0) {
self->quantizer_scale = plm_buffer_read(self->buffer, 5);
}
if (self->macroblock_intra) {
// Intra-coded macroblocks reset motion vectors
self->motion_backward.h = self->motion_forward.h = 0;
self->motion_backward.v = self->motion_forward.v = 0;
} else {
// Non-intra macroblocks reset DC predictors
self->dc_predictor[0] = 128;
self->dc_predictor[1] = 128;
self->dc_predictor[2] = 128;
plm_video_decode_motion_vectors(self);
plm_video_predict_macroblock(self);
}
// Decode blocks
int cbp =
((self->macroblock_type & 0x02) != 0)
? plm_buffer_read_vlc(self->buffer, PLM_VIDEO_CODE_BLOCK_PATTERN)
: (self->macroblock_intra ? 0x3f : 0);
for (int block = 0, mask = 0x20; block < 6; block++) {
if ((cbp & mask) != 0) {
plm_video_decode_block(self, block);
}
mask >>= 1;
}
}
void plm_video_decode_motion_vectors(plm_video_t *self) {
// Forward
if (self->motion_forward.is_set) {
int r_size = self->motion_forward.r_size;
self->motion_forward.h = plm_video_decode_motion_vector(
self, r_size, self->motion_forward.h);
self->motion_forward.v = plm_video_decode_motion_vector(
self, r_size, self->motion_forward.v);
} else if (self->picture_type == PLM_VIDEO_PICTURE_TYPE_PREDICTIVE) {
// No motion information in P-picture, reset vectors
self->motion_forward.h = 0;
self->motion_forward.v = 0;
}
if (self->motion_backward.is_set) {
int r_size = self->motion_backward.r_size;
self->motion_backward.h = plm_video_decode_motion_vector(
self, r_size, self->motion_backward.h);
self->motion_backward.v = plm_video_decode_motion_vector(
self, r_size, self->motion_backward.v);
}
}
int plm_video_decode_motion_vector(plm_video_t *self, int r_size, int motion) {
int fscale = 1 << r_size;
int m_code = plm_buffer_read_vlc(self->buffer, PLM_VIDEO_MOTION);
int r = 0;
int d;
if ((m_code != 0) && (fscale != 1)) {
r = plm_buffer_read(self->buffer, r_size);
d = ((abs(m_code) - 1) << r_size) + r + 1;
if (m_code < 0) {
d = -d;
}
} else {
d = m_code;
}
motion += d;
if (motion > (fscale << 4) - 1) {
motion -= fscale << 5;
} else if (motion < ((-fscale) << 4)) {
motion += fscale << 5;
}
return motion;
}
void plm_video_predict_macroblock(plm_video_t *self) {
int fw_h = self->motion_forward.h;
int fw_v = self->motion_forward.v;
if (self->motion_forward.full_px) {
fw_h <<= 1;
fw_v <<= 1;
}
if (self->picture_type == PLM_VIDEO_PICTURE_TYPE_B) {
int bw_h = self->motion_backward.h;
int bw_v = self->motion_backward.v;
if (self->motion_backward.full_px) {
bw_h <<= 1;
bw_v <<= 1;
}
if (self->motion_forward.is_set) {
plm_video_copy_macroblock(self, fw_h, fw_v, &self->frame_forward);
if (self->motion_backward.is_set) {
plm_video_interpolate_macroblock(self, bw_h, bw_v,
&self->frame_backward);
}
} else {
plm_video_copy_macroblock(self, bw_h, bw_v, &self->frame_backward);
}
} else {
plm_video_copy_macroblock(self, fw_h, fw_v, &self->frame_forward);
}
}
void plm_video_copy_macroblock(plm_video_t *self, int motion_h, int motion_v,
plm_frame_t *d) {
plm_frame_t *s = &self->frame_current;
plm_video_process_macroblock(self, s->y.data, d->y.data, motion_h, motion_v,
16, FALSE);
plm_video_process_macroblock(self, s->cr.data, d->cr.data, motion_h / 2,
motion_v / 2, 8, FALSE);
plm_video_process_macroblock(self, s->cb.data, d->cb.data, motion_h / 2,
motion_v / 2, 8, FALSE);
}
void plm_video_interpolate_macroblock(plm_video_t *self, int motion_h,
int motion_v, plm_frame_t *d) {
plm_frame_t *s = &self->frame_current;
plm_video_process_macroblock(self, s->y.data, d->y.data, motion_h, motion_v,
16, TRUE);
plm_video_process_macroblock(self, s->cr.data, d->cr.data, motion_h / 2,
motion_v / 2, 8, TRUE);
plm_video_process_macroblock(self, s->cb.data, d->cb.data, motion_h / 2,
motion_v / 2, 8, TRUE);
}
#define PLM_BLOCK_SET(DEST, DEST_INDEX, DEST_WIDTH, SOURCE_INDEX, \
SOURCE_WIDTH, BLOCK_SIZE, OP) \
do { \
int dest_scan = DEST_WIDTH - BLOCK_SIZE; \
int source_scan = SOURCE_WIDTH - BLOCK_SIZE; \
for (int y = 0; y < BLOCK_SIZE; y++) { \
for (int x = 0; x < BLOCK_SIZE; x++) { \
DEST[DEST_INDEX] = OP; \
SOURCE_INDEX++; \
DEST_INDEX++; \
} \
SOURCE_INDEX += source_scan; \
DEST_INDEX += dest_scan; \
} \
} while (FALSE)
void plm_video_process_macroblock(plm_video_t *self, uint8_t *d, uint8_t *s,
int motion_h, int motion_v, int block_size,
int interpolate) {
int dw = self->mb_width * block_size;
int hp = motion_h >> 1;
int vp = motion_v >> 1;
int odd_h = (motion_h & 1) == 1;
int odd_v = (motion_v & 1) == 1;
unsigned int si = ((self->mb_row * block_size) + vp) * dw +
(self->mb_col * block_size) + hp;
unsigned int di = (self->mb_row * dw + self->mb_col) * block_size;
unsigned int max_address =
(dw * (self->mb_height * block_size - block_size + 1) - block_size);
if (si > max_address || di > max_address) {
return; // corrupt video
}
#define PLM_MB_CASE(INTERPOLATE, ODD_H, ODD_V, OP) \
case ((INTERPOLATE << 2) | (ODD_H << 1) | (ODD_V)): \
PLM_BLOCK_SET(d, di, dw, si, dw, block_size, OP); \
break
switch ((interpolate << 2) | (odd_h << 1) | (odd_v)) {
PLM_MB_CASE(0, 0, 0, (s[si]));
PLM_MB_CASE(0, 0, 1, (s[si] + s[si + dw] + 1) >> 1);
PLM_MB_CASE(0, 1, 0, (s[si] + s[si + 1] + 1) >> 1);
PLM_MB_CASE(0, 1, 1,
(s[si] + s[si + 1] + s[si + dw] + s[si + dw + 1] + 2) >> 2);
PLM_MB_CASE(1, 0, 0, (d[di] + (s[si]) + 1) >> 1);
PLM_MB_CASE(1, 0, 1,
(d[di] + ((s[si] + s[si + dw] + 1) >> 1) + 1) >> 1);
PLM_MB_CASE(1, 1, 0, (d[di] + ((s[si] + s[si + 1] + 1) >> 1) + 1) >> 1);
PLM_MB_CASE(
1, 1, 1,
(d[di] +
((s[si] + s[si + 1] + s[si + dw] + s[si + dw + 1] + 2) >> 2) +
1) >>
1);
}
#undef PLM_MB_CASE
}
void plm_video_decode_block(plm_video_t *self, int block) {
int n = 0;
uint8_t *quant_matrix;
// Decode DC coefficient of intra-coded blocks
if (self->macroblock_intra) {
int predictor;
int dct_size;
// DC prediction
int plane_index = block > 3 ? block - 3 : 0;
predictor = self->dc_predictor[plane_index];
dct_size =
plm_buffer_read_vlc(self->buffer, PLM_VIDEO_DCT_SIZE[plane_index]);
// Read DC coeff
if (dct_size > 0) {
int differential = plm_buffer_read(self->buffer, dct_size);
if ((differential & (1 << (dct_size - 1))) != 0) {
self->block_data[0] = predictor + differential;
} else {
self->block_data[0] =
predictor + (-(1 << dct_size) | (differential + 1));
}
} else {
self->block_data[0] = predictor;
}
// Save predictor value
self->dc_predictor[plane_index] = self->block_data[0];
// Dequantize + premultiply
self->block_data[0] <<= (3 + 5);
quant_matrix = self->intra_quant_matrix;
n = 1;
} else {
quant_matrix = self->non_intra_quant_matrix;
}
// Decode AC coefficients (+DC for non-intra)
int level = 0;
while (TRUE) {
int run = 0;
uint16_t coeff =
plm_buffer_read_vlc_uint(self->buffer, PLM_VIDEO_DCT_COEFF);
if ((coeff == 0x0001) && (n > 0) &&
(plm_buffer_read(self->buffer, 1) == 0)) {
// end_of_block
break;
}
if (coeff == 0xffff) {
// escape
run = plm_buffer_read(self->buffer, 6);
level = plm_buffer_read(self->buffer, 8);
if (level == 0) {
level = plm_buffer_read(self->buffer, 8);
} else if (level == 128) {
level = plm_buffer_read(self->buffer, 8) - 256;
} else if (level > 128) {
level = level - 256;
}
} else {
run = coeff >> 8;
level = coeff & 0xff;
if (plm_buffer_read(self->buffer, 1)) {
level = -level;
}
}
n += run;
if (n < 0 || n >= 64) {
return; // invalid
}
int de_zig_zagged = PLM_VIDEO_ZIG_ZAG[n];
n++;
// Dequantize, oddify, clip
level <<= 1;
if (!self->macroblock_intra) {
level += (level < 0 ? -1 : 1);
}
level =
(level * self->quantizer_scale * quant_matrix[de_zig_zagged]) >> 4;
if ((level & 1) == 0) {
level -= level > 0 ? 1 : -1;
}
if (level > 2047) {
level = 2047;
} else if (level < -2048) {
level = -2048;
}
// Save premultiplied coefficient
self->block_data[de_zig_zagged] =
level * PLM_VIDEO_PREMULTIPLIER_MATRIX[de_zig_zagged];
}
// Move block to its place
uint8_t *d;
int dw;
int di;
if (block < 4) {
d = self->frame_current.y.data;
dw = self->luma_width;
di = (self->mb_row * self->luma_width + self->mb_col) << 4;
if ((block & 1) != 0) {
di += 8;
}
if ((block & 2) != 0) {
di += self->luma_width << 3;
}
} else {
d = (block == 4) ? self->frame_current.cb.data
: self->frame_current.cr.data;
dw = self->chroma_width;
di = ((self->mb_row * self->luma_width) << 2) + (self->mb_col << 3);
}
int *s = self->block_data;
int si = 0;
if (self->macroblock_intra) {
// Overwrite (no prediction)
if (n == 1) {
int clamped = plm_clamp((s[0] + 128) >> 8);
PLM_BLOCK_SET(d, di, dw, si, 8, 8, clamped);
s[0] = 0;
} else {
plm_video_idct(s);
PLM_BLOCK_SET(d, di, dw, si, 8, 8, plm_clamp(s[si]));
memset(self->block_data, 0, sizeof(self->block_data));
}
} else {
// Add data to the predicted macroblock
if (n == 1) {
int value = (s[0] + 128) >> 8;
PLM_BLOCK_SET(d, di, dw, si, 8, 8, plm_clamp(d[di] + value));
s[0] = 0;
} else {
plm_video_idct(s);
PLM_BLOCK_SET(d, di, dw, si, 8, 8, plm_clamp(d[di] + s[si]));
memset(self->block_data, 0, sizeof(self->block_data));
}
}
}
void plm_video_idct(int *block) {
int b1, b3, b4, b6, b7, tmp1, tmp2, m0, x0, x1, x2, x3, x4, y3, y4, y5, y6,
y7;
// Transform columns
for (int i = 0; i < 8; ++i) {
b1 = block[4 * 8 + i];
b3 = block[2 * 8 + i] + block[6 * 8 + i];
b4 = block[5 * 8 + i] - block[3 * 8 + i];
tmp1 = block[1 * 8 + i] + block[7 * 8 + i];
tmp2 = block[3 * 8 + i] + block[5 * 8 + i];
b6 = block[1 * 8 + i] - block[7 * 8 + i];
b7 = tmp1 + tmp2;
m0 = block[0 * 8 + i];
x4 = ((b6 * 473 - b4 * 196 + 128) >> 8) - b7;
x0 = x4 - (((tmp1 - tmp2) * 362 + 128) >> 8);
x1 = m0 - b1;
x2 = (((block[2 * 8 + i] - block[6 * 8 + i]) * 362 + 128) >> 8) - b3;
x3 = m0 + b1;
y3 = x1 + x2;
y4 = x3 + b3;
y5 = x1 - x2;
y6 = x3 - b3;
y7 = -x0 - ((b4 * 473 + b6 * 196 + 128) >> 8);
block[0 * 8 + i] = b7 + y4;
block[1 * 8 + i] = x4 + y3;
block[2 * 8 + i] = y5 - x0;
block[3 * 8 + i] = y6 - y7;
block[4 * 8 + i] = y6 + y7;
block[5 * 8 + i] = x0 + y5;
block[6 * 8 + i] = y3 - x4;
block[7 * 8 + i] = y4 - b7;
}
// Transform rows
for (int i = 0; i < 64; i += 8) {
b1 = block[4 + i];
b3 = block[2 + i] + block[6 + i];
b4 = block[5 + i] - block[3 + i];
tmp1 = block[1 + i] + block[7 + i];
tmp2 = block[3 + i] + block[5 + i];
b6 = block[1 + i] - block[7 + i];
b7 = tmp1 + tmp2;
m0 = block[0 + i];
x4 = ((b6 * 473 - b4 * 196 + 128) >> 8) - b7;
x0 = x4 - (((tmp1 - tmp2) * 362 + 128) >> 8);
x1 = m0 - b1;
x2 = (((block[2 + i] - block[6 + i]) * 362 + 128) >> 8) - b3;
x3 = m0 + b1;
y3 = x1 + x2;
y4 = x3 + b3;
y5 = x1 - x2;
y6 = x3 - b3;
y7 = -x0 - ((b4 * 473 + b6 * 196 + 128) >> 8);
block[0 + i] = (b7 + y4 + 128) >> 8;
block[1 + i] = (x4 + y3 + 128) >> 8;
block[2 + i] = (y5 - x0 + 128) >> 8;
block[3 + i] = (y6 - y7 + 128) >> 8;
block[4 + i] = (y6 + y7 + 128) >> 8;
block[5 + i] = (x0 + y5 + 128) >> 8;
block[6 + i] = (y3 - x4 + 128) >> 8;
block[7 + i] = (y4 - b7 + 128) >> 8;
}
}
// YCbCr conversion following the BT.601 standard:
// https://infogalactic.com/info/YCbCr#ITU-R_BT.601_conversion
#define PLM_PUT_PIXEL(RI, GI, BI, Y_OFFSET, DEST_OFFSET) \
y = ((frame->y.data[y_index + Y_OFFSET] - 16) * 76309) >> 16; \
dest[d_index + DEST_OFFSET + RI] = plm_clamp(y + r); \
dest[d_index + DEST_OFFSET + GI] = plm_clamp(y - g); \
dest[d_index + DEST_OFFSET + BI] = plm_clamp(y + b);
#define PLM_DEFINE_FRAME_CONVERT_FUNCTION(NAME, BYTES_PER_PIXEL, RI, GI, BI) \
void NAME(plm_frame_t *frame, uint8_t *dest, int stride) { \
int cols = frame->width >> 1; \
int rows = frame->height >> 1; \
int yw = frame->y.width; \
int cw = frame->cb.width; \
for (int row = 0; row < rows; row++) { \
int c_index = row * cw; \
int y_index = row * 2 * yw; \
int d_index = row * 2 * stride; \
for (int col = 0; col < cols; col++) { \
int y; \
int cr = frame->cr.data[c_index] - 128; \
int cb = frame->cb.data[c_index] - 128; \
int r = (cr * 104597) >> 16; \
int g = (cb * 25674 + cr * 53278) >> 16; \
int b = (cb * 132201) >> 16; \
PLM_PUT_PIXEL(RI, GI, BI, 0, 0); \
PLM_PUT_PIXEL(RI, GI, BI, 1, BYTES_PER_PIXEL); \
PLM_PUT_PIXEL(RI, GI, BI, yw, stride); \
PLM_PUT_PIXEL(RI, GI, BI, yw + 1, stride + BYTES_PER_PIXEL); \
c_index += 1; \
y_index += 2; \
d_index += 2 * BYTES_PER_PIXEL; \
} \
} \
}
PLM_DEFINE_FRAME_CONVERT_FUNCTION(plm_frame_to_rgb, 3, 0, 1, 2)
PLM_DEFINE_FRAME_CONVERT_FUNCTION(plm_frame_to_bgr, 3, 2, 1, 0)
PLM_DEFINE_FRAME_CONVERT_FUNCTION(plm_frame_to_rgba, 4, 0, 1, 2)
PLM_DEFINE_FRAME_CONVERT_FUNCTION(plm_frame_to_bgra, 4, 2, 1, 0)
PLM_DEFINE_FRAME_CONVERT_FUNCTION(plm_frame_to_argb, 4, 1, 2, 3)
PLM_DEFINE_FRAME_CONVERT_FUNCTION(plm_frame_to_abgr, 4, 3, 2, 1)
#undef PLM_PUT_PIXEL
#undef PLM_DEFINE_FRAME_CONVERT_FUNCTION
// -----------------------------------------------------------------------------
// plm_audio implementation
// Based on kjmp2 by Martin J. Fiedler
// http://keyj.emphy.de/kjmp2/
static const int PLM_AUDIO_FRAME_SYNC = 0x7ff;
static const int PLM_AUDIO_MPEG_2_5 = 0x0;
static const int PLM_AUDIO_MPEG_2 = 0x2;
static const int PLM_AUDIO_MPEG_1 = 0x3;
static const int PLM_AUDIO_LAYER_III = 0x1;
static const int PLM_AUDIO_LAYER_II = 0x2;
static const int PLM_AUDIO_LAYER_I = 0x3;
static const int PLM_AUDIO_MODE_STEREO = 0x0;
static const int PLM_AUDIO_MODE_JOINT_STEREO = 0x1;
static const int PLM_AUDIO_MODE_DUAL_CHANNEL = 0x2;
static const int PLM_AUDIO_MODE_MONO = 0x3;
static const unsigned short PLM_AUDIO_SAMPLE_RATE[] = {
44100, 48000, 32000, 0, // MPEG-1
22050, 24000, 16000, 0 // MPEG-2
};
static const short PLM_AUDIO_BIT_RATE[] = {
32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, // MPEG-1
8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 // MPEG-2
};
static const int PLM_AUDIO_SCALEFACTOR_BASE[] = { 0x02000000, 0x01965FEA,
0x01428A30 };
static const float PLM_AUDIO_SYNTHESIS_WINDOW[] = {
0.0, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5,
-1.0, -1.0, -1.0, -1.0, -1.5, -1.5, -2.0,
-2.0, -2.5, -2.5, -3.0, -3.5, -3.5, -4.0,
-4.5, -5.0, -5.5, -6.5, -7.0, -8.0, -8.5,
-9.5, -10.5, -12.0, -13.0, -14.5, -15.5, -17.5,
-19.0, -20.5, -22.5, -24.5, -26.5, -29.0, -31.5,
-34.0, -36.5, -39.5, -42.5, -45.5, -48.5, -52.0,
-55.5, -58.5, -62.5, -66.0, -69.5, -73.5, -77.0,
-80.5, -84.5, -88.0, -91.5, -95.0, -98.0, -101.0,
-104.0, 106.5, 109.0, 111.0, 112.5, 113.5, 114.0,
114.0, 113.5, 112.0, 110.5, 107.5, 104.0, 100.0,
94.5, 88.5, 81.5, 73.0, 63.5, 53.0, 41.5,
28.5, 14.5, -1.0, -18.0, -36.0, -55.5, -76.5,
-98.5, -122.0, -147.0, -173.5, -200.5, -229.5, -259.5,
-290.5, -322.5, -355.5, -389.5, -424.0, -459.5, -495.5,
-532.0, -568.5, -605.0, -641.5, -678.0, -714.0, -749.0,
-783.5, -817.0, -849.0, -879.5, -908.5, -935.0, -959.5,
-981.0, -1000.5, -1016.0, -1028.5, -1037.5, -1042.5, -1043.5,
-1040.0, -1031.5, 1018.5, 1000.0, 976.0, 946.5, 911.0,
869.5, 822.0, 767.5, 707.0, 640.0, 565.5, 485.0,
397.0, 302.5, 201.0, 92.5, -22.5, -144.0, -272.5,
-407.0, -547.5, -694.0, -846.0, -1003.0, -1165.0, -1331.5,
-1502.0, -1675.5, -1852.5, -2031.5, -2212.5, -2394.0, -2576.5,
-2758.5, -2939.5, -3118.5, -3294.5, -3467.5, -3635.5, -3798.5,
-3955.0, -4104.5, -4245.5, -4377.5, -4499.0, -4609.5, -4708.0,
-4792.5, -4863.5, -4919.0, -4958.0, -4979.5, -4983.0, -4967.5,
-4931.5, -4875.0, -4796.0, -4694.5, -4569.5, -4420.0, -4246.0,
-4046.0, -3820.0, -3567.0, 3287.0, 2979.5, 2644.0, 2280.5,
1888.0, 1467.5, 1018.5, 541.0, 35.0, -499.0, -1061.0,
-1650.0, -2266.5, -2909.0, -3577.0, -4270.0, -4987.5, -5727.5,
-6490.0, -7274.0, -8077.5, -8899.5, -9739.0, -10594.5, -11464.5,
-12347.0, -13241.0, -14144.5, -15056.0, -15973.5, -16895.5, -17820.0,
-18744.5, -19668.0, -20588.0, -21503.0, -22410.5, -23308.5, -24195.0,
-25068.5, -25926.5, -26767.0, -27589.0, -28389.0, -29166.5, -29919.0,
-30644.5, -31342.0, -32009.5, -32645.0, -33247.0, -33814.5, -34346.0,
-34839.5, -35295.0, -35710.0, -36084.5, -36417.5, -36707.5, -36954.0,
-37156.5, -37315.0, -37428.0, -37496.0, 37519.0, 37496.0, 37428.0,
37315.0, 37156.5, 36954.0, 36707.5, 36417.5, 36084.5, 35710.0,
35295.0, 34839.5, 34346.0, 33814.5, 33247.0, 32645.0, 32009.5,
31342.0, 30644.5, 29919.0, 29166.5, 28389.0, 27589.0, 26767.0,
25926.5, 25068.5, 24195.0, 23308.5, 22410.5, 21503.0, 20588.0,
19668.0, 18744.5, 17820.0, 16895.5, 15973.5, 15056.0, 14144.5,
13241.0, 12347.0, 11464.5, 10594.5, 9739.0, 8899.5, 8077.5,
7274.0, 6490.0, 5727.5, 4987.5, 4270.0, 3577.0, 2909.0,
2266.5, 1650.0, 1061.0, 499.0, -35.0, -541.0, -1018.5,
-1467.5, -1888.0, -2280.5, -2644.0, -2979.5, 3287.0, 3567.0,
3820.0, 4046.0, 4246.0, 4420.0, 4569.5, 4694.5, 4796.0,
4875.0, 4931.5, 4967.5, 4983.0, 4979.5, 4958.0, 4919.0,
4863.5, 4792.5, 4708.0, 4609.5, 4499.0, 4377.5, 4245.5,
4104.5, 3955.0, 3798.5, 3635.5, 3467.5, 3294.5, 3118.5,
2939.5, 2758.5, 2576.5, 2394.0, 2212.5, 2031.5, 1852.5,
1675.5, 1502.0, 1331.5, 1165.0, 1003.0, 846.0, 694.0,
547.5, 407.0, 272.5, 144.0, 22.5, -92.5, -201.0,
-302.5, -397.0, -485.0, -565.5, -640.0, -707.0, -767.5,
-822.0, -869.5, -911.0, -946.5, -976.0, -1000.0, 1018.5,
1031.5, 1040.0, 1043.5, 1042.5, 1037.5, 1028.5, 1016.0,
1000.5, 981.0, 959.5, 935.0, 908.5, 879.5, 849.0,
817.0, 783.5, 749.0, 714.0, 678.0, 641.5, 605.0,
568.5, 532.0, 495.5, 459.5, 424.0, 389.5, 355.5,
322.5, 290.5, 259.5, 229.5, 200.5, 173.5, 147.0,
122.0, 98.5, 76.5, 55.5, 36.0, 18.0, 1.0,
-14.5, -28.5, -41.5, -53.0, -63.5, -73.0, -81.5,
-88.5, -94.5, -100.0, -104.0, -107.5, -110.5, -112.0,
-113.5, -114.0, -114.0, -113.5, -112.5, -111.0, -109.0,
106.5, 104.0, 101.0, 98.0, 95.0, 91.5, 88.0,
84.5, 80.5, 77.0, 73.5, 69.5, 66.0, 62.5,
58.5, 55.5, 52.0, 48.5, 45.5, 42.5, 39.5,
36.5, 34.0, 31.5, 29.0, 26.5, 24.5, 22.5,
20.5, 19.0, 17.5, 15.5, 14.5, 13.0, 12.0,
10.5, 9.5, 8.5, 8.0, 7.0, 6.5, 5.5,
5.0, 4.5, 4.0, 3.5, 3.5, 3.0, 2.5,
2.5, 2.0, 2.0, 1.5, 1.5, 1.0, 1.0,
1.0, 1.0, 0.5, 0.5, 0.5, 0.5, 0.5,
0.5
};
// Quantizer lookup, step 1: bitrate classes
static const uint8_t PLM_AUDIO_QUANT_LUT_STEP_1[2][16] = {
// 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384 <- bitrate
{ 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2 }, // mono
// 16, 24, 28, 32, 40, 48, 56, 64, 80, 96,112,128,160,192 <- bitrate / chan
{ 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2 } // stereo
};
// Quantizer lookup, step 2: bitrate class, sample rate -> B2 table idx, sblimit
#define PLM_AUDIO_QUANT_TAB_A \
(27 | 64) // Table 3-B.2a: high-rate, sblimit = 27
#define PLM_AUDIO_QUANT_TAB_B \
(30 | 64) // Table 3-B.2b: high-rate, sblimit = 30
#define PLM_AUDIO_QUANT_TAB_C 8 // Table 3-B.2c: low-rate, sblimit = 8
#define PLM_AUDIO_QUANT_TAB_D 12 // Table 3-B.2d: low-rate, sblimit = 12
static const uint8_t QUANT_LUT_STEP_2[3][3] = {
// 44.1 kHz, 48 kHz, 32 kHz
{ PLM_AUDIO_QUANT_TAB_C, PLM_AUDIO_QUANT_TAB_C,
PLM_AUDIO_QUANT_TAB_D }, // 32 - 48 kbit/sec/ch
{ PLM_AUDIO_QUANT_TAB_A, PLM_AUDIO_QUANT_TAB_A,
PLM_AUDIO_QUANT_TAB_A }, // 56 - 80 kbit/sec/ch
{ PLM_AUDIO_QUANT_TAB_B, PLM_AUDIO_QUANT_TAB_A,
PLM_AUDIO_QUANT_TAB_B } // 96+ kbit/sec/ch
};
// Quantizer lookup, step 3: B2 table, subband -> nbal, row index
// (upper 4 bits: nbal, lower 4 bits: row index)
static const uint8_t PLM_AUDIO_QUANT_LUT_STEP_3[3][32] = {
// Low-rate table (3-B.2c and 3-B.2d)
{ 0x44, 0x44, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34 },
// High-rate table (3-B.2a and 3-B.2b)
{ 0x43, 0x43, 0x43, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
0x42, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31,
0x31, 0x31, 0x31, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 },
// MPEG-2 LSR table (B.2 in ISO 13818-3)
{ 0x45, 0x45, 0x45, 0x45, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
0x34, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24,
0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24 }
};
// Quantizer lookup, step 4: table row, allocation[] value -> quant table index
static const uint8_t PLM_AUDIO_QUANT_LUT_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
};
typedef struct plm_audio_t {
float time;
int samples_decoded;
int samplerate_index;
int bitrate_index;
int version;
int layer;
int mode;
int bound;
int v_pos;
int next_frame_data_size;
int has_header;
plm_buffer_t *buffer;
int destroy_buffer_when_done;
const plm_quantizer_spec_t *allocation[2][32];
uint8_t scale_factor_info[2][32];
int scale_factor[2][32][3];
int sample[2][32][3];
plm_samples_t samples;
float D[1024];
float V[2][1024];
float U[32];
} plm_audio_t;
int plm_audio_find_frame_sync(plm_audio_t *self);
int plm_audio_decode_header(plm_audio_t *self);
void plm_audio_decode_frame(plm_audio_t *self);
const plm_quantizer_spec_t *plm_audio_read_allocation(plm_audio_t *self, int sb,
int tab3);
void plm_audio_read_samples(plm_audio_t *self, int ch, int sb, int part);
void plm_audio_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 *)malloc(sizeof(plm_audio_t));
memset(self, 0, sizeof(plm_audio_t));
self->samples.count = PLM_AUDIO_SAMPLES_PER_FRAME;
self->buffer = buffer;
self->destroy_buffer_when_done = destroy_when_done;
self->samplerate_index = 3; // Indicates 0
memcpy(self->D, PLM_AUDIO_SYNTHESIS_WINDOW, 512 * sizeof(float));
memcpy(self->D + 512, PLM_AUDIO_SYNTHESIS_WINDOW, 512 * sizeof(float));
// Attempt to decode first header
self->next_frame_data_size = plm_audio_decode_header(self);
return self;
}
void plm_audio_destroy(plm_audio_t *self) {
if (self->destroy_buffer_when_done) {
plm_buffer_destroy(self->buffer);
}
free(self);
}
int plm_audio_has_header(plm_audio_t *self) {
if (self->has_header) {
return TRUE;
}
self->next_frame_data_size = plm_audio_decode_header(self);
return self->has_header;
}
int plm_audio_get_samplerate(plm_audio_t *self) {
return plm_audio_has_header(self)
? PLM_AUDIO_SAMPLE_RATE[self->samplerate_index]
: 0;
}
float plm_audio_get_time(plm_audio_t *self) { return self->time; }
void plm_audio_set_time(plm_audio_t *self, float time) {
self->samples_decoded =
time * (float)PLM_AUDIO_SAMPLE_RATE[self->samplerate_index];
self->time = time;
}
void plm_audio_rewind(plm_audio_t *self) {
plm_buffer_rewind(self->buffer);
self->time = 0;
self->samples_decoded = 0;
self->next_frame_data_size = 0;
}
int plm_audio_has_ended(plm_audio_t *self) {
return plm_buffer_has_ended(self->buffer);
}
plm_samples_t *plm_audio_decode(plm_audio_t *self) {
// Do we have at least enough information to decode the frame header?
if (!self->next_frame_data_size) {
if (!plm_buffer_has(self->buffer, 48)) {
return NULL;
}
self->next_frame_data_size = plm_audio_decode_header(self);
}
if (self->next_frame_data_size == 0 ||
!plm_buffer_has(self->buffer, self->next_frame_data_size << 3)) {
return NULL;
}
plm_audio_decode_frame(self);
self->next_frame_data_size = 0;
self->samples.time = self->time;
self->samples_decoded += PLM_AUDIO_SAMPLES_PER_FRAME;
self->time = (float)self->samples_decoded /
(float)PLM_AUDIO_SAMPLE_RATE[self->samplerate_index];
return &self->samples;
}
int plm_audio_find_frame_sync(plm_audio_t *self) {
size_t i;
for (i = self->buffer->bit_index >> 3; i < self->buffer->length - 1; i++) {
if (self->buffer->bytes[i] == 0xFF &&
(self->buffer->bytes[i + 1] & 0xFE) == 0xFC) {
self->buffer->bit_index = ((i + 1) << 3) + 3;
return TRUE;
}
}
self->buffer->bit_index = (i + 1) << 3;
return FALSE;
}
int plm_audio_decode_header(plm_audio_t *self) {
if (!plm_buffer_has(self->buffer, 48)) {
return 0;
}
plm_buffer_skip_bytes(self->buffer, 0x00);
int sync = plm_buffer_read(self->buffer, 11);
// Attempt to resync if no syncword was found. This sucks balls. The MP2
// stream contains a syncword just before every frame (11 bits set to 1).
// However, this syncword is not guaranteed to not occur elswhere in the
// stream. So, if we have to resync, we also have to check if the header
// (samplerate, bitrate) differs from the one we had before. This all
// may still lead to garbage data being decoded :/
if (sync != PLM_AUDIO_FRAME_SYNC && !plm_audio_find_frame_sync(self)) {
return 0;
}
self->version = plm_buffer_read(self->buffer, 2);
self->layer = plm_buffer_read(self->buffer, 2);
int hasCRC = !plm_buffer_read(self->buffer, 1);
if (self->version != PLM_AUDIO_MPEG_1 ||
self->layer != PLM_AUDIO_LAYER_II) {
return 0;
}
int bitrate_index = plm_buffer_read(self->buffer, 4) - 1;
if (bitrate_index > 13) {
return 0;
}
int samplerate_index = plm_buffer_read(self->buffer, 2);
if (samplerate_index == 3) {
return 0;
}
int padding = plm_buffer_read(self->buffer, 1);
plm_buffer_skip(self->buffer, 1); // f_private
int mode = plm_buffer_read(self->buffer, 2);
// If we already have a header, make sure the samplerate, bitrate and mode
// are still the same, otherwise we might have missed sync.
if (self->has_header &&
(self->bitrate_index != bitrate_index ||
self->samplerate_index != samplerate_index || self->mode != mode)) {
return 0;
}
self->bitrate_index = bitrate_index;
self->samplerate_index = samplerate_index;
self->mode = mode;
self->has_header = TRUE;
// Parse the mode_extension, set up the stereo bound
if (mode == PLM_AUDIO_MODE_JOINT_STEREO) {
self->bound = (plm_buffer_read(self->buffer, 2) + 1) << 2;
} else {
plm_buffer_skip(self->buffer, 2);
self->bound = (mode == PLM_AUDIO_MODE_MONO) ? 0 : 32;
}
// Discard the last 4 bits of the header and the CRC value, if present
plm_buffer_skip(self->buffer, 4);
if (hasCRC) {
plm_buffer_skip(self->buffer, 16);
}
// Compute frame size, check if we have enough data to decode the whole
// frame.
int bitrate = PLM_AUDIO_BIT_RATE[self->bitrate_index];
int samplerate = PLM_AUDIO_SAMPLE_RATE[self->samplerate_index];
int frame_size = (144000 * bitrate / samplerate) + padding;
return frame_size - (hasCRC ? 6 : 4);
}
void plm_audio_decode_frame(plm_audio_t *self) {
// Prepare the quantizer table lookups
int tab3 = 0;
int sblimit = 0;
int tab1 = (self->mode == PLM_AUDIO_MODE_MONO) ? 0 : 1;
int tab2 = PLM_AUDIO_QUANT_LUT_STEP_1[tab1][self->bitrate_index];
tab3 = QUANT_LUT_STEP_2[tab2][self->samplerate_index];
sblimit = tab3 & 63;
tab3 >>= 6;
if (self->bound > sblimit) {
self->bound = sblimit;
}
// Read the allocation information
for (int sb = 0; sb < self->bound; sb++) {
self->allocation[0][sb] = plm_audio_read_allocation(self, sb, tab3);
self->allocation[1][sb] = plm_audio_read_allocation(self, sb, tab3);
}
for (int sb = self->bound; sb < sblimit; sb++) {
self->allocation[0][sb] = self->allocation[1][sb] =
plm_audio_read_allocation(self, sb, tab3);
}
// Read scale factor selector information
int channels = (self->mode == PLM_AUDIO_MODE_MONO) ? 1 : 2;
for (int sb = 0; sb < sblimit; sb++) {
for (int ch = 0; ch < channels; ch++) {
if (self->allocation[ch][sb]) {
self->scale_factor_info[ch][sb] =
plm_buffer_read(self->buffer, 2);
}
}
if (self->mode == PLM_AUDIO_MODE_MONO) {
self->scale_factor_info[1][sb] = self->scale_factor_info[0][sb];
}
}
// Read scale factors
for (int sb = 0; sb < sblimit; sb++) {
for (int ch = 0; ch < channels; ch++) {
if (self->allocation[ch][sb]) {
int *sf = self->scale_factor[ch][sb];
switch (self->scale_factor_info[ch][sb]) {
case 0:
sf[0] = plm_buffer_read(self->buffer, 6);
sf[1] = plm_buffer_read(self->buffer, 6);
sf[2] = plm_buffer_read(self->buffer, 6);
break;
case 1:
sf[0] = sf[1] = plm_buffer_read(self->buffer, 6);
sf[2] = plm_buffer_read(self->buffer, 6);
break;
case 2:
sf[0] = sf[1] = sf[2] =
plm_buffer_read(self->buffer, 6);
break;
case 3:
sf[0] = plm_buffer_read(self->buffer, 6);
sf[1] = sf[2] = plm_buffer_read(self->buffer, 6);
break;
}
}
}
if (self->mode == PLM_AUDIO_MODE_MONO) {
self->scale_factor[1][sb][0] = self->scale_factor[0][sb][0];
self->scale_factor[1][sb][1] = self->scale_factor[0][sb][1];
self->scale_factor[1][sb][2] = self->scale_factor[0][sb][2];
}
}
// Coefficient input and reconstruction
int out_pos = 0;
for (int part = 0; part < 3; part++) {
for (int granule = 0; granule < 4; granule++) {
// Read the samples
for (int sb = 0; sb < self->bound; sb++) {
plm_audio_read_samples(self, 0, sb, part);
plm_audio_read_samples(self, 1, sb, part);
}
for (int sb = self->bound; sb < sblimit; sb++) {
plm_audio_read_samples(self, 0, sb, part);
self->sample[1][sb][0] = self->sample[0][sb][0];
self->sample[1][sb][1] = self->sample[0][sb][1];
self->sample[1][sb][2] = self->sample[0][sb][2];
}
for (int sb = sblimit; sb < 32; sb++) {
self->sample[0][sb][0] = 0;
self->sample[0][sb][1] = 0;
self->sample[0][sb][2] = 0;
self->sample[1][sb][0] = 0;
self->sample[1][sb][1] = 0;
self->sample[1][sb][2] = 0;
}
// Synthesis loop
for (int p = 0; p < 3; p++) {
// Shifting step
self->v_pos = (self->v_pos - 64) & 1023;
for (int ch = 0; ch < 2; ch++) {
plm_audio_matrix_transform(self->sample[ch], p, self->V[ch],
self->v_pos);
// Build U, windowing, calculate output
memset(self->U, 0, sizeof(self->U));
int d_index = 512 - (self->v_pos >> 1);
int v_index = (self->v_pos % 128) >> 1;
while (v_index < 1024) {
for (int i = 0; i < 32; ++i) {
self->U[i] +=
self->D[d_index++] * self->V[ch][v_index++];
}
v_index += 128 - 32;
d_index += 64 - 32;
}
d_index -= (512 - 32);
v_index = (128 - 32 + 1024) - v_index;
while (v_index < 1024) {
for (int i = 0; i < 32; ++i) {
self->U[i] +=
self->D[d_index++] * self->V[ch][v_index++];
}
v_index += 128 - 32;
d_index += 64 - 32;
}
// Output samples
#ifdef PLM_AUDIO_SEPARATE_CHANNELS
float *out_channel =
ch == 0 ? self->samples.left : self->samples.right;
for (int j = 0; j < 32; j++) {
out_channel[out_pos + j] = self->U[j] / 2147418112.0f;
}
#else
for (int j = 0; j < 32; j++) {
self->samples.interleaved[((out_pos + j) << 1) + ch] =
self->U[j] / 2147418112.0f;
}
#endif
} // End of synthesis channel loop
out_pos += 32;
} // End of synthesis sub-block loop
} // Decoding of the granule finished
}
plm_buffer_align(self->buffer);
}
const plm_quantizer_spec_t *plm_audio_read_allocation(plm_audio_t *self, int sb,
int tab3) {
int tab4 = PLM_AUDIO_QUANT_LUT_STEP_3[tab3][sb];
int qtab = PLM_AUDIO_QUANT_LUT_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] + ((1 << shift) >> 1)) >> shift;
}
// Decode samples
int adj = q->levels;
if (q->group) {
// Decode grouped samples
val = plm_buffer_read(self->buffer, q->bits);
sample[0] = val % adj;
val /= adj;
sample[1] = val % adj;
sample[2] = val / adj;
} else {
// Decode direct samples
sample[0] = plm_buffer_read(self->buffer, q->bits);
sample[1] = plm_buffer_read(self->buffer, q->bits);
sample[2] = plm_buffer_read(self->buffer, q->bits);
}
// Postmultiply samples
int scale = 65536 / (adj + 1);
adj = ((adj + 1) >> 1) - 1;
val = (adj - sample[0]) * scale;
sample[0] = (val * (sf >> 12) + ((val * (sf & 4095) + 2048) >> 12)) >> 12;
val = (adj - sample[1]) * scale;
sample[1] = (val * (sf >> 12) + ((val * (sf & 4095) + 2048) >> 12)) >> 12;
val = (adj - sample[2]) * scale;
sample[2] = (val * (sf >> 12) + ((val * (sf & 4095) + 2048) >> 12)) >> 12;
}
void plm_audio_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;
}
#endif // PL_MPEG_IMPLEMENTATION
#ifndef __clang__
#pragma GCC optimize("O3")
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <st3m_audio.h>
#include <st3m_media.h>
#include "ctx.h"
#define PL_MPEG_IMPLEMENTATION
#include "pl_mpeg.h"
typedef struct {
st3m_media control;
plm_t *plm;
uint8_t *frame_data;
int width;
int height;
int frame_drop;
int sample_rate;
int frame_no;
int prev_frame_no;
int prev_prev_frame_no;
// last decoded frame contained chroma samples
// this allows us to take a grayscale fast-path
unsigned last_frame_chroma : 1;
unsigned color : 1;
// whether we smooth the video when scaling it up
unsigned smoothing : 1;
unsigned video : 1;
unsigned audio : 1;
unsigned loop : 1;
float scale;
} mpg1_state;
static void mpg1_on_video(plm_t *player, plm_frame_t *frame, void *user);
static void mpg1_on_audio(plm_t *player, plm_samples_t *samples, void *user);
static void mpg1_think(st3m_media *media, float ms_elapsed) {
mpg1_state *self = (void *)media;
float elapsed_time = ms_elapsed / 1000.0;
double seek_to = -1;
if (self->control.seek >= 0.0) {
seek_to = self->control.seek * self->control.duration;
self->control.seek = -1;
}
if (elapsed_time > 1.0 / 25.0) {
elapsed_time = 1.0 / 25.0;
}
if (self->control.paused) elapsed_time = 0;
// Seek or advance decode
if (seek_to != -1) {
// XXX : clear queued audio
plm_seek(self->plm, seek_to, FALSE);
} else {
plm_decode(self->plm, elapsed_time);
}
if (plm_has_ended(self->plm)) {
}
}
static inline int memcpy_chroma(uint8_t *restrict target, uint8_t *restrict src,
int count) {
int ret = 0;
for (int i = 0; i < count; i++) {
uint8_t val = src[i];
target[i] = val;
ret = (ret | (val != 128));
}
return ret;
}
static void mpg1_on_video(plm_t *mpeg, plm_frame_t *frame, void *user) {
mpg1_state *self = (mpg1_state *)user;
self->frame_no++;
self->width = frame->y.width;
self->height = frame->y.height;
memcpy(self->frame_data, frame->y.data, frame->y.width * frame->y.height);
if (self->color) {
/* copy u and v components */
self->last_frame_chroma = memcpy_chroma(
self->frame_data + frame->y.width * frame->y.height, frame->cb.data,
(frame->y.width / 2) * (frame->y.height / 2));
self->last_frame_chroma = memcpy_chroma(
self->frame_data + frame->y.width * frame->y.height +
(frame->y.width / 2) * (frame->y.height / 2),
frame->cr.data, (frame->y.width / 2) * (frame->y.height / 2));
}
}
static void mpg1_on_audio(plm_t *mpeg, plm_samples_t *samples, void *user) {
mpg1_state *mpg1 = user;
if (!mpg1->control.audio_buffer) return;
// if (self->control.paused) return;
if (mpg1->sample_rate == 44100) {
int phase = 0;
for (int i = 0; i < samples->count; i++) {
again:
mpg1->control.audio_buffer[mpg1->control.audio_w++] =
samples->interleaved[i * 2] * 20000;
if (mpg1->control.audio_w >= AUDIO_BUF_SIZE)
mpg1->control.audio_w = 0;
mpg1->control.audio_buffer[mpg1->control.audio_w++] =
samples->interleaved[i * 2 + 1] * 20000;
if (mpg1->control.audio_w >= AUDIO_BUF_SIZE)
mpg1->control.audio_w = 0;
phase += ((48000 / 44100.0) - 1.0) * 65536;
if (phase > 65536) {
phase -= 65536;
phase -= ((48000 / 44100.0) - 1.0) * 65536;
goto again;
}
}
} else
for (int i = 0; i < samples->count; i++) {
mpg1->control.audio_buffer[mpg1->control.audio_w++] =
samples->interleaved[i * 2] * 20000;
if (mpg1->control.audio_w >= AUDIO_BUF_SIZE)
mpg1->control.audio_w = 0;
mpg1->control.audio_buffer[mpg1->control.audio_w++] =
samples->interleaved[i * 2 + 1] * 20000;
if (mpg1->control.audio_w >= AUDIO_BUF_SIZE)
mpg1->control.audio_w = 0;
}
}
static void mpg1_draw(st3m_media *media, Ctx *ctx) {
mpg1_state *mpg1 = (mpg1_state *)media;
{
float dim = 240 * mpg1->scale;
if (mpg1->video) {
float scale = dim / mpg1->width;
float scaleh = dim / mpg1->height;
if (scaleh < scale) scale = scaleh;
char eid[16];
sprintf(eid, "%i", mpg1->frame_no);
if (mpg1->frame_no != mpg1->prev_frame_no) {
if (mpg1->frame_no <
20) { // ensure we've filled at least some complete frames
ctx_rectangle(ctx, -120, -120, 240, 240);
ctx_gray(ctx, 0.0);
ctx_fill(ctx);
}
ctx_translate(ctx, -dim / 2, -dim / 2);
ctx_translate(ctx, (dim - mpg1->width * scale) / 2.0,
(dim - mpg1->height * scale) / 2.0);
ctx_scale(ctx, scale, scale);
ctx_rectangle(ctx, 0, 2, dim, dim - 1);
ctx_define_texture(ctx, eid, mpg1->width, mpg1->height,
mpg1->width,
mpg1->last_frame_chroma ? CTX_FORMAT_YUV420
: CTX_FORMAT_GRAY8,
mpg1->frame_data, NULL);
ctx_image_smoothing(ctx, mpg1->smoothing);
ctx_compositing_mode(ctx, CTX_COMPOSITE_COPY);
ctx_fill(ctx);
char eid[16];
sprintf(eid, "%i", mpg1->prev_prev_frame_no);
ctx_drop_eid(ctx, eid);
mpg1->prev_prev_frame_no = mpg1->prev_frame_no;
mpg1->prev_frame_no = mpg1->frame_no;
} else {
// do nothing, keep display contents
}
} else {
ctx_rgb(ctx, 0.2, 0.3, 0.4);
ctx_fill(ctx);
}
}
}
static void mpg1_destroy(st3m_media *media) {
mpg1_state *self = (void *)media;
plm_destroy(self->plm);
free(self->frame_data);
free(self);
}
st3m_media *st3m_media_load_mpg1(const char *path) {
mpg1_state *self = (mpg1_state *)malloc(sizeof(mpg1_state));
memset(self, 0, sizeof(mpg1_state));
self->control.draw = mpg1_draw;
self->control.think = mpg1_think;
self->control.destroy = mpg1_destroy;
self->plm = plm_create_with_filename(path);
self->color = 1;
self->last_frame_chroma = 0;
self->prev_frame_no = 255; // anything but 0
self->scale = 0.75;
self->audio = 1;
self->video = 1;
self->loop = 0;
self->width = 0;
self->height = 0;
self->smoothing = 0;
self->frame_drop = 1;
if ((!self->plm) || (plm_get_width(self->plm) == 0)) {
printf("Couldn't open %s", path);
free(self);
return NULL;
}
self->sample_rate = plm_get_samplerate(self->plm);
self->control.duration = plm_get_duration(self->plm);
plm_set_video_decode_callback(self->plm, mpg1_on_video, self);
plm_set_audio_decode_callback(self->plm, mpg1_on_audio, self);
plm_set_video_enabled(self->plm, self->video);
plm_set_loop(self->plm, self->loop);
plm_set_audio_enabled(self->plm, self->audio);
plm_set_audio_stream(self->plm, 0);
if (plm_get_num_audio_streams(self->plm) > 0) {
plm_set_audio_lead_time(self->plm, 0.05);
}
self->frame_data =
(uint8_t *)malloc(plm_get_width(self->plm) * plm_get_height(self->plm) *
2); // XXX : this is not quite right
mpg1_think((st3m_media *)self, 0); // the frame is constructed in think
return (st3m_media *)self;
}
from st3m.application import Application, ApplicationContext
from st3m.ui.view import ViewManager
from st3m.goose import Optional
from st3m.input import InputState
import st3m.run
import media
import os
from ctx import Context
class JukeBox(Application):
def __init__(self, app_ctx: ApplicationContext) -> None:
super().__init__(app_ctx)
self._streams = [
"http://radio-paralax.de:8000/",
"http://stream6.jungletrain.net:8000/",
"http://air.doscast.com:8054/livehitradio",
"http://lyd.nrk.no/nrk_radio_jazz_mp3_l",
"http://lyd.nrk.no/nrk_radio_mp3_mp3_l",
# "http://lyd.nrk.no/nrk_radio_alltid_nyheter_mp3_l",
# "http://pippin.gimp.org/tmp/b71207f10d522d354a001768e21a78fe"
]
for entry in os.ilistdir("/sd/"):
if entry[1] == 0x8000:
if (
entry[0].endswith(".mp3")
or entry[0].endswith(".mod")
or entry[0].endswith(".mpg")
):
self._streams.insert(0, "/sd/" + entry[0])
self._stream_no = 0
def load_stream(self) -> None:
media.stop()
self._filename = self._streams[self._stream_no]
print("loading " + self._filename)
media.load(self._filename)
def think(self, ins: InputState, delta_ms: int) -> None:
super().think(ins, delta_ms)
if self.input.buttons.app.right.pressed: # or media.get_position() >= 1.0:
self._stream_no += 1
if self._stream_no >= len(self._streams):
self._stream_no = len(self._streams) - 1
self.load_stream()
if self.input.buttons.app.left.pressed:
self._stream_no -= 1
if self._stream_no < 0:
self._stream_no = 0
self.load_stream()
media.think(delta_ms)
def draw(self, ctx: Context) -> None:
media.draw(ctx)
def on_enter(self, vm: Optional[ViewManager]) -> None:
super().on_enter(vm)
self.load_stream()
def on_exit(self) -> None:
media.stop()
if __name__ == "__main__":
st3m.run.run_view(JukeBox(ApplicationContext()))
[app]
name = "Wurzelitzer"
menu = "Music"
[entry]
class = "JukeBox"
[metadata]
author = "Flow3r Badge Authors"
license = "LGPL-3.0-only"
url = "https://git.flow3r.garden/flow3r/flow3r-firmware"
from ctx import Context
def stop() -> None:
"""
Stops media playback, frees resources.
"""
...
def load(path: str) -> int:
"""
Load path
"""
...
def draw(ctx: Context) -> None:
"""
Draws current state of media object to provided ctx context.
"""
...
def think(ms: float) -> None:
"""
Process ms amounts of media, queuing PCM data and preparing for draw()
"""
...
dummy/
\ No newline at end of file