changeset 2296:789802d99629

Add basic FLAC decoder and add FLAC playback support to the media player
author Michael Pavone <pavone@retrodev.com>
date Sun, 19 Feb 2023 21:12:46 -0800
parents eb45ad9d8a3f
children e6b2b2341c68
files Makefile flac.c flac.h mediaplayer.c mediaplayer.h
diffstat 5 files changed, 654 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Fri Feb 10 23:17:43 2023 -0800
+++ b/Makefile	Sun Feb 19 21:12:46 2023 -0800
@@ -196,7 +196,7 @@
 endif
 endif
 endif
-AUDIOOBJS=ym2612.o psg.o wave.o vgm.o event_log.o render_audio.o rf5c164.o oscilloscope.o
+AUDIOOBJS=ym2612.o psg.o wave.o flac.o vgm.o event_log.o render_audio.o rf5c164.o oscilloscope.o
 CONFIGOBJS=config.o tern.o util.o paths.o
 NUKLEAROBJS=$(FONT) nuklear_ui/blastem_nuklear.o nuklear_ui/sfnt.o
 RENDEROBJS=ppm.o controller_info.o
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flac.c	Sun Feb 19 21:12:46 2023 -0800
@@ -0,0 +1,582 @@
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include "flac.h"
+
+static uint8_t read_byte_buffer(flac_file *f)
+{
+	if (f->offset >= f->buffer_size) {
+		return 0;
+	}
+	uint8_t *buf = f->read_data;
+	return buf[f->offset++];
+}
+
+static void seek_buffer(flac_file *f, uint32_t offset, uint8_t relative)
+{
+	f->offset = relative ? f->offset + offset : offset;
+}
+
+static uint8_t read_byte_file(flac_file *f)
+{
+	int result = fgetc(f->read_data);
+	if (result == EOF) {
+		return 0;
+	}
+	return result;
+}
+
+static void seek_file(flac_file *f, uint32_t offset, uint8_t relative)
+{
+	fseek(f->read_data, offset, relative ? SEEK_CUR : SEEK_SET);
+}
+
+static void read_chars(flac_file *f, char *dest, uint32_t count)
+{
+	for (; count > 0; --count)
+	{
+		*(dest++) = f->read_byte(f);
+	}
+}
+
+static uint16_t read16(flac_file *f)
+{
+	uint16_t ret = f->read_byte(f) << 8;
+	ret |= f->read_byte(f);
+	return ret;
+}
+
+static uint32_t read_bits(flac_file *f, uint32_t num_bits)
+{
+	uint32_t ret = 0;
+	while (num_bits)
+	{
+		if (!f->bits) {
+			f->cur_byte = f->read_byte(f);
+			f->bits = 8;
+		}
+		uint32_t new_bits = f->bits;
+		if (new_bits > num_bits) {
+			new_bits = num_bits;
+		}
+		ret <<= new_bits;
+		uint32_t mask = (1 << new_bits) - 1;
+		ret |= (f->cur_byte >> (f->bits - new_bits)) & mask;
+		f->bits -= new_bits;
+		num_bits -= new_bits;
+	}
+	return ret;
+}
+
+static uint64_t read_bits64(flac_file *f, uint64_t num_bits)
+{
+	uint64_t ret = 0;
+	while (num_bits)
+	{
+		if (!f->bits) {
+			f->cur_byte = f->read_byte(f);
+			f->bits = 8;
+		}
+		uint64_t new_bits = f->bits;
+		if (new_bits > num_bits) {
+			new_bits = num_bits;
+		}
+		ret <<= new_bits;
+		uint64_t mask = (1 << new_bits) - 1;
+		ret |= f->cur_byte & mask;
+		f->cur_byte >>= new_bits;
+		f->bits -= new_bits;
+		num_bits -= new_bits;
+	}
+	return ret;
+}
+
+typedef struct {
+	uint32_t size;
+	uint8_t  type;
+	uint8_t  is_last;
+} meta_block_header;
+
+enum {
+	STREAMINFO,
+	PADDING,
+	APPLICATION,
+	SEEKTABLE,
+	VORBIS_COMMENT,
+	CUESHEET,
+	PICTURE
+};
+
+static void read_meta_block_header(flac_file *f, meta_block_header *dest)
+{
+	dest->is_last = read_bits(f, 1);
+	dest->type = read_bits(f, 7);
+	dest->size = read_bits(f, 24);
+}
+
+static void parse_streaminfo(flac_file *f)
+{
+	read16(f);//min block size
+	read16(f);//max block size
+	read_bits(f, 24);//min frame size
+	read_bits(f, 24);//max frame size
+	f->sample_rate = read_bits(f, 20);
+	f->channels = read_bits(f, 3) + 1;
+	f->bits_per_sample = read_bits(f, 5) + 1;
+	f->total_samples = read_bits64(f, 36);
+	f->seek(f, 16, 1);//MD5
+}
+
+static uint8_t parse_header(flac_file *f)
+{
+	char id[4];
+	read_chars(f, id, sizeof(id));
+	if (memcmp("fLaC", id, sizeof(id))) {
+		return 0;
+	}
+	meta_block_header header;
+	do {
+		read_meta_block_header(f, &header);
+		if (header.type == STREAMINFO) {
+			parse_streaminfo(f);
+		} else {
+			f->seek(f, header.size, 1);
+		}
+	} while (!header.is_last);
+	return 1;
+}
+
+flac_file *flac_file_from_buffer(void *buffer, uint32_t size)
+{
+	flac_file *f = calloc(1, sizeof(flac_file));
+	f->read_data = buffer;
+	f->read_byte = read_byte_buffer;
+	f->seek = seek_buffer;
+	f->buffer_size = size;
+	if (parse_header(f)) {
+		return f;
+	}
+	free(f);
+	return NULL;
+}
+
+flac_file *flac_file_from_file(FILE *file)
+{
+	flac_file *f = calloc(1, sizeof(flac_file));
+	f->read_data = file;
+	f->read_byte = read_byte_file;
+	f->seek = seek_file;
+	if (parse_header(f)) {
+		return f;
+	}
+	free(f);
+	return NULL;
+}
+
+static uint64_t read_utf64(flac_file *f)
+{
+	uint8_t byte = f->read_byte(f);
+	if (!(byte & 0x80)) {
+		return byte;
+	}
+	uint8_t mask = 0x40;
+	uint8_t length = 0;
+	while (byte & mask)
+	{
+		mask >>= 1;
+		length++;
+	}
+	uint64_t value = byte + (mask - 1);
+	for (uint8_t i = 0; i < length; i++)
+	{
+		value <<= 6;
+		value |= f->read_byte(f) & 0x3F;
+	}
+	return value;
+}
+
+static uint32_t read_utf32(flac_file *f)
+{
+	uint8_t byte = f->read_byte(f);
+	if (!(byte & 0x80)) {
+		return byte;
+	}
+	uint8_t mask = 0x40;
+	uint8_t length = 0;
+	while (byte & mask)
+	{
+		mask >>= 1;
+		length++;
+	}
+	uint32_t value = byte + (mask - 1);
+	for (uint8_t i = 0; i < length; i++)
+	{
+		value <<= 6;
+		value |= f->read_byte(f) & 0x3F;
+	}
+	return value;
+}
+
+static uint8_t parse_frame_header(flac_file *f)
+{
+	uint16_t sync = read_bits(f, 14);
+	if (sync != 0x3FFE) {
+		fprintf(stderr, "Invalid sync FLAC sync pattern: %X\n", sync);
+		return 0;
+	}
+	read_bits(f, 1);//reserved
+	uint8_t block_size_strategy = read_bits(f, 1);
+	uint8_t block_size_code = read_bits(f, 4);
+	uint8_t sample_rate_code = read_bits(f, 4);
+	uint8_t channels = read_bits(f, 4);
+	uint8_t joint_stereo = 0;
+	if (channels > 7) {
+		joint_stereo = (channels & 7) + 1;
+		channels = 2;
+	} else {
+		++channels;
+	}
+	f->frame_channels = channels;
+	f->frame_joint_stereo = joint_stereo;
+	uint8_t bits_per_sample_code = read_bits(f, 3);
+	if (!bits_per_sample_code) {
+		f->frame_bits_per_sample = f->bits_per_sample;
+	} else if (bits_per_sample_code < 3) {
+		f->frame_bits_per_sample = 4 + 4 * bits_per_sample_code;
+	} else {
+		f->frame_bits_per_sample = 4 * bits_per_sample_code;
+	}
+	read_bits(f, 1);//reserved
+	uint32_t block_num;
+	if (block_size_strategy) {
+		f->frame_start_sample = read_utf64(f);
+	} else {
+		block_num = read_utf32(f);
+	}
+	uint32_t block_size = 0;
+	switch (block_size_code)
+	{
+	case 0:
+		fputs("Detected reserved block size 0", stderr);
+		return 0;
+	case 1:
+		block_size = 192;
+		break;
+	case 6:
+		block_size = f->read_byte(f) + 1;
+		break;
+	case 7:
+		block_size = read16(f) + 1;
+		break;
+	default:
+		if (block_size_code < 8) {
+			block_size = 576 * (1 << (block_size_code - 2));
+		} else {
+			block_size = 256 * (1 << (block_size_code - 8));
+		}
+		break;
+	}
+	f->frame_block_size = block_size;
+	if (!block_size_strategy) {
+		f->frame_start_sample  = ((uint64_t)block_num) * ((uint64_t)block_size);
+	}
+	uint32_t sample_rate;
+	switch (sample_rate_code)
+	{
+	case 15:
+		fputs("Invalid frame header sample rate", stderr);
+	case 0:
+		sample_rate = f->sample_rate;
+		break;
+	case 1:
+		sample_rate = 88200;
+		break;
+	case 2:
+		sample_rate = 176400;
+		break;
+	case 3:
+		sample_rate = 192000;
+		break;
+	case 4:
+		sample_rate = 8000;
+		break;
+	case 5:
+		sample_rate = 16000;
+		break;
+	case 6:
+		sample_rate = 22050;
+		break;
+	case 7:
+		sample_rate = 44100;
+		break;
+	case 8:
+		sample_rate = 32000;
+		break;
+	case 9:
+		sample_rate = 44100;
+		break;
+	case 10:
+		sample_rate = 48000;
+		break;
+	case 11:
+		sample_rate = 96000;
+		break;
+	case 12:
+		sample_rate = f->read_byte(f) * 1000;
+		break;
+	case 13:
+		sample_rate = read16(f);
+		break;
+	case 14:
+		sample_rate = read16(f) * 10;
+		break;
+	}
+	f->frame_sample_rate = sample_rate;
+	f->read_byte(f);//CRC-8
+	return 1;
+}
+
+enum {
+	SUBFRAME_CONSTANT,
+	SUBFRAME_VERBATIM,
+	SUBFRAME_FIXED = 8,
+	SUBFRAME_LPC = 0x20,
+};
+
+static int32_t sign_extend(uint32_t value, uint32_t bits)
+{
+	if (value & (1 << (bits - 1))) {
+		value |= ~((1 << bits) - 1);
+	}
+	return value;
+}
+
+static int32_t signed_sample(uint32_t sample_bits, uint32_t sample, uint8_t wasted_bits)
+{
+	sample <<= wasted_bits;
+	sample = sign_extend(sample, sample_bits);
+	return sample;
+}
+
+static void decode_residuals(flac_file *f, flac_subframe *sub, int64_t *coefficients, uint32_t order, int64_t shift)
+{
+	uint8_t residual_method = read_bits(f, 2);
+	uint8_t rice_param_bits = residual_method ? 5 : 4;
+	uint32_t partition_count = 1 << read_bits(f, 4);
+	uint32_t cur = order;
+	uint32_t partition_size = f->frame_block_size / partition_count;
+	for (uint32_t partition = 0; partition < partition_count; partition++)
+	{
+		uint32_t rice_param = read_bits(f, rice_param_bits);
+		if (rice_param == (1 << rice_param_bits) - 1) {
+			//escape code, residuals are unencoded
+			rice_param = read_bits(f, rice_param_bits);
+			for (uint32_t end = partition ? cur + partition_size : partition_size; cur < end; cur++)
+			{
+				int64_t prediction = 0;
+				for (uint32_t i = 0; i < order; i++)
+				{
+					prediction += ((int64_t)sub->decoded[cur - 1 - i]) * coefficients[i];
+				}
+				if (shift) {
+					prediction >>= shift;
+				}
+				prediction += sign_extend(read_bits(f, rice_param), rice_param);
+				sub->decoded[cur] = prediction;
+			}
+		} else {
+			for (uint32_t end = partition ? cur + partition_size : partition_size; cur < end; cur++)
+			{
+				int64_t prediction = 0;
+				for (uint32_t i = 0; i < order; i++)
+				{
+					prediction += ((int64_t)sub->decoded[cur - 1 - i]) * coefficients[i];
+				}
+				if (shift) {
+					prediction >>= shift;
+				}
+				uint32_t residual = 0;
+				while (!read_bits(f, 1))
+				{
+					++residual;
+				}
+				residual <<= rice_param;
+				residual |= read_bits(f, rice_param);
+				if (residual & 1) {
+					sub->decoded[cur] = prediction - (residual >> 1) - 1;
+				} else {
+					sub->decoded[cur] = prediction + (residual >> 1);
+				}
+			}
+		}
+	}
+}
+
+static void decode_subframe(flac_file *f, flac_subframe *sub)
+{
+	if (f->frame_block_size > sub->allocated_samples) {
+		sub->decoded = realloc(sub->decoded, sizeof(int32_t) * f->frame_block_size);
+		sub->allocated_samples = f->frame_block_size ;
+	}
+	int64_t prediction_coefficients[32];
+	read_bits(f, 1);//reserved
+	uint8_t type = read_bits(f, 6);
+	uint8_t has_wasted_bits = read_bits(f, 1);
+	uint8_t wasted_bits = 0;
+	if (has_wasted_bits) {
+		++wasted_bits;
+		while (!read_bits(f, 1))
+		{
+			++wasted_bits;
+		}
+	}
+	uint32_t sample_bits = f->frame_bits_per_sample - wasted_bits;
+	if (f->frame_joint_stereo) {
+		int channel = sub - f->subframes;
+		if (f->frame_joint_stereo == 2 && !channel || (channel && f->frame_joint_stereo != 2)) {
+			sample_bits++;
+		}
+	}
+	if (type == SUBFRAME_CONSTANT) {
+		int32_t sample = signed_sample(sample_bits, read_bits(f, sample_bits), wasted_bits);
+		for (uint32_t i = 0; i < f->frame_block_size; i++)
+		{
+			sub->decoded[i] = sample;
+		}
+	} else if (type == SUBFRAME_VERBATIM) {
+		for (uint32_t i = 0; i < f->frame_block_size; i++)
+		{
+			sub->decoded[i] = signed_sample(sample_bits, read_bits(f, sample_bits), wasted_bits);
+		}
+	} else if (type & SUBFRAME_LPC) {
+		uint32_t order = (type & 0x1F) + 1;
+		for (uint32_t i = 0; i < order; i++)
+		{
+			sub->decoded[i] = signed_sample(sample_bits, read_bits(f, sample_bits), wasted_bits);
+		}
+		uint32_t coefficient_bits = read_bits(f, 4) + 1;
+		int64_t shift_bits = read_bits(f, 5);
+		for (uint32_t i = 0; i < order; i++)
+		{
+			prediction_coefficients[i] = sign_extend(read_bits(f, coefficient_bits), coefficient_bits);
+		}
+		decode_residuals(f, sub, prediction_coefficients, order, shift_bits);
+	} else if (type & SUBFRAME_FIXED) {
+		uint32_t order = type & 7;
+		for (uint32_t i = 0; i < order; i++)
+		{
+			sub->decoded[i] = signed_sample(sample_bits, read_bits(f, sample_bits), wasted_bits);
+		}
+		switch (order)
+		{
+		case 1:
+			prediction_coefficients[0] = 1;
+			break;
+		case 2:
+			prediction_coefficients[0] = 2;
+			prediction_coefficients[1] = -1;
+			break;
+		case 3:
+			prediction_coefficients[0] = 3;
+			prediction_coefficients[1] = -3;
+			prediction_coefficients[2] = 1;
+			break;
+		case 4:
+			prediction_coefficients[0] = 4;
+			prediction_coefficients[1] = -6;
+			prediction_coefficients[2] = 4;
+			prediction_coefficients[3] = -1;
+			break;
+		}
+		decode_residuals(f, sub, prediction_coefficients, order, 0);
+	} else {
+		fprintf(stderr, "Invalid subframe type %X\n", type);
+	}
+}
+
+static uint8_t decode_frame(flac_file *f)
+{
+	if (!parse_frame_header(f)) {
+		return 0;
+	}
+	if (f->frame_channels > f->subframe_alloc) {
+		f->subframes = realloc(f->subframes, sizeof(flac_subframe) * f->frame_channels);
+		memset(f->subframes + f->subframe_alloc, 0, sizeof(flac_subframe) * (f->frame_channels - f->subframe_alloc));
+		f->subframe_alloc = f->frame_channels;
+	}
+	for (uint8_t channel = 0; channel < f->frame_channels; channel++)
+	{
+		decode_subframe(f, f->subframes + channel);
+	}
+	f->bits = 0;
+	read16(f);//Frame footer CRC-16
+	f->frame_sample_pos = 0;
+	return 1;
+}
+
+uint8_t flac_get_sample(flac_file *f, int16_t *out, uint8_t desired_channels)
+{
+	if (f->frame_sample_pos == f->frame_block_size) {
+		if (!decode_frame(f)) {
+			return 0;
+		}
+	}
+	uint8_t copy_channels;
+	if (f->frame_channels == 1 && desired_channels > 1) {
+		int16_t sample = f->subframes->decoded[f->frame_sample_pos];
+		*(out++) = sample;
+		*(out++) = sample;
+		copy_channels = 2;
+	} else {
+		int32_t left, right, mid, diff;
+		switch (f->frame_joint_stereo)
+		{
+		case 0:
+			copy_channels = desired_channels;
+			if (copy_channels > f->frame_channels) {
+				copy_channels = f->frame_channels;
+			}
+			for (uint8_t i = 0; i < copy_channels; i++)
+			{
+				*(out++) = f->subframes[i].decoded[f->frame_sample_pos];
+			}
+			break;
+		case 1:
+			//left-side
+			copy_channels = 2;
+			*(out++) = left = f->subframes[0].decoded[f->frame_sample_pos];
+			if (desired_channels > 1) {
+				*(out++) = left + f->subframes[1].decoded[f->frame_sample_pos];
+			}
+			break;
+		case 2:
+			//side-right
+			copy_channels = 2;
+			right = f->subframes[1].decoded[f->frame_sample_pos];
+			left = right + f->subframes[0].decoded[f->frame_sample_pos];
+			*(out++) = left;
+			if (desired_channels > 1) {
+				*(out++) = right;
+			}
+			break;
+		case 3:
+			//mid-side
+			copy_channels = 2;
+			mid = f->subframes[0].decoded[f->frame_sample_pos];
+			diff = f->subframes[1].decoded[f->frame_sample_pos];
+			left = (diff + 2 * mid) >> 1;
+			*(out++) = left;
+			if (desired_channels > 1) {
+				*(out++) = left - diff;
+			}
+			break;
+		}
+	}
+	for (uint8_t i = copy_channels; i < desired_channels; i++)
+	{
+		*(out++) = 0;
+	}
+	f->frame_sample_pos++;
+
+	return 1;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flac.h	Sun Feb 19 21:12:46 2023 -0800
@@ -0,0 +1,48 @@
+#ifndef FLAC_H_
+#define FLAC_H_
+
+#include <stdint.h>
+#include <stdio.h>
+
+typedef struct flac_file flac_file;
+
+typedef uint8_t (*flac_read)(flac_file *f);
+typedef void (*flac_seek)(flac_file *f, uint32_t offset, uint8_t relative);
+
+typedef struct {
+	uint32_t allocated_samples;
+	int32_t *decoded;
+} flac_subframe;
+
+struct flac_file {
+	uint64_t      total_samples;
+	uint64_t      frame_start_sample;
+	void          *read_data;
+	flac_read     read_byte;
+	flac_seek     seek;
+	flac_subframe *subframes;
+	uint32_t      offset;
+	uint32_t      buffer_size;
+
+	uint32_t      frame_sample_pos;
+	uint32_t      remaining_frame_samples;
+
+	uint32_t      sample_rate;
+	uint32_t      frame_sample_rate;
+	uint32_t      frame_block_size;
+	uint8_t       bits_per_sample;
+	uint8_t       frame_bits_per_sample;
+	uint8_t       channels;
+	uint8_t       frame_channels;
+	uint8_t       frame_joint_stereo;
+	uint8_t       subframe_alloc;
+
+	uint8_t       cur_byte;
+	uint8_t       bits;
+};
+
+flac_file *flac_file_from_buffer(void *buffer, uint32_t size);
+flac_file *flac_file_from_file(FILE *file);
+uint8_t flac_get_sample(flac_file *f, int16_t *out, uint8_t desired_channels);
+
+#endif //FLAC_H_
--- a/mediaplayer.c	Fri Feb 10 23:17:43 2023 -0800
+++ b/mediaplayer.c	Sun Feb 19 21:12:46 2023 -0800
@@ -443,7 +443,16 @@
 
 void flac_frame(media_player *player)
 {
-	render_sleep_ms(15);
+	for (uint32_t remaining_samples = player->flac->sample_rate / 60; remaining_samples > 0; remaining_samples--)
+	{
+		int16_t samples[2];
+		if (flac_get_sample(player->flac, samples, 2)) {
+			render_put_stereo_sample(player->audio, samples[0], samples[1]);
+		} else {
+			player->state = STATE_PAUSED;
+			return;
+		}
+	}
 }
 
 void vgm_init(media_player *player, uint32_t opts)
@@ -571,6 +580,14 @@
 	free(player->wave);
 }
 
+static void flac_player_init(media_player *player)
+{
+	player->flac = flac_file_from_buffer(player->media->buffer, player->media->size);
+	if (player->flac) {
+		player->audio = render_audio_source("Audio File", player->flac->sample_rate, 1, 2);
+	}
+}
+
 static void resume_player(system_header *system)
 {
 	media_player *player = (media_player *)system;
@@ -689,6 +706,9 @@
 	case AUDIO_WAVE:
 		wave_player_init(player);
 		break;
+	case AUDIO_FLAC:
+		flac_player_init(player);
+		break;
 	}
 
 	return player;
--- a/mediaplayer.h	Fri Feb 10 23:17:43 2023 -0800
+++ b/mediaplayer.h	Sun Feb 19 21:12:46 2023 -0800
@@ -5,6 +5,7 @@
 #include "system.h"
 #include "vgm.h"
 #include "wave.h"
+#include "flac.h"
 #include "render_audio.h"
 
 typedef struct chip_info chip_info;
@@ -28,6 +29,7 @@
 	vgm_extended_header *vgm_ext;
 	data_block          *ym_seek_block;
 	wave_header         *wave;
+	flac_file           *flac;
 	audio_source        *audio;
 	chip_info           *chips;
 	uint32_t            num_chips;