rockbox/lib/rbcodec/metadata/ogg.c
Aidan MacDonald 3a57f2f721 Add "rbfs" prefix to native filesystem functions
Most of the churn here occurs because 'filesize' is one of
the redefined filesystem functions, but some of the structs
used by the native FS code also include a 'filesize' member
variable which gets renamed by the macro in some but not all
source files.

It's easier to rename 'filesize()' to 'ffilesize()' rather
than try to clean up the macro mess or renaming the struct
members.

There is weirdness with root_realpath() which now breaks on
native builds because it was assumed to be unprefixed there.
dir_get_info() was unprefixed everywhere but this just seems
inconsistent; make it follow the FS_PREFIX() convention too.

Change-Id: Ic3700c6234ea45f32679c1a8429d70fdb8f4088a
2026-09-17 16:22:31 -04:00

238 lines
7.1 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
*
* Copyright (C) 2005 Dave Chapman
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <inttypes.h>
#include "platform.h"
#include "metadata.h"
#include "metadata_common.h"
#include "metadata_parsers.h"
#include "logf.h"
//NOTE: buf size must be >= 92 bytes
int get_ogg_format_and_move_to_comments(int fd, unsigned char *buf)
{
/* 92 bytes is enough for both Vorbis and Speex headers */
if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, buf, 92) < 92))
{
return AFMT_UNKNOWN;
}
/* All Ogg streams start with OggS */
if (memcmp(buf, "OggS", 4) != 0)
{
return AFMT_UNKNOWN;
}
/* Check for format magic and then get metadata */
if (memcmp(&buf[29], "vorbis", 6) == 0)
{
/* Comments are in second Ogg page (byte 58 onwards for Vorbis) */
if (lseek(fd, 58, SEEK_SET) < 0)
{
return AFMT_UNKNOWN;
}
return AFMT_OGG_VORBIS;
}
else if (memcmp(&buf[28], "Speex ", 8) == 0)
{
uint32_t header_size = get_long_le(&buf[60]);
/* Comments are in second Ogg page (byte 108 onwards for Speex) */
if (lseek(fd, 28 + header_size, SEEK_SET) < 0)
{
return AFMT_UNKNOWN;
}
return AFMT_SPEEX;
}
else if (memcmp(&buf[28], "OpusHead", 8) == 0)
{
/* Comments are in second Ogg page (byte 108 onwards for Speex) */
if (lseek(fd, 47, SEEK_SET) < 0)
{
DEBUGF("Could not seek to ogg");
return AFMT_UNKNOWN;
}
return AFMT_OPUS;
}
/* Unsupported format, try to print the marker, catches Ogg/FLAC at least */
DEBUGF("Unsupported format in Ogg stream: %16s\n", &buf[28]);
return AFMT_UNKNOWN;
}
/* A simple parser to read vital metadata from an Ogg Vorbis file.
* Can also handle parsing Ogg Speex files for metadata. Returns
* false if metadata needed by the codec couldn't be read.
*/
bool get_ogg_metadata(int fd, struct mp3entry* id3)
{
/* An Ogg File is split into pages, each starting with the string
* "OggS". Each page has a timestamp (in PCM samples) referred to as
* the "granule position".
*
* An Ogg Vorbis has the following structure:
* 1) Identification header (containing samplerate, numchannels, etc)
* 2) Comment header - containing the Vorbis Comments
* 3) Setup header - containing codec setup information
* 4) Many audio packets...
*
* An Ogg Speex has the following structure:
* 1) Identification header (containing samplerate, numchannels, etc)
* Described in this page: (http://www.speex.org/manual2/node7.html)
* 2) Comment header - containing the Vorbis Comments
* 3) Many audio packets.
*/
/* Use the path name of the id3 structure as a temporary buffer. */
unsigned char* buf = (unsigned char *)id3->path;
long comment_size;
long last_serial = 0;
long serial, r;
int segments;
int i;
bool eof = false;
id3->codectype = get_ogg_format_and_move_to_comments(fd, buf);
switch (id3->codectype)
{
case AFMT_OGG_VORBIS:
id3->frequency = get_long_le(&buf[40]);
id3->vbr = true;
break;
case AFMT_SPEEX:
id3->frequency = get_slong(&buf[64]);
id3->vbr = get_long_le(&buf[88]);
break;
case AFMT_OPUS:
id3->frequency = 48000;
id3->vbr = true;
// FIXME handle an actual channel mapping table
break;
default:
return false;
}
id3->filesize = ffilesize(fd);
/* We need to ensure the serial number from this page is the same as the
* one from the last page (since we only support a single bitstream).
*/
serial = get_long_le(&buf[14]);
long remaining = 0;
comment_size = read_vorbis_tags(fd, id3, remaining);
/* We now need to search for the last page in the file - identified by
* by ('O','g','g','S',0) and retrieve totalsamples.
*/
/* A page is always < 64 kB */
if (lseek(fd, -(MIN(64 * 1024, id3->filesize)), SEEK_END) < 0)
{
return false;
}
while (!eof)
{
r = read(fd, &buf[remaining], MAX_PATH - remaining);
if (r <= 0)
{
eof = true;
}
else
{
remaining += r;
}
/* Inefficient (but simple) search */
i = 0;
while (i < (remaining - 3))
{
if ((buf[i] == 'O') && (memcmp(&buf[i], "OggS", 4) == 0))
{
if (i < (remaining - 17))
{
/* Note that this only reads the low 32 bits of a
* 64 bit value.
*/
id3->samples = get_long_le(&buf[i + 6]);
last_serial = get_long_le(&buf[i + 14]);
/* If this page is very small the beginning of the next
* header could be in buffer. Jump near end of this header
* and continue */
i += 27;
}
else
{
break;
}
}
else
{
i++;
}
}
if (i < remaining)
{
/* Move the remaining bytes to start of buffer.
* Reuse var 'segments' as it is no longer needed */
segments = 0;
while (i < remaining)
{
buf[segments++] = buf[i++];
}
remaining = segments;
}
else
{
/* Discard the rest of the buffer */
remaining = 0;
}
}
/* This file has mutiple vorbis bitstreams (or is corrupt). */
/* FIXME we should display an error here. */
if (serial != last_serial)
{
logf("serialno mismatch");
logf("%ld", serial);
logf("%ld", last_serial);
return false;
}
id3->length = ((int64_t) id3->samples * 1000) / id3->frequency;
if (id3->length <= 0)
{
logf("ogg length invalid!");
return false;
}
id3->bitrate = (((int64_t) id3->filesize - comment_size) * 8) / id3->length;
return true;
}