diff --git a/components/audio_mod/CMakeLists.txt b/components/audio_mod/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..c8225c633e1e910cdd6f726bb3df42fc95318f84 --- /dev/null +++ b/components/audio_mod/CMakeLists.txt @@ -0,0 +1,8 @@ +idf_component_register( + SRCS + audio_mod.c + INCLUDE_DIRS + . + ../ctx + ../st3m +) diff --git a/components/audio_mod/audio_mod.c b/components/audio_mod/audio_mod.c new file mode 100644 index 0000000000000000000000000000000000000000..404ca18408c18b7f0bf075b782776bc63a493d66 --- /dev/null +++ b/components/audio_mod/audio_mod.c @@ -0,0 +1,112 @@ +#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; +} 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_move_to(ctx, -90, 0); + ctx_text(ctx, buf); + ctx_fill(ctx); +} + +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); + 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; + } + + return (st3m_media *)self; +} diff --git a/components/audio_mod/pocketmod.h b/components/audio_mod/pocketmod.h new file mode 100644 index 0000000000000000000000000000000000000000..f6e6524d2cb0268786c4f6b58ece4badf22384ac --- /dev/null +++ b/components/audio_mod/pocketmod.h @@ -0,0 +1,969 @@ +/* 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. + +*******************************************************************************/ diff --git a/components/audio_mp3/CMakeLists.txt b/components/audio_mp3/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..e64b76d64965c430c5e0e73fae8059e55b1c24fa --- /dev/null +++ b/components/audio_mp3/CMakeLists.txt @@ -0,0 +1,8 @@ +idf_component_register( + SRCS + audio_mp3.c + INCLUDE_DIRS + . + ../ctx + ../st3m +) diff --git a/components/audio_mp3/LICENSE b/components/audio_mp3/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..2c4afabdb632b5d68e60c9fe9fe0b0819676a729 --- /dev/null +++ b/components/audio_mp3/LICENSE @@ -0,0 +1,117 @@ +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/> + diff --git a/components/audio_mp3/audio_mp3.c b/components/audio_mp3/audio_mp3.c new file mode 100644 index 0000000000000000000000000000000000000000..0d8993c7735c2276d6168b97f1826891fcf7bdd5 --- /dev/null +++ b/components/audio_mp3/audio_mp3.c @@ -0,0 +1,335 @@ +#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/sockets.h" +#include "lwip/netdb.h" +#include "lwip/ip4.h" +#include "lwip/igmp.h" + +#include "ctx.h" + +//#define MINIMP3_ONLY_MP3 +#define MINIMP3_NONSTANDARD_BUT_LOGICAL +#define MINIMP3_NO_SIMD +#define MINIMP3_IMPLEMENTATION +#include "minimp3.h" + +#define BUFFER_SIZE (32 * 1024) + +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; +} mp3_state; + +static int has_data(mp3_state *mp3) +{ + 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 + (16 * 1024) >= mp3->count) { + memmove(mp3->data, &mp3->data[mp3->pos], mp3->count - mp3->pos); + mp3->offset += mp3->pos; + mp3->count -= mp3->pos; + mp3->pos = 0; + } + + if ((mp3->size - mp3->count > 0) && has_data(mp3)) + { + int desire_bytes = (mp3->size-mp3->count); + 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; +#if 1 + 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); +#endif +} + +static void mp3_think(st3m_media *media, float ms_elapsed) { + mp3_state *self = (void *)media; + + mp3_fetch_data(self); + + 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; + 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; + 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]; + 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]; + 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; + + 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->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; + + printf ("%i\n", __LINE__);fflush(0);sleep(1); + if (!strncmp (path, "http://", 7)) + { + int port = 80; + char *hostname = strdup (path + 7); + char *rest = NULL; + self->buffer_size = 64 * 1024; + rest = strchr(hostname, '/')+1; + strchr (hostname, '/')[0] = 0; + if (strchr (hostname, ':')) + { + port = atoi (strchr (hostname, ':')+1); + strchr (hostname, ':')[0] = 0; + } + + printf (" {%s} {%i} {%s}\n", hostname, port, rest); + fflush(0);sleep(1); + + 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; + } + 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; + + // prebuffer a full fill - at least we get to play this without glitches + do { + int desire = self->size - self->count; + if (desire > 4096) desire = 4096; + int count = read (self->socket, self->data + self->count, desire); + self->count += count; + } while (self->count < self->size); + + mp3dec_init(&self->mp3d); + self->control.duration = 1200; + free (hostname); + 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 = "Anonymous"; + 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) { + printf("!!!!bo\n"); + free(self); + return NULL; + } + mp3dec_init(&self->mp3d); + self->control.duration = 1200.0; + return(st3m_media*) self; +} diff --git a/components/audio_mp3/minimp3.h b/components/audio_mp3/minimp3.h new file mode 100644 index 0000000000000000000000000000000000000000..721e098eccbba66dbe850a74b75af601f0e9bdfd --- /dev/null +++ b/components/audio_mp3/minimp3.h @@ -0,0 +1,2174 @@ +#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 */ diff --git a/components/micropython/usermodule/micropython.cmake b/components/micropython/usermodule/micropython.cmake index 171b8df593d9cc7897d26467fcbe594a411ca688..14551f6e26ce70078c9cce90234b942a17c9f55a 100644 --- a/components/micropython/usermodule/micropython.cmake +++ b/components/micropython/usermodule/micropython.cmake @@ -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 diff --git a/components/micropython/usermodule/mp_media.c b/components/micropython/usermodule/mp_media.c new file mode 100644 index 0000000000000000000000000000000000000000..aa813913f6683e8384b89ff202b439278d043f36 --- /dev/null +++ b/components/micropython/usermodule/mp_media.c @@ -0,0 +1,50 @@ +#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); diff --git a/components/st3m/CMakeLists.txt b/components/st3m/CMakeLists.txt index e0beec286e02394ad7b271f7b9dc03047f701fb2..81089d86b9630b9043380ded433b6a6feedf4b78 100644 --- a/components/st3m/CMakeLists.txt +++ b/components/st3m/CMakeLists.txt @@ -15,6 +15,8 @@ idf_component_register( st3m_usb_msc.c st3m_usb.c st3m_console.c + st3m_media.c + st3m_mode.c st3m_mode.c st3m_captouch.c st3m_ringbuffer.c @@ -36,6 +38,9 @@ idf_component_register( esp_timer esp_netif usb + video_mpeg + audio_mod + audio_mp3 ) idf_component_get_property(tusb_lib tinyusb COMPONENT_LIB) diff --git a/components/st3m/st3m_media.c b/components/st3m/st3m_media.c new file mode 100644 index 0000000000000000000000000000000000000000..021269a43e85bfe5348e1f8386470af7caa90af5 --- /dev/null +++ b/components/st3m/st3m_media.c @@ -0,0 +1,136 @@ +#include "st3m_media.h" +#include "st3m_audio.h" + +#include <math.h> +#include <stdio.h> +#include <string.h> + +#include "esp_log.h" +#include "freertos/FreeRTOS.h" + +#ifdef CONFIG_FLOW3R_CTX_FLAVOUR_FULL +static st3m_media *audio_media = NULL; + +// XXX should be refactored to either be temporary SPIRAM allocation +// or a static shared global pcm queuing API +// +static int16_t audio_buffer[AUDIO_BUF_SIZE]; + +void st3m_media_audio_out(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; +} + +void st3m_media_stop(void) { + if (audio_media && audio_media->destroy) audio_media->destroy(audio_media); + audio_media = 0; + st3m_audio_set_player_function(st3m_audio_player_function_dummy); +} + +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); + +int st3m_media_load(const char *path) { + if (strstr(path, ".mpg")) { + st3m_media_stop(); + audio_media = st3m_media_load_mpg1(path); + } else if (strstr(path, ".mod")) { + st3m_media_stop(); + audio_media = st3m_media_load_mod(path); + } else if (strstr(path, "mp3") || !strncmp (path, "http://", 7)) { + st3m_media_stop(); + audio_media = st3m_media_load_mp3(path); + } + + if (!audio_media) return 0; + + st3m_audio_set_player_function(st3m_media_audio_out); + audio_media->audio_buffer = audio_buffer; + audio_media->audio_r = 0; + audio_media->audio_w = 1; + + return 1; +} + +#endif diff --git a/components/st3m/st3m_media.h b/components/st3m/st3m_media.h new file mode 100644 index 0000000000000000000000000000000000000000..fbf928b8eb164fc7e4824dc15f6c6d27bb07be5e --- /dev/null +++ b/components/st3m/st3m_media.h @@ -0,0 +1,103 @@ +#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); + diff --git a/components/video_mpeg/CMakeLists.txt b/components/video_mpeg/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..50e483046ccb5c3c2a6b1dd6082094dcb4659e26 --- /dev/null +++ b/components/video_mpeg/CMakeLists.txt @@ -0,0 +1,8 @@ +idf_component_register( + SRCS + video_mpeg.c + INCLUDE_DIRS + . + ../ctx + ../st3m +) diff --git a/components/video_mpeg/pl_mpeg.h b/components/video_mpeg/pl_mpeg.h new file mode 100644 index 0000000000000000000000000000000000000000..d24b3e83bb1e891cf23868664685ee6ad8c5e505 --- /dev/null +++ b/components/video_mpeg/pl_mpeg.h @@ -0,0 +1,4207 @@ +/* +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 diff --git a/components/video_mpeg/video_mpeg.c b/components/video_mpeg/video_mpeg.c new file mode 100644 index 0000000000000000000000000000000000000000..f78259d4b6c53fc7c7c160f29286be7d457ece36 --- /dev/null +++ b/components/video_mpeg/video_mpeg.c @@ -0,0 +1,236 @@ +#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(mpg1_state *self, float ms_elapsed) { + 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 (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 < + 10) { // 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, 0, dim, dim); + 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->smoothing = 0; + self->frame_drop = 1; + if (!self->plm) { + 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 riught + + mpg1_think(self, 0); // the frame is constructed in think + return (st3m_media *)self; +} diff --git a/python_payload/apps/mp3/__init__.py b/python_payload/apps/mp3/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..73c9e3c856b8497011fe2d5cea27b73affc0eb0c --- /dev/null +++ b/python_payload/apps/mp3/__init__.py @@ -0,0 +1,23 @@ +from st3m.application import Application, ApplicationContext +from st3m.input import InputState +import st3m.run +import media +from ctx import Context + +class MediaMp3(Application): + def __init__(self, app_ctx: ApplicationContext) -> None: + super().__init__(app_ctx) + self._filename = "/sd/toms_diner.mp3" + + def draw(self, ctx: Context) -> None: + media.draw(ctx) + + def on_enter(self, vm: Optional[ViewManager]) -> None: + super().on_enter(vm) + media.load(self._filename) + + def on_exit(self) -> None: + media.stop() + +if __name__ == '__main__': + st3m.run.run_view(MediaMp3(ApplicationContext())) diff --git a/python_payload/apps/mp3/flow3r.toml b/python_payload/apps/mp3/flow3r.toml new file mode 100644 index 0000000000000000000000000000000000000000..b6226206322a09de3455321221120f7c2f667889 --- /dev/null +++ b/python_payload/apps/mp3/flow3r.toml @@ -0,0 +1,11 @@ +[app] +name = "MediaMP3" +menu = "Apps" + +[entry] +class = "MediaMp3" + +[metadata] +author = "Flow3r Badge Authors" +license = "LGPL-3.0-only" +url = "https://git.flow3r.garden/flow3r/flow3r-firmware" diff --git a/python_payload/apps/mpg1/__init__.py b/python_payload/apps/mpg1/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f2466e9c862e22640b875fa8fb1a60425edaa32f --- /dev/null +++ b/python_payload/apps/mpg1/__init__.py @@ -0,0 +1,29 @@ +from st3m.application import Application, ApplicationContext +from st3m.input import InputState +import st3m.run +import media +from ctx import Context + +class MediaMpgVideo(Application): + def __init__(self, app_ctx: ApplicationContext) -> None: + super().__init__(app_ctx) + # self._filename = "/sd/toms_diner.mp3" + # self._filename = "/sd/fanitullen.mp3" + self._filename = "/sd/alien.mpg" + # self._filename = "/sd/bapple.mpg" + # self._filename = "/sd/video.mpg" + # self._filename = "/sd/bunny.mpg" + # self._filename = "/sd/bjork-160x120.mpg" + + def draw(self, ctx: Context) -> None: + media.draw(ctx) + + def on_enter(self, vm: Optional[ViewManager]) -> None: + super().on_enter(vm) + media.load(self._filename) + + def on_exit(self) -> None: + media.stop() + +if __name__ == '__main__': + st3m.run.run_view(MediaMpgVideo(ApplicationContext())) diff --git a/python_payload/apps/mpg1/flow3r.toml b/python_payload/apps/mpg1/flow3r.toml new file mode 100644 index 0000000000000000000000000000000000000000..7f180ae9cdcc9c38ce598928a90ca6bd0a24ba9c --- /dev/null +++ b/python_payload/apps/mpg1/flow3r.toml @@ -0,0 +1,11 @@ +[app] +name = "MediaMPEG1" +menu = "Apps" + +[entry] +class = "MediaMpgVideo" + +[metadata] +author = "Flow3r Badge Authors" +license = "LGPL-3.0-only" +url = "https://git.flow3r.garden/flow3r/flow3r-firmware" diff --git a/python_payload/main.py b/python_payload/main.py index 51fb0c396be75622570185dcf80584405a06f866..1fbaf1b7306db561a8915497bbe05c0577193e3e 100644 --- a/python_payload/main.py +++ b/python_payload/main.py @@ -1,3 +1,8 @@ from st3m.run import run_main +import network +station = network.WLAN(network.STA_IF) +station.active(True) +station.connect("Camp2023-open") + run_main() diff --git a/python_payload/mypystubs/ctx.pyi b/python_payload/mypystubs/ctx.pyi index bbd830b3767a2d469112a65c174b4e9521c99cd5..7eff84ceb5e96da6d8bc749f4372f46b8ce899fa 100644 --- a/python_payload/mypystubs/ctx.pyi +++ b/python_payload/mypystubs/ctx.pyi @@ -75,22 +75,6 @@ class Context(Protocol): should be balanced. """ pass - def start_frame(self) -> "Context": - """ - Prepare for rendering a new frame, clears internal drawlist and - initializes the state. - - TODO(q3k): we probably shouldn't expose this - """ - pass - def end_frame(self) -> "Context": - """ - We're done rendering a frame, this does nothing on a context created for - a framebuffer, where drawing commands are immediate. - - TODO(q3k): we probably shouldn't expose this - """ - pass def start_group(self) -> "Context": """ Start a compositing group. @@ -194,24 +178,30 @@ class Context(Protocol): pass def rel_line_to(self, x: float, y: float) -> "Context": """ - TOD(q3k): document + Adds a line segment from the current point to current_x + x, + current_y + y. """ pass def rel_move_to(self, x: float, y: float) -> "Context": """ - TOD(q3k): document + Moves the virtual pen a relative amount without adding a segment to + the current path. """ pass def rel_curve_to( self, cx0: float, cy0: float, cx1: float, cy1: float, x: float, y: float ) -> "Context": """ - TOD(q3k): document + Adds a cubic bezier segment using relative coordinates, behaves + like curve_to but all coordinate arguments are relative to the + coordinates of the virtual pen when called. """ pass def rel_quad_to(self, cx: float, cy: float, x: float, y: float) -> "Context": """ - TOD(q3k): document + Adds a quadratic bezier segment using relative coordinates, behaves + like quad_to but all coordinate arguments are relative to the + coordinates of the virtual pen when called. """ pass def rel_arc_to( @@ -262,17 +252,14 @@ class Context(Protocol): pass def fill(self) -> "Context": """ - TOD(q3k): document + Fill the current path with the current source (color, gradient or + texture). """ pass def stroke(self) -> "Context": """ - TOD(q3k): document - """ - pass - def paint(self) -> "Context": - """ - TOD(q3k): document + Stroke the current path with the current source (color, gradient or + texture), with current line_width """ pass def linear_gradient(self, x0: float, y0: float, x1: float, y1: float) -> "Context": @@ -305,12 +292,13 @@ class Context(Protocol): pass def logo(self, x: float, y: float, dim: float) -> "Context": """ - TOD(q3k): document + Draws the ctx logo, centered at x,y with a footprint of roughly dim + units. """ pass def text(self, text: str) -> "Context": """ - TOD(q3k): document + Add a text fragment using the current fill source, font and font_size. """ pass def scope(self) -> "Context": diff --git a/python_payload/mypystubs/mpeg1video.pyi b/python_payload/mypystubs/mpeg1video.pyi new file mode 100644 index 0000000000000000000000000000000000000000..375504243384c6066df633fe553f1c060be920f2 --- /dev/null +++ b/python_payload/mypystubs/mpeg1video.pyi @@ -0,0 +1,20 @@ +from ctx import Context + +def load(path: str) -> int: + """ + Load path + """ + ... + +def iterate(ctx: Context) -> int: + """ + Iterates video decoding, draws frame to ctx. + Returns 0 if video is still playing + """ + ... + +def cleanup() -> int: + """ + Clean up resources used by loaded video. + """ + ... diff --git a/python_payload/st3m/reactor.py b/python_payload/st3m/reactor.py index 56f52e8c51e6c75ebbcdff9e6e26a1e4f32b7b63..cecc327d492813f07cea34d460a4f0af59b869ac 100644 --- a/python_payload/st3m/reactor.py +++ b/python_payload/st3m/reactor.py @@ -2,6 +2,7 @@ from st3m.goose import ABCBase, abstractmethod, List, Optional from st3m.input import InputState from ctx import Context +import media import time import sys_display import sys_kernel @@ -149,6 +150,7 @@ class Reactor: # Think! self._top.think(hr, delta) + media.think(delta) # Draw! if self._ctx is None: diff --git a/recovery/bootloader_components/main/bootloader_start.c b/recovery/bootloader_components/main/bootloader_start.c index a24de7f3dd465ece00a70ac87a89b8d383fbeaee..37d0fe7337b1986a3ebb6fcbf45feb18779d5cf0 100644 --- a/recovery/bootloader_components/main/bootloader_start.c +++ b/recovery/bootloader_components/main/bootloader_start.c @@ -86,4 +86,6 @@ static int select_partition_number(bootloader_state_t *bs, bool want_recovery) { } // Return global reent struct if any newlib functions are linked to bootloader -struct _reent *__getreent(void) { return _GLOBAL_REENT; } +struct _reent *__getreent(void) { + return _GLOBAL_REENT; +} diff --git a/tools/encode-mpg.sh b/tools/encode-mpg.sh new file mode 100755 index 0000000000000000000000000000000000000000..c84381af8fc8099573acdc43a2959eca05638bec --- /dev/null +++ b/tools/encode-mpg.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -e -x +if (( $# != 2 )); then + >&2 echo "Usage: mpg1-encode <video-file> <output-file>" + exit +fi + +ffmpeg -i $1 -vf scale=128:96 -c:v mpeg1video -b:v 96k -ac 1 -c:a mp2 -format mpeg -b:a 64k -ar 48000 -r 24 $2 + +