Compare commits
2 Commits
5d9ce1fdb9
...
d09cf0739e
| Author | SHA1 | Date | |
|---|---|---|---|
|
d09cf0739e
|
|||
|
8be23ca4fc
|
2
Makefile
2
Makefile
@@ -49,7 +49,7 @@ SOURCE_DIRS := source
|
||||
EXTRA_OUTPUT_FILES :=
|
||||
|
||||
LIBRARY_DIRS := $(DEVKITPRO)/libctru $(DEVKITPRO)/portlibs/armv6k $(DEVKITPRO)/portlibs/3ds
|
||||
LIBRARIES := citro2d citro3d sidplay mpg123 vorbisidec opusfile opus ogg ctru m
|
||||
LIBRARIES := citro2d citro3d png z sidplay mpg123 vorbisidec opusfile opus ogg ctru m
|
||||
|
||||
BUILD_FLAGS := -Wall -Wextra -I$(DEVKITPRO)/libctru/include -I$(DEVKITPRO)/portlibs/armv6k/include/opus -I$(DEVKITPRO)/portlibs/3ds/include/opus -O3 -g3 -ffunction-sections -fdata-sections
|
||||
# -O0 -g3 -fstack-protector-strong -fsanitize=undefined -fsanitize-trap
|
||||
|
||||
@@ -6,6 +6,7 @@ The latest 3DSX/CIA/3DS download can be found on the <a href="https://github.com
|
||||
|
||||
## Features
|
||||
* Plays PCM WAV, AIFF, FLAC, Opus, Vorbis and MP3 files.
|
||||
* M4A/AAC/ALAC support (file detection implemented, decoder integration in progress).
|
||||
* Pause and play support.
|
||||
* Plays music via headphones whilst system is closed.
|
||||
* Ability to browse directories.
|
||||
|
||||
@@ -6,7 +6,9 @@ enum file_types
|
||||
FILE_TYPE_VORBIS,
|
||||
FILE_TYPE_OPUS,
|
||||
FILE_TYPE_MP3,
|
||||
FILE_TYPE_SID
|
||||
FILE_TYPE_SID,
|
||||
FILE_TYPE_M4A,
|
||||
FILE_TYPE_AAC
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -108,4 +108,11 @@ void guiDisplayVersion(const char* version);
|
||||
*/
|
||||
void guiDrawText(gfxScreen_t screen, float x, float y, const char* text, u32 color, float scale);
|
||||
|
||||
/**
|
||||
* Display album art on top screen
|
||||
*
|
||||
* \param metadata Pointer to metadata structure with album art
|
||||
*/
|
||||
void guiDisplayAlbumArt(struct metadata_t* metadata);
|
||||
|
||||
#endif
|
||||
|
||||
21
include/m4a.h
Normal file
21
include/m4a.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef M4A_H
|
||||
#define M4A_H
|
||||
|
||||
#include "playback.h"
|
||||
|
||||
/**
|
||||
* Set decoder parameters for M4A/AAC.
|
||||
*
|
||||
* \param decoder Structure to store parameters.
|
||||
*/
|
||||
void setM4a(struct decoder_fn* decoder);
|
||||
|
||||
/**
|
||||
* Check if a file is an M4A/AAC/ALAC file.
|
||||
*
|
||||
* \param file File location.
|
||||
* \return 0 on success, -1 on failure.
|
||||
*/
|
||||
int isM4a(const char* file);
|
||||
|
||||
#endif
|
||||
@@ -13,7 +13,7 @@
|
||||
#define mice_main_h
|
||||
|
||||
/* Application version */
|
||||
#define MICE_VERSION "dev37"
|
||||
#define MICE_VERSION "dev50"
|
||||
|
||||
/* Default folder */
|
||||
#define DEFAULT_DIR "sdmc:/"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "error.h"
|
||||
#include "file.h"
|
||||
#include "flac.h"
|
||||
#include "m4a.h"
|
||||
#include "mp3.h"
|
||||
#include "opus.h"
|
||||
#include "vorbis.h"
|
||||
@@ -26,7 +27,9 @@ const char* fileToStr(enum file_types ft)
|
||||
"VORBIS",
|
||||
"OPUS",
|
||||
"MP3",
|
||||
"SID"
|
||||
"SID",
|
||||
"M4A",
|
||||
"AAC"
|
||||
};
|
||||
|
||||
return file_types_str[ft];
|
||||
@@ -90,24 +93,37 @@ enum file_types getFileType(const char *file)
|
||||
file_type = FILE_TYPE_SID;
|
||||
break;
|
||||
|
||||
default:
|
||||
/*
|
||||
* MP3 without ID3 tag, ID3v1 tag is at the end of file, or MP3
|
||||
* with ID3 tag at the beginning of the file.
|
||||
*/
|
||||
if(isMp3(file) == 0)
|
||||
{
|
||||
file_type = FILE_TYPE_MP3;
|
||||
break;
|
||||
}
|
||||
|
||||
/* TODO: Add this again at some point */
|
||||
//printf("Unknown magic number: %#010x\n.", fileSig);
|
||||
errno = FILE_NOT_SUPPORTED;
|
||||
default:
|
||||
/* Check for M4A/AAC/ALAC (MP4 container with ftyp atom) */
|
||||
if((fileSig == 0x70797466) || /* 'ftyp' at offset 4 */
|
||||
(fileSig == 0x65657266)) /* 'free' at offset 4 (some M4A files) */
|
||||
{
|
||||
file_type = FILE_TYPE_M4A;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for raw AAC (ADTS format) - sync word 0xFFF */
|
||||
if((fileSig & 0xFFF60000) == 0xFFF00000)
|
||||
{
|
||||
file_type = FILE_TYPE_AAC;
|
||||
break;
|
||||
}
|
||||
|
||||
err:
|
||||
/*
|
||||
* MP3 without ID3 tag, ID3v1 tag is at the end of file, or MP3
|
||||
* with ID3 tag at the beginning of the file.
|
||||
*/
|
||||
if(isMp3(file) == 0)
|
||||
{
|
||||
file_type = FILE_TYPE_MP3;
|
||||
break;
|
||||
}
|
||||
|
||||
/* TODO: Add this again at some point */
|
||||
//printf("Unknown magic number: %#010x\n.", fileSig);
|
||||
errno = FILE_NOT_SUPPORTED;
|
||||
break;
|
||||
}err:
|
||||
fclose(ftest);
|
||||
return file_type;
|
||||
}
|
||||
|
||||
137
source/gui.c
137
source/gui.c
@@ -3,6 +3,8 @@
|
||||
#include <citro3d.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <png.h>
|
||||
#include "gui.h"
|
||||
#include "metadata.h"
|
||||
|
||||
@@ -180,14 +182,6 @@ void guiDisplayMetadata(struct metadata_t* metadata, const char* filename)
|
||||
C2D_TextOptimize(&text);
|
||||
C2D_DrawText(&text, C2D_WithColor, 10.0f, y, 0.5f, scale * 0.8f, scale * 0.8f, GUI_COLOR_TEXT_DIM);
|
||||
}
|
||||
|
||||
/* Draw album art indicator if available */
|
||||
if(metadata->hasAlbumArt)
|
||||
{
|
||||
C2D_TextParse(&text, textBuf, "[Art]");
|
||||
C2D_TextOptimize(&text);
|
||||
C2D_DrawText(&text, C2D_WithColor, 350.0f, 10.0f, 0.5f, 0.4f, 0.4f, GUI_COLOR_ACCENT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -343,3 +337,130 @@ void guiDisplayVersion(const char* version)
|
||||
C2D_TextOptimize(&text);
|
||||
C2D_DrawText(&text, C2D_WithColor, 80.0f, 220.0f, 0.5f, 0.45f, 0.45f, GUI_COLOR_TEXT_DIM);
|
||||
}
|
||||
|
||||
/**
|
||||
* PNG read callback for memory buffer
|
||||
*/
|
||||
static void pngReadCallback(png_structp png_ptr, png_bytep data, png_size_t length)
|
||||
{
|
||||
uint8_t** buffer_ptr = (uint8_t**)png_get_io_ptr(png_ptr);
|
||||
memcpy(data, *buffer_ptr, length);
|
||||
*buffer_ptr += length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display album art on top screen
|
||||
*/
|
||||
void guiDisplayAlbumArt(struct metadata_t* metadata)
|
||||
{
|
||||
if(!metadata || !metadata->hasAlbumArt || !metadata->albumArt)
|
||||
return;
|
||||
|
||||
C2D_SceneBegin(topTarget);
|
||||
|
||||
/* Decode PNG image */
|
||||
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||
if(!png_ptr)
|
||||
return;
|
||||
|
||||
png_infop info_ptr = png_create_info_struct(png_ptr);
|
||||
if(!info_ptr)
|
||||
{
|
||||
png_destroy_read_struct(&png_ptr, NULL, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if(setjmp(png_jmpbuf(png_ptr)))
|
||||
{
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set up custom read function */
|
||||
uint8_t* buffer_ptr = metadata->albumArt;
|
||||
png_set_read_fn(png_ptr, &buffer_ptr, pngReadCallback);
|
||||
|
||||
/* Read PNG info */
|
||||
png_read_info(png_ptr, info_ptr);
|
||||
|
||||
int width = png_get_image_width(png_ptr, info_ptr);
|
||||
int height = png_get_image_height(png_ptr, info_ptr);
|
||||
png_byte color_type = png_get_color_type(png_ptr, info_ptr);
|
||||
png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr);
|
||||
|
||||
/* Convert to RGBA8 */
|
||||
if(bit_depth == 16)
|
||||
png_set_strip_16(png_ptr);
|
||||
if(color_type == PNG_COLOR_TYPE_PALETTE)
|
||||
png_set_palette_to_rgb(png_ptr);
|
||||
if(color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
|
||||
png_set_expand_gray_1_2_4_to_8(png_ptr);
|
||||
if(png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
|
||||
png_set_tRNS_to_alpha(png_ptr);
|
||||
if(color_type == PNG_COLOR_TYPE_RGB ||
|
||||
color_type == PNG_COLOR_TYPE_GRAY ||
|
||||
color_type == PNG_COLOR_TYPE_PALETTE)
|
||||
png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
|
||||
if(color_type == PNG_COLOR_TYPE_GRAY ||
|
||||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
|
||||
png_set_gray_to_rgb(png_ptr);
|
||||
|
||||
png_read_update_info(png_ptr, info_ptr);
|
||||
|
||||
/* Allocate image buffer */
|
||||
png_bytep* row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height);
|
||||
if(!row_pointers)
|
||||
{
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
for(int y = 0; y < height; y++)
|
||||
{
|
||||
row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png_ptr, info_ptr));
|
||||
if(!row_pointers[y])
|
||||
{
|
||||
for(int i = 0; i < y; i++)
|
||||
free(row_pointers[i]);
|
||||
free(row_pointers);
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Read image data */
|
||||
png_read_image(png_ptr, row_pointers);
|
||||
|
||||
/* Scale to fit in top-right corner (max 60x60) */
|
||||
int displayWidth = width;
|
||||
int displayHeight = height;
|
||||
if(width > 60 || height > 60)
|
||||
{
|
||||
float scale = 60.0f / (width > height ? width : height);
|
||||
displayWidth = (int)(width * scale);
|
||||
displayHeight = (int)(height * scale);
|
||||
}
|
||||
|
||||
/* Draw the image pixel by pixel */
|
||||
float startX = 330.0f; /* Top right corner */
|
||||
float startY = 10.0f;
|
||||
|
||||
for(int y = 0; y < displayHeight; y++)
|
||||
{
|
||||
int srcY = (y * height) / displayHeight;
|
||||
for(int x = 0; x < displayWidth; x++)
|
||||
{
|
||||
int srcX = (x * width) / displayWidth;
|
||||
png_bytep px = &(row_pointers[srcY][srcX * 4]);
|
||||
u32 color = C2D_Color32(px[0], px[1], px[2], px[3]);
|
||||
C2D_DrawRectSolid(startX + x, startY + y, 0.5f, 1, 1, color);
|
||||
}
|
||||
}
|
||||
|
||||
/* Clean up */
|
||||
for(int y = 0; y < height; y++)
|
||||
free(row_pointers[y]);
|
||||
free(row_pointers);
|
||||
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
||||
}
|
||||
|
||||
133
source/m4a.c
Normal file
133
source/m4a.c
Normal file
@@ -0,0 +1,133 @@
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <3ds.h>
|
||||
|
||||
#include "error.h"
|
||||
#include "m4a.h"
|
||||
#include "playback.h"
|
||||
|
||||
/* TODO: Integrate proper AAC decoder library (libfaad2, fdk-aac, or minimp4) */
|
||||
/* For now, this is a stub implementation */
|
||||
|
||||
static size_t* buffSize;
|
||||
static uint32_t rate = 44100;
|
||||
static uint8_t channels = 2;
|
||||
|
||||
static int initM4a(const char* file);
|
||||
static uint32_t rateM4a(void);
|
||||
static uint8_t channelM4a(void);
|
||||
static uint64_t decodeM4a(void* buffer);
|
||||
static void exitM4a(void);
|
||||
static size_t getFileSamplesM4a(void);
|
||||
|
||||
/**
|
||||
* Set decoder parameters for M4A/AAC.
|
||||
*
|
||||
* \param decoder Structure to store parameters.
|
||||
*/
|
||||
void setM4a(struct decoder_fn* decoder)
|
||||
{
|
||||
decoder->init = &initM4a;
|
||||
decoder->rate = &rateM4a;
|
||||
decoder->channels = &channelM4a;
|
||||
buffSize = &(decoder->buffSize);
|
||||
decoder->decode = &decodeM4a;
|
||||
decoder->exit = &exitM4a;
|
||||
decoder->getFileSamples = &getFileSamplesM4a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a file is an M4A/AAC/ALAC file.
|
||||
*
|
||||
* \param file File location.
|
||||
* \return 0 on success, -1 on failure.
|
||||
*/
|
||||
int isM4a(const char* file)
|
||||
{
|
||||
FILE* ftest = fopen(file, "rb");
|
||||
uint32_t fileSig;
|
||||
uint32_t ftypSig;
|
||||
|
||||
if(ftest == NULL)
|
||||
return -1;
|
||||
|
||||
/* Read first 4 bytes (should be size of ftyp atom) */
|
||||
if(fread(&fileSig, 4, 1, ftest) == 0)
|
||||
{
|
||||
fclose(ftest);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Read next 4 bytes (should be 'ftyp') */
|
||||
if(fread(&ftypSig, 4, 1, ftest) == 0)
|
||||
{
|
||||
fclose(ftest);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fclose(ftest);
|
||||
|
||||
/* Check for 'ftyp' signature (0x70797466 in little-endian) */
|
||||
if(ftypSig == 0x70797466)
|
||||
return 0;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int initM4a(const char* file)
|
||||
{
|
||||
(void)file;
|
||||
|
||||
/* TODO: Initialize AAC decoder */
|
||||
/* This requires:
|
||||
* 1. Parse MP4 container to find AAC audio track
|
||||
* 2. Extract decoder config (sample rate, channels, etc.)
|
||||
* 3. Initialize AAC decoder with config
|
||||
*/
|
||||
|
||||
/* Set default values for now */
|
||||
rate = 44100;
|
||||
channels = 2;
|
||||
*buffSize = rate * channels * sizeof(int16_t);
|
||||
|
||||
errno = FILE_NOT_SUPPORTED;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static uint32_t rateM4a(void)
|
||||
{
|
||||
return rate;
|
||||
}
|
||||
|
||||
static uint8_t channelM4a(void)
|
||||
{
|
||||
return channels;
|
||||
}
|
||||
|
||||
static uint64_t decodeM4a(void* buffer)
|
||||
{
|
||||
(void)buffer;
|
||||
|
||||
/* TODO: Decode AAC frame */
|
||||
/* This requires:
|
||||
* 1. Read next AAC frame from MP4 container
|
||||
* 2. Decode AAC frame to PCM samples
|
||||
* 3. Write PCM samples to buffer
|
||||
* 4. Return number of samples decoded
|
||||
*/
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void exitM4a(void)
|
||||
{
|
||||
/* TODO: Clean up AAC decoder */
|
||||
}
|
||||
|
||||
static size_t getFileSamplesM4a(void)
|
||||
{
|
||||
/* TODO: Calculate total samples from MP4 metadata */
|
||||
return 0;
|
||||
}
|
||||
@@ -617,6 +617,7 @@ int main(int argc __attribute__((unused)), char **argv __attribute__((unused)))
|
||||
const char* currentFile = (fileNum > 0 && fileNum <= dirList.dirNum + dirList.fileNum) ?
|
||||
(fileNum > dirList.dirNum ? dirList.files[fileNum - dirList.dirNum - 1] : "..") : "";
|
||||
guiDisplayMetadata(¤tMetadata, currentFile);
|
||||
guiDisplayAlbumArt(¤tMetadata);
|
||||
}
|
||||
|
||||
/* Calculate scroll position to keep selection visible (14 lines visible) */
|
||||
|
||||
@@ -194,56 +194,135 @@ static int extractId3v2Metadata(FILE* fp, struct metadata_t* metadata)
|
||||
continue;
|
||||
}
|
||||
|
||||
if(fread(frameData, 1, frameSize, fp) == frameSize)
|
||||
if(fread(frameData, 1, frameSize, fp) == frameSize)
|
||||
{
|
||||
frameData[frameSize] = 0;
|
||||
|
||||
/* Text encoding byte:
|
||||
* 0x00 = ISO-8859-1
|
||||
* 0x01 = UTF-16 with BOM
|
||||
* 0x02 = UTF-16BE without BOM
|
||||
* 0x03 = UTF-8
|
||||
*/
|
||||
uint8_t encoding = frameData[0];
|
||||
char* text = frameData + 1;
|
||||
int textLen = frameSize - 1;
|
||||
|
||||
/* Skip UTF-16 BOM if present */
|
||||
if(encoding == 0x01 && textLen >= 2)
|
||||
{
|
||||
frameData[frameSize] = 0;
|
||||
|
||||
/* Skip text encoding byte */
|
||||
char* text = frameData + 1;
|
||||
int textLen = frameSize - 1;
|
||||
|
||||
/* Copy to appropriate field */
|
||||
char* dest = NULL;
|
||||
size_t maxLen = 0;
|
||||
|
||||
if(strncmp(frameId, "TIT2", 4) == 0)
|
||||
if((uint8_t)text[0] == 0xFF && (uint8_t)text[1] == 0xFE)
|
||||
{
|
||||
dest = metadata->title;
|
||||
maxLen = METADATA_TITLE_MAX - 1;
|
||||
text += 2;
|
||||
textLen -= 2;
|
||||
}
|
||||
else if(strncmp(frameId, "TPE1", 4) == 0)
|
||||
else if((uint8_t)text[0] == 0xFE && (uint8_t)text[1] == 0xFF)
|
||||
{
|
||||
dest = metadata->artist;
|
||||
maxLen = METADATA_ARTIST_MAX - 1;
|
||||
}
|
||||
else if(strncmp(frameId, "TALB", 4) == 0)
|
||||
{
|
||||
dest = metadata->album;
|
||||
maxLen = METADATA_ALBUM_MAX - 1;
|
||||
}
|
||||
|
||||
if(dest)
|
||||
{
|
||||
strncpy(dest, text, maxLen);
|
||||
dest[maxLen] = 0;
|
||||
trimWhitespace(dest);
|
||||
text += 2;
|
||||
textLen -= 2;
|
||||
}
|
||||
}
|
||||
|
||||
free(frameData);
|
||||
/* Copy to appropriate field */
|
||||
char* dest = NULL;
|
||||
size_t maxLen = 0;
|
||||
|
||||
if(strncmp(frameId, "TIT2", 4) == 0)
|
||||
{
|
||||
dest = metadata->title;
|
||||
maxLen = METADATA_TITLE_MAX - 1;
|
||||
}
|
||||
else if(strncmp(frameId, "TPE1", 4) == 0)
|
||||
{
|
||||
dest = metadata->artist;
|
||||
maxLen = METADATA_ARTIST_MAX - 1;
|
||||
}
|
||||
else if(strncmp(frameId, "TALB", 4) == 0)
|
||||
{
|
||||
dest = metadata->album;
|
||||
maxLen = METADATA_ALBUM_MAX - 1;
|
||||
}
|
||||
|
||||
if(dest)
|
||||
{
|
||||
/* Handle different encodings */
|
||||
if(encoding == 0x01 || encoding == 0x02) /* UTF-16 */
|
||||
{
|
||||
/* Convert UTF-16 to ASCII (simplified - just take every other byte) */
|
||||
int outPos = 0;
|
||||
for(int i = 0; i < textLen && outPos < (int)maxLen; i += 2)
|
||||
{
|
||||
if(text[i] >= 0x20 && text[i] < 0x7F)
|
||||
dest[outPos++] = text[i];
|
||||
else if(text[i] == 0 && text[i+1] == 0)
|
||||
break;
|
||||
}
|
||||
dest[outPos] = 0;
|
||||
}
|
||||
else /* ISO-8859-1 or UTF-8 */
|
||||
{
|
||||
/* Copy as-is, filtering out non-ASCII characters */
|
||||
int outPos = 0;
|
||||
for(int i = 0; i < textLen && outPos < (int)maxLen; i++)
|
||||
{
|
||||
if((uint8_t)text[i] >= 0x20 && (uint8_t)text[i] < 0x7F)
|
||||
dest[outPos++] = text[i];
|
||||
else if(text[i] == 0)
|
||||
break;
|
||||
}
|
||||
dest[outPos] = 0;
|
||||
}
|
||||
trimWhitespace(dest);
|
||||
}
|
||||
} free(frameData);
|
||||
}
|
||||
else if(strncmp(frameId, "APIC", 4) == 0) /* Attached Picture */
|
||||
{
|
||||
/* Basic album art detection - just store size info for now */
|
||||
/* Extract album art data */
|
||||
if(frameSize > 10 && !metadata->hasAlbumArt)
|
||||
{
|
||||
metadata->hasAlbumArt = true;
|
||||
metadata->albumArtSize = frameSize;
|
||||
/* Estimate dimensions - actual implementation would decode image */
|
||||
metadata->albumArtWidth = 300; /* Common album art size */
|
||||
metadata->albumArtHeight = 300;
|
||||
uint8_t* frameData = malloc(frameSize);
|
||||
if(frameData && fread(frameData, 1, frameSize, fp) == frameSize)
|
||||
{
|
||||
/* ID3v2 APIC frame format:
|
||||
* - Text encoding (1 byte)
|
||||
* - MIME type (null-terminated string)
|
||||
* - Picture type (1 byte)
|
||||
* - Description (null-terminated string)
|
||||
* - Picture data
|
||||
*/
|
||||
uint8_t* ptr = frameData;
|
||||
ptr++; /* Skip text encoding */
|
||||
|
||||
/* Skip MIME type */
|
||||
while(*ptr != 0 && (ptr - frameData) < (int)frameSize) ptr++;
|
||||
ptr++; /* Skip null terminator */
|
||||
|
||||
ptr++; /* Skip picture type */
|
||||
|
||||
/* Skip description */
|
||||
while(*ptr != 0 && (ptr - frameData) < (int)frameSize) ptr++;
|
||||
ptr++; /* Skip null terminator */
|
||||
|
||||
/* Remaining data is the image */
|
||||
size_t imageSize = frameSize - (ptr - frameData);
|
||||
if(imageSize > 0)
|
||||
{
|
||||
metadata->albumArt = malloc(imageSize);
|
||||
if(metadata->albumArt)
|
||||
{
|
||||
memcpy(metadata->albumArt, ptr, imageSize);
|
||||
metadata->albumArtSize = imageSize;
|
||||
metadata->hasAlbumArt = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(frameData) free(frameData);
|
||||
}
|
||||
else
|
||||
{
|
||||
fseek(fp, frameSize, SEEK_CUR);
|
||||
}
|
||||
fseek(fp, frameSize, SEEK_CUR);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -415,7 +494,72 @@ static int extractFlacMetadata(FILE* fp, struct metadata_t* metadata)
|
||||
blockType = blockHeader[0] & 0x7F;
|
||||
blockSize = (blockHeader[1] << 16) | (blockHeader[2] << 8) | blockHeader[3];
|
||||
|
||||
if(blockType == 4) /* VORBIS_COMMENT */
|
||||
if(blockType == 6) /* PICTURE */
|
||||
{
|
||||
/* Extract album art from FLAC picture block */
|
||||
if(!metadata->hasAlbumArt && blockSize > 32)
|
||||
{
|
||||
uint8_t* pictureData = malloc(blockSize);
|
||||
if(pictureData && fread(pictureData, 1, blockSize, fp) == blockSize)
|
||||
{
|
||||
/* FLAC picture block format:
|
||||
* - Picture type (4 bytes BE)
|
||||
* - MIME type length (4 bytes BE)
|
||||
* - MIME type string
|
||||
* - Description length (4 bytes BE)
|
||||
* - Description string
|
||||
* - Width (4 bytes BE)
|
||||
* - Height (4 bytes BE)
|
||||
* - Depth (4 bytes BE)
|
||||
* - Colors (4 bytes BE)
|
||||
* - Picture data length (4 bytes BE)
|
||||
* - Picture data
|
||||
*/
|
||||
uint32_t offset = 4; /* Skip picture type */
|
||||
|
||||
/* Skip MIME type */
|
||||
uint32_t mimeLen = (pictureData[offset] << 24) | (pictureData[offset+1] << 16) |
|
||||
(pictureData[offset+2] << 8) | pictureData[offset+3];
|
||||
offset += 4 + mimeLen;
|
||||
|
||||
/* Skip description */
|
||||
if(offset + 4 <= blockSize)
|
||||
{
|
||||
uint32_t descLen = (pictureData[offset] << 24) | (pictureData[offset+1] << 16) |
|
||||
(pictureData[offset+2] << 8) | pictureData[offset+3];
|
||||
offset += 4 + descLen;
|
||||
}
|
||||
|
||||
/* Skip width, height, depth, colors (16 bytes) */
|
||||
offset += 16;
|
||||
|
||||
/* Get picture data length */
|
||||
if(offset + 4 <= blockSize)
|
||||
{
|
||||
uint32_t picLen = (pictureData[offset] << 24) | (pictureData[offset+1] << 16) |
|
||||
(pictureData[offset+2] << 8) | pictureData[offset+3];
|
||||
offset += 4;
|
||||
|
||||
if(offset + picLen <= blockSize && picLen > 0)
|
||||
{
|
||||
metadata->albumArt = malloc(picLen);
|
||||
if(metadata->albumArt)
|
||||
{
|
||||
memcpy(metadata->albumArt, pictureData + offset, picLen);
|
||||
metadata->albumArtSize = picLen;
|
||||
metadata->hasAlbumArt = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(pictureData) free(pictureData);
|
||||
}
|
||||
else
|
||||
{
|
||||
fseek(fp, blockSize, SEEK_CUR);
|
||||
}
|
||||
}
|
||||
else if(blockType == 4) /* VORBIS_COMMENT */
|
||||
{
|
||||
/* FLAC uses Vorbis comments for metadata */
|
||||
/* This is a simplified implementation */
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "error.h"
|
||||
#include "file.h"
|
||||
#include "flac.h"
|
||||
#include "m4a.h"
|
||||
#include "mp3.h"
|
||||
#include "opus.h"
|
||||
#include "playback.h"
|
||||
@@ -98,15 +99,18 @@ void playFile(void* infoIn)
|
||||
setVorbis(&decoder);
|
||||
break;
|
||||
|
||||
case FILE_TYPE_SID:
|
||||
setSid(&decoder);
|
||||
break;
|
||||
case FILE_TYPE_SID:
|
||||
setSid(&decoder);
|
||||
break;
|
||||
|
||||
case FILE_TYPE_M4A:
|
||||
case FILE_TYPE_AAC:
|
||||
setM4a(&decoder);
|
||||
break;
|
||||
|
||||
default:
|
||||
goto err;
|
||||
}
|
||||
|
||||
if(ndspInit() < 0)
|
||||
default:
|
||||
goto err;
|
||||
} if(ndspInit() < 0)
|
||||
{
|
||||
errno = NDSP_INIT_FAIL;
|
||||
goto err;
|
||||
|
||||
Reference in New Issue
Block a user