diff foosdk/sdk/foobar2000/helpers/mp3_utils.cpp @ 1:20d02a178406 default tip

*: check in everything else yay
author Paper <paper@tflc.us>
date Mon, 05 Jan 2026 02:15:46 -0500
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/foosdk/sdk/foobar2000/helpers/mp3_utils.cpp	Mon Jan 05 02:15:46 2026 -0500
@@ -0,0 +1,281 @@
+#include "StdAfx.h"
+
+#include "mp3_utils.h"
+#include "bitreader_helper.h"
+
+using namespace bitreader_helper;
+
+static unsigned extract_header_bits(const t_uint8 p_header[4],unsigned p_base,unsigned p_bits)
+{
+	PFC_ASSERT(p_base+p_bits<=32);
+	return (unsigned) extract_bits(p_header,p_base,p_bits);
+}
+
+namespace {
+
+	class header_parser
+	{
+	public:
+		header_parser(const t_uint8 p_header[4]) : m_bitptr(0) 
+		{
+			memcpy(m_header,p_header,4);
+		}
+		unsigned read(unsigned p_bits)
+		{
+			unsigned ret = extract_header_bits(m_header,m_bitptr,p_bits);
+			m_bitptr += p_bits;
+			return ret;
+		}
+	private:
+		t_uint8 m_header[4];
+		unsigned m_bitptr;
+	};
+}
+
+typedef t_uint16 uint16;
+
+static const uint16 bitrate_table_l1v1[16]  = {  0, 32, 64, 96,128,160,192,224,256,288,320,352,384,416,448,  0};
+static const uint16 bitrate_table_l2v1[16]  = {  0, 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384,  0};
+static const uint16 bitrate_table_l3v1[16]  = {  0, 32, 40, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,  0};
+static const uint16 bitrate_table_l1v2[16]  = {  0, 32, 48, 56, 64, 80, 96,112,128,144,160,176,192,224,256,  0};
+static const uint16 bitrate_table_l23v2[16] = {  0,  8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,  0};
+static const uint16 sample_rate_table[] = {11025,12000,8000};
+
+unsigned mp3_utils::QueryMPEGFrameSize(const t_uint8 p_header[4])
+{
+	TMPEGFrameInfo info;
+	if (!ParseMPEGFrameHeader(info,p_header)) return 0;
+	return info.m_bytes;
+}
+
+bool mp3_utils::ParseMPEGFrameHeader(TMPEGFrameInfo & p_info,const t_uint8 p_header[4])
+{
+	enum {MPEG_LAYER_1 = 3, MPEG_LAYER_2 = 2, MPEG_LAYER_3 = 1};
+	enum {_MPEG_1 = 3, _MPEG_2 = 2, _MPEG_25 = 0};
+
+	header_parser parser(p_header);
+	if (parser.read(11) != 0x7FF) return false;
+	unsigned mpeg_version = parser.read(2);
+	unsigned layer = parser.read(2);
+	unsigned protection = parser.read(1);
+	unsigned bitrate_index = parser.read(4);
+	unsigned sample_rate_index = parser.read(2);
+	if (sample_rate_index == 3) return false;//reserved
+	unsigned paddingbit = parser.read(1);
+	int paddingdelta = 0;
+	parser.read(1);//private
+	unsigned channel_mode = parser.read(2);
+	unsigned channel_mode_ext = parser.read(2);//channel_mode_extension
+	parser.read(1);//copyright
+	parser.read(1);//original
+	parser.read(2);//emphasis
+
+	unsigned bitrate = 0,sample_rate = 0;
+
+	switch(layer)
+	{
+	default:
+		return false;
+	case MPEG_LAYER_3:
+		paddingdelta = paddingbit ? 1 : 0;
+		
+		p_info.m_layer = 3;
+		switch(mpeg_version)
+		{
+		case _MPEG_1:
+			p_info.m_duration = 1152;
+			bitrate = bitrate_table_l3v1[bitrate_index];
+			break;
+		case _MPEG_2:
+		case _MPEG_25:
+			p_info.m_duration = 576;
+			bitrate = bitrate_table_l23v2[bitrate_index];
+			break;
+		default:
+			return false;
+		}
+	
+		break;
+	case MPEG_LAYER_2:
+		paddingdelta = paddingbit ? 1 : 0;
+		p_info.m_duration = 1152;
+		p_info.m_layer = 2;
+		switch(mpeg_version)
+		{
+		case _MPEG_1:
+			bitrate = bitrate_table_l2v1[bitrate_index];
+			break;
+		case _MPEG_2:
+		case _MPEG_25:
+			bitrate = bitrate_table_l23v2[bitrate_index];
+			break;
+		default:
+			return false;
+		}
+		break;
+	case MPEG_LAYER_1:
+		paddingdelta = paddingbit ? 4 : 0;
+		p_info.m_duration = 384;
+		p_info.m_layer = 1;
+		switch(mpeg_version)
+		{
+		case _MPEG_1:
+			bitrate = bitrate_table_l1v1[bitrate_index];
+			break;
+		case _MPEG_2:
+		case _MPEG_25:
+			bitrate = bitrate_table_l1v2[bitrate_index];
+			break;		
+		default:
+			return false;
+		}
+		break;
+	}
+	if (bitrate == 0) return false;
+
+	sample_rate = sample_rate_table[sample_rate_index];
+	if (sample_rate == 0) return false;
+	switch(mpeg_version)
+	{
+	case _MPEG_1:
+		sample_rate *= 4;
+		p_info.m_mpegversion = MPEG_1;
+		break;
+	case _MPEG_2:
+		sample_rate *= 2;
+		p_info.m_mpegversion = MPEG_2;
+		break;
+	case _MPEG_25:
+		p_info.m_mpegversion = MPEG_25;
+		break;
+	}
+
+	switch(channel_mode)
+	{
+	case 0:
+	case 1:
+	case 2:
+		p_info.m_channels = 2;
+		break;
+	case 3:
+		p_info.m_channels = 1;
+		break;
+	}
+
+	
+	p_info.m_channel_mode = channel_mode;
+	p_info.m_channel_mode_ext = channel_mode_ext;
+
+	p_info.m_sample_rate = sample_rate;
+	p_info.m_sample_rate_idx = sample_rate_index;
+
+	p_info.m_bitrate = bitrate;
+	p_info.m_bitrate_idx = bitrate_index;
+
+	p_info.m_bytes = ( bitrate /*kbps*/ * (1000/8) /* kbps-to-bytes*/ * p_info.m_duration /*samples-per-frame*/ ) / sample_rate + paddingdelta;
+
+	if (p_info.m_layer == 1) p_info.m_bytes &= ~3;
+
+	p_info.m_crc = protection == 0;
+
+	return true;
+}
+
+unsigned mp3header::get_samples_per_frame()
+{
+	mp3_utils::TMPEGFrameInfo fr;
+	if (!decode(fr)) return 0;
+	return fr.m_duration;
+}
+
+bool mp3_utils::IsSameStream(TMPEGFrameInfo const & p_frame1,TMPEGFrameInfo const & p_frame2) {
+	return 
+		// FFmpeg writes VBR headers with null channel mode...
+		/* p_frame1.m_channel_mode == p_frame2.m_channel_mode &&  */
+		p_frame1.m_sample_rate == p_frame2.m_sample_rate &&
+		p_frame1.m_layer == p_frame2.m_layer &&
+		p_frame1.m_mpegversion == p_frame2.m_mpegversion;
+}
+
+
+
+bool mp3_utils::ValidateFrameCRC(const t_uint8 * frameData, t_size frameSize, TMPEGFrameInfo const & info) {
+	if (frameSize < info.m_bytes) return false; //FAIL, incomplete data
+	if (!info.m_crc) return true; //nothing to check, frame appears valid
+	return ExtractFrameCRC(frameData, frameSize, info) == CalculateFrameCRC(frameData, frameSize, info);
+}
+
+static t_uint32 CRC_update(unsigned value, t_uint32 crc)
+{
+	enum { CRC16_POLYNOMIAL = 0x8005 };
+    unsigned i;
+    value <<= 8;
+    for (i = 0; i < 8; i++) {
+		value <<= 1;
+		crc <<= 1;
+		if (((crc ^ value) & 0x10000)) crc ^= CRC16_POLYNOMIAL;
+    }
+    return crc;
+}
+
+
+void mp3_utils::RecalculateFrameCRC(t_uint8 * frameData, t_size frameSize, TMPEGFrameInfo const & info) {
+	PFC_ASSERT( frameSize >= info.m_bytes && info.m_crc );
+
+	const t_uint16 crc = CalculateFrameCRC(frameData, frameSize, info);
+	frameData[4] = (t_uint8)(crc >> 8);
+	frameData[5] = (t_uint8)(crc & 0xFF);
+}
+
+static t_uint16 grabFrameCRC(const t_uint8 * frameData, t_size sideInfoLen) {
+    t_uint32 crc = 0xffff;
+    crc = CRC_update(frameData[2], crc);
+    crc = CRC_update(frameData[3], crc);
+    for (t_size i = 6; i < sideInfoLen; i++) {
+		crc = CRC_update(frameData[i], crc);
+    }
+
+	return (t_uint32) (crc & 0xFFFF);
+}
+
+t_uint16 mp3_utils::ExtractFrameCRC(const t_uint8 * frameData, t_size frameSize, TMPEGFrameInfo const & info) {
+	PFC_ASSERT(frameSize >= info.m_bytes && info.m_crc); (void)info; (void)frameSize;
+
+	return ((t_uint16)frameData[4] << 8) | (t_uint16)frameData[5];
+
+}
+t_uint16 mp3_utils::CalculateFrameCRC(const t_uint8 * frameData, t_size frameSize, TMPEGFrameInfo const & info) {
+	PFC_ASSERT(frameSize >= info.m_bytes && info.m_crc); (void)frameSize;
+
+	t_size sideInfoLen = 0;
+	if (info.m_mpegversion == MPEG_1)
+		sideInfoLen = (info.m_channels == 1) ? 4 + 17 : 4 + 32;
+	else
+		sideInfoLen = (info.m_channels == 1) ? 4 + 9 : 4 + 17;
+
+	//CRC
+	sideInfoLen += 2;
+
+	PFC_ASSERT( sideInfoLen  <= frameSize );
+
+	return grabFrameCRC(frameData, sideInfoLen);
+}
+
+
+bool mp3_utils::ValidateFrameCRC(const t_uint8 * frameData, t_size frameSize) {
+	if (frameSize < 4) return false; //FAIL, not a valid frame
+	TMPEGFrameInfo info;
+	if (!ParseMPEGFrameHeader(info, frameData)) return false; //FAIL, not a valid frame
+	return ValidateFrameCRC(frameData, frameSize, info);
+}
+
+
+bool mp3_utils::ParseMPEGFrameHeader(TMPEGFrameInfo & p_info, const void * bytes, size_t bytesAvail) {
+	if (bytesAvail < 4) return false; //FAIL, not a valid frame
+	return ParseMPEGFrameHeader(p_info, reinterpret_cast<const t_uint8*>(bytes));
+}
+
+bool mp3_utils::IsValidMPEGFrameHeader(const void* fourbytes) {
+	TMPEGFrameInfo info = {};
+	return ParseMPEGFrameHeader(info, fourbytes, 4);
+}