rockbox/lib/rbcodec/metadata/rm.c
Sean Bartell cadb3627fc Add rbcodecplatform.h and rbcodecconfig.h.
librbcodec users must provide these two files when the library is built.
rbcodecconfig.h provides configuration #defines and basic types, and
will be included by public librbcodec headers, so it must not conflict
with the user's code. rbcodecplatform.h provides various OS functions,
and will only be included by source files and private headers. This
system is intended to provide maximum flexibility for use on embedded
systems, where no operating system headers are included. Unix systems
can just copy rbcodecconfig-example.h and rbcodecplatform-unix.h with
minimal changes.

Change-Id: I350a2274d173da391fd1ca00c4202e9760d91def
Reviewed-on: http://gerrit.rockbox.org/143
Reviewed-by: Nils Wallménius <nils@rockbox.org>
Tested-by: Nils Wallménius <nils@rockbox.org>
2012-05-03 14:49:35 +02:00

464 lines
16 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2009 Mohamed Tarek
*
* 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 <codecs/librm/rm.h>
#include "metadata.h"
#include "metadata_common.h"
#include "metadata_parsers.h"
#include "logf.h"
/* Uncomment the following line for debugging */
//#define DEBUG_RM
#ifndef DEBUG_RM
#undef DEBUGF
#define DEBUGF(...)
#endif
#define ID3V1_OFFSET -128
#define METADATA_FOOTER_OFFSET -140
static inline void print_cook_extradata(RMContext *rmctx) {
DEBUGF(" cook_version = 0x%08lx\n", rm_get_uint32be(rmctx->codec_extradata));
DEBUGF(" samples_per_frame_per_channel = %d\n", rm_get_uint16be(&rmctx->codec_extradata[4]));
DEBUGF(" number_of_subbands_in_freq_domain = %d\n", rm_get_uint16be(&rmctx->codec_extradata[6]));
if(rmctx->extradata_size == 16) {
DEBUGF(" joint_stereo_subband_start = %d\n",rm_get_uint16be(&rmctx->codec_extradata[12]));
DEBUGF(" joint_stereo_vlc_bits = %d\n", rm_get_uint16be(&rmctx->codec_extradata[14]));
}
}
struct real_object_t
{
uint32_t fourcc;
uint32_t size;
uint16_t version;
};
static int real_read_object_header(int fd, struct real_object_t* obj)
{
int n;
if ((n = read_uint32be(fd, &obj->fourcc)) <= 0)
return n;
if ((n = read_uint32be(fd, &obj->size)) <= 0)
return n;
if ((n = read_uint16be(fd, &obj->version)) <= 0)
return n;
return 1;
}
#if (defined(SIMULATOR) && defined(DEBUG_RM))
static char* fourcc2str(uint32_t f)
{
static char res[5];
res[0] = (f & 0xff000000) >> 24;
res[1] = (f & 0xff0000) >> 16;
res[2] = (f & 0xff00) >> 8;
res[3] = (f & 0xff);
res[4] = 0;
return res;
}
#endif
static inline int real_read_audio_stream_info(int fd, RMContext *rmctx)
{
int skipped = 0;
uint32_t version;
struct real_object_t obj;
#ifdef SIMULATOR
uint32_t header_size;
uint16_t flavor;
uint32_t coded_framesize;
uint8_t interleaver_id_length;
uint8_t fourcc_length;
#endif
uint32_t interleaver_id;
uint32_t fourcc = 0;
memset(&obj,0,sizeof(obj));
read_uint32be(fd, &version);
skipped += 4;
DEBUGF(" version=0x%04lx\n",((version >> 16) & 0xff));
if (((version >> 16) & 0xff) == 3) {
/* Very old version */
} else {
#ifdef SIMULATOR
real_read_object_header(fd, &obj);
read_uint32be(fd, &header_size);
/* obj.size will be filled with an unknown value, replaced with header_size */
DEBUGF(" Object: %s, size: %ld bytes, version: 0x%04x\n",fourcc2str(obj.fourcc),header_size,obj.version);
read_uint16be(fd, &flavor);
read_uint32be(fd, &coded_framesize);
#else
lseek(fd, 20, SEEK_CUR);
#endif
lseek(fd, 12, SEEK_CUR); /* unknown */
read_uint16be(fd, &rmctx->sub_packet_h);
read_uint16be(fd, &rmctx->block_align);
read_uint16be(fd, &rmctx->sub_packet_size);
lseek(fd, 2, SEEK_CUR); /* unknown */
skipped += 40;
if (((version >> 16) & 0xff) == 5)
{
lseek(fd, 6, SEEK_CUR); /* unknown */
skipped += 6;
}
read_uint16be(fd, &rmctx->sample_rate);
lseek(fd, 4, SEEK_CUR); /* unknown */
read_uint16be(fd, &rmctx->nb_channels);
skipped += 8;
if (((version >> 16) & 0xff) == 4)
{
#ifdef SIMULATOR
read_uint8(fd, &interleaver_id_length);
read_uint32be(fd, &interleaver_id);
read_uint8(fd, &fourcc_length);
#else
lseek(fd, 6, SEEK_CUR);
#endif
read_uint32be(fd, &fourcc);
skipped += 10;
}
if (((version >> 16) & 0xff) == 5)
{
read_uint32be(fd, &interleaver_id);
read_uint32be(fd, &fourcc);
skipped += 8;
}
lseek(fd, 3, SEEK_CUR); /* unknown */
skipped += 3;
if (((version >> 16) & 0xff) == 5)
{
lseek(fd, 1, SEEK_CUR); /* unknown */
skipped += 1;
}
switch(fourcc) {
case FOURCC('c','o','o','k'):
rmctx->codec_type = CODEC_COOK;
read_uint32be(fd, &rmctx->extradata_size);
skipped += 4;
read(fd, rmctx->codec_extradata, rmctx->extradata_size);
skipped += rmctx->extradata_size;
break;
case FOURCC('r','a','a','c'):
case FOURCC('r','a','c','p'):
rmctx->codec_type = CODEC_AAC;
read_uint32be(fd, &rmctx->extradata_size);
skipped += 4;
read(fd, rmctx->codec_extradata, rmctx->extradata_size);
skipped += rmctx->extradata_size;
break;
case FOURCC('d','n','e','t'):
rmctx->codec_type = CODEC_AC3;
break;
case FOURCC('a','t','r','c'):
rmctx->codec_type = CODEC_ATRAC;
read_uint32be(fd, &rmctx->extradata_size);
skipped += 4;
read(fd, rmctx->codec_extradata, rmctx->extradata_size);
skipped += rmctx->extradata_size;
break;
default: /* Not a supported codec */
return -1;
}
DEBUGF(" flavor = %d\n",flavor);
DEBUGF(" coded_frame_size = %ld\n",coded_framesize);
DEBUGF(" sub_packet_h = %d\n",rmctx->sub_packet_h);
DEBUGF(" frame_size = %d\n",rmctx->block_align);
DEBUGF(" sub_packet_size = %d\n",rmctx->sub_packet_size);
DEBUGF(" sample_rate= %d\n",rmctx->sample_rate);
DEBUGF(" channels= %d\n",rmctx->nb_channels);
DEBUGF(" fourcc = %s\n",fourcc2str(fourcc));
DEBUGF(" codec_extra_data_length = %ld\n",rmctx->extradata_size);
DEBUGF(" codec_extradata :\n");
if(rmctx->codec_type == CODEC_COOK) {
DEBUGF(" cook_extradata :\n");
print_cook_extradata(rmctx);
}
}
return skipped;
}
static int rm_parse_header(int fd, RMContext *rmctx, struct mp3entry *id3)
{
struct real_object_t obj;
int res;
int skipped;
off_t curpos __attribute__((unused));
uint8_t len; /* Holds a string_length, which is then passed to read_string() */
#ifdef SIMULATOR
uint32_t avg_bitrate = 0;
uint32_t max_packet_size;
uint32_t avg_packet_size;
uint32_t packet_count;
uint32_t duration;
uint32_t preroll;
uint32_t index_offset;
uint16_t stream_id;
uint32_t start_time;
uint32_t codec_data_size;
#endif
uint32_t v;
uint32_t max_bitrate;
uint16_t num_streams;
uint32_t next_data_off;
uint8_t header_end;
memset(&obj,0,sizeof(obj));
curpos = lseek(fd, 0, SEEK_SET);
res = real_read_object_header(fd, &obj);
if (obj.fourcc == FOURCC('.','r','a',0xfd))
{
/* Very old .ra format - not yet supported */
return -1;
}
else if (obj.fourcc != FOURCC('.','R','M','F'))
{
return -1;
}
lseek(fd, 8, SEEK_CUR); /* unknown */
DEBUGF("Object: %s, size: %d bytes, version: 0x%04x, pos: %d\n",fourcc2str(obj.fourcc),(int)obj.size,obj.version,(int)curpos);
res = real_read_object_header(fd, &obj);
header_end = 0;
while(res)
{
DEBUGF("Object: %s, size: %d bytes, version: 0x%04x, pos: %d\n",fourcc2str(obj.fourcc),(int)obj.size,obj.version,(int)curpos);
skipped = 10;
if(obj.fourcc == FOURCC('I','N','D','X'))
break;
switch (obj.fourcc)
{
case FOURCC('P','R','O','P'): /* File properties */
read_uint32be(fd, &max_bitrate);
read_uint32be(fd, &rmctx->bit_rate); /*avg bitrate*/
#ifdef SIMULATOR
read_uint32be(fd, &max_packet_size);
read_uint32be(fd, &avg_packet_size);
read_uint32be(fd, &packet_count);
#else
lseek(fd, 3*sizeof(uint32_t), SEEK_CUR);
#endif
read_uint32be(fd, &rmctx->duration);
#ifdef SIMULATOR
read_uint32be(fd, &preroll);
read_uint32be(fd, &index_offset);
#else
lseek(fd, 2*sizeof(uint32_t), SEEK_CUR);
#endif
read_uint32be(fd, &rmctx->data_offset);
read_uint16be(fd, &num_streams);
read_uint16be(fd, &rmctx->flags);
skipped += 40;
DEBUGF(" max_bitrate = %ld\n",max_bitrate);
DEBUGF(" avg_bitrate = %ld\n",rmctx->bit_rate);
DEBUGF(" max_packet_size = %ld\n",max_packet_size);
DEBUGF(" avg_packet_size = %ld\n",avg_packet_size);
DEBUGF(" packet_count = %ld\n",packet_count);
DEBUGF(" duration = %ld\n",rmctx->duration);
DEBUGF(" preroll = %ld\n",preroll);
DEBUGF(" index_offset = %ld\n",index_offset);
DEBUGF(" data_offset = %ld\n",rmctx->data_offset);
DEBUGF(" num_streams = %d\n",num_streams);
DEBUGF(" flags=0x%04x\n",rmctx->flags);
break;
case FOURCC('C','O','N','T'):
/* Four strings - Title, Author, Copyright, Comment */
read_uint8(fd,&len);
skipped += (int)read_string(fd, id3->id3v1buf[0], sizeof(id3->id3v1buf[0]), '\0', len);
read_uint8(fd,&len);
skipped += (int)read_string(fd, id3->id3v1buf[1], sizeof(id3->id3v1buf[1]), '\0', len);
read_uint8(fd,&len);
skipped += (int)read_string(fd, id3->id3v1buf[2], sizeof(id3->id3v1buf[2]), '\0', len);
read_uint8(fd,&len);
skipped += (int)read_string(fd, id3->id3v1buf[3], sizeof(id3->id3v1buf[3]), '\0', len);
skipped += 4;
DEBUGF(" title=\"%s\"\n",id3->id3v1buf[0]);
DEBUGF(" author=\"%s\"\n",id3->id3v1buf[1]);
DEBUGF(" copyright=\"%s\"\n",id3->id3v1buf[2]);
DEBUGF(" comment=\"%s\"\n",id3->id3v1buf[3]);
break;
case FOURCC('M','D','P','R'): /* Media properties */
#ifdef SIMULATOR
read_uint16be(fd,&stream_id);
read_uint32be(fd,&max_bitrate);
read_uint32be(fd,&avg_bitrate);
read_uint32be(fd,&max_packet_size);
read_uint32be(fd,&avg_packet_size);
read_uint32be(fd,&start_time);
read_uint32be(fd,&preroll);
read_uint32be(fd,&duration);
#else
lseek(fd, 30, SEEK_CUR);
#endif
skipped += 30;
read_uint8(fd,&len);
skipped += 1;
lseek(fd, len, SEEK_CUR); /* desc */
skipped += len;
read_uint8(fd,&len);
skipped += 1;
#ifdef SIMULATOR
lseek(fd, len, SEEK_CUR); /* mimetype */
read_uint32be(fd,&codec_data_size);
#else
lseek(fd, len + 4, SEEK_CUR);
#endif
skipped += len + 4;
read_uint32be(fd,&v);
skipped += 4;
DEBUGF(" stream_id = 0x%04x\n",stream_id);
DEBUGF(" max_bitrate = %ld\n",max_bitrate);
DEBUGF(" avg_bitrate = %ld\n",avg_bitrate);
DEBUGF(" max_packet_size = %ld\n",max_packet_size);
DEBUGF(" avg_packet_size = %ld\n",avg_packet_size);
DEBUGF(" start_time = %ld\n",start_time);
DEBUGF(" preroll = %ld\n",preroll);
DEBUGF(" duration = %ld\n",duration);
DEBUGF(" codec_data_size = %ld\n",codec_data_size);
DEBUGF(" v=\"%s\"\n", fourcc2str(v));
if (v == FOURCC('.','r','a',0xfd))
{
int temp;
temp= real_read_audio_stream_info(fd, rmctx);
if(temp < 0)
return -1;
else
skipped += temp;
}
else if (v == FOURCC('L','S','D',':'))
{
DEBUGF("Real audio lossless is not supported.");
return -1;
}
else
{
/* We shall not abort with -1 here. *.rm file often seem
* to have a second media properties header that contains
* other metadata. */
DEBUGF("Unknown header signature :\"%s\"\n", fourcc2str(v));
}
break;
case FOURCC('D','A','T','A'):
read_uint32be(fd,&rmctx->nb_packets);
skipped += 4;
read_uint32be(fd,&next_data_off);
skipped += 4;
/***
* nb_packets correction :
* in some samples, number of packets may not exactly form
* an integer number of scrambling units. This is corrected
* by constructing a partially filled unit out of the few
* remaining samples at the end of decoding.
***/
if(rmctx->nb_packets % rmctx->sub_packet_h)
rmctx->nb_packets += rmctx->sub_packet_h - (rmctx->nb_packets % rmctx->sub_packet_h);
DEBUGF(" data_nb_packets = %ld\n",rmctx->nb_packets);
DEBUGF(" next DATA offset = %ld\n",next_data_off);
header_end = 1;
break;
}
if(header_end) break;
curpos = lseek(fd, obj.size - skipped, SEEK_CUR);
res = real_read_object_header(fd, &obj);
}
return 0;
}
bool get_rm_metadata(int fd, struct mp3entry* id3)
{
RMContext *rmctx = (RMContext*) (( (intptr_t)id3->id3v2buf + 3 ) &~ 3);
memset(rmctx,0,sizeof(RMContext));
if(rm_parse_header(fd, rmctx, id3) < 0)
return false;
if (!setid3v1title(fd, id3)) {
/* file has no id3v1 tags, use the tags from CONT chunk */
id3->title = id3->id3v1buf[0];
id3->artist = id3->id3v1buf[1];
id3->comment= id3->id3v1buf[3];
}
switch(rmctx->codec_type)
{
case CODEC_COOK:
/* Already set, do nothing */
break;
case CODEC_AAC:
id3->codectype = AFMT_RM_AAC;
break;
case CODEC_AC3:
id3->codectype = AFMT_RM_AC3;
break;
case CODEC_ATRAC:
id3->codectype = AFMT_RM_ATRAC3;
break;
}
id3->channels = rmctx->nb_channels;
id3->extradata_size = rmctx->extradata_size;
id3->bitrate = rmctx->bit_rate / 1000;
id3->frequency = rmctx->sample_rate;
id3->length = rmctx->duration;
id3->filesize = filesize(fd);
return true;
}