adding a reader for the AIFF and AIFF-C formats

git-svn-id: file:///srv/svn/repos/haiku/trunk/current@5742 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
beveloper
2003-12-23 23:23:40 +00:00
parent 91e76129e7
commit b2e54a92e3
4 changed files with 565 additions and 0 deletions
+1
View File
@@ -1,5 +1,6 @@
SubDir OBOS_TOP src add-ons media plugins ;
SubInclude OBOS_TOP src add-ons media plugins aiff_reader ;
SubInclude OBOS_TOP src add-ons media plugins au_reader ;
SubInclude OBOS_TOP src add-ons media plugins avi_reader ;
SubInclude OBOS_TOP src add-ons media plugins mp3_reader ;
@@ -0,0 +1,85 @@
#ifndef _AIFF_H
#define _AIFF_H
// The complete size of a chunk is chunk_size + sizeof(chunk_struct)
// If the complete size of a chunk is not even, the chunk is padded with one byte
// sample order for multi channel sound:
// 2: left, right
// 3: left, right, center
// 4: front left, front right, rear left, rear right
// 4: left, center, right, surround
// 6: left center, left, center, right center, right, surround
// Apple maps Microsoft ACM formats into a compression code of the form "ms??",
// where the second two characters are the Microsoft Audio Compression Manager
// (ACM) code. For instance, Microsoft ADPCM, with ACM value of 1, is mapped
// to the code "ms\x00\x01".
struct chunk_struct
{
uint32 chunk_id;
uint32 chunk_size;
} _PACKED;
struct aiff_chunk
{
uint32 chunk_id; // 'FORM'
uint32 chunk_size;
uint32 aiff_id; // 'AIFF' or 'AIFC'
// chunks[]
} _PACKED;
struct comm_chunk
{
// uint32 chunk_id; // 'COMM'
// uint32 chunk_size;
uint16 channel_count;
uint32 frame_count;
uint16 bits_per_sample; // store as 8 bit entities
char sample_rate[10]; // 80 bit IEEE floating point, big endian
uint32 compression_id; // only present if chunk_size >= 22
} _PACKED;
struct ssnd_chunk
{
// uint32 chunk_id; // 'SSND'
// uint32 chunk_size;
uint32 offset;
uint32 block_size;
// samples[]
} _PACKED;
#if DEBUG
inline const char * string_for_compression(uint32 compression)
{
static char s[64];
switch (compression) {
case 'NONE': return "not compressed";
case 'ADP4': return "4:1 Intel/DVI ADPCM";
case 'DWVW': return "Delta With Variable Word Width";
case 'FL32': return "IEEE 32-bit floating point";
case 'fl32': return "IEEE 32-bit floating point";
case 'fl64': return "IEEE 64-bit floating point";
case 'alaw': return "8-bit ITU-T G.711 A-law";
case 'ALAW': return "8-bit ITU-T G.711 A-law";
case 'ulaw': return "8-bit ITU-T G.711 µ-law";
case 'ULAW': return "8-bit ITU-T G.711 µ-law";
case 'ima4': return "IMA 4:1";
case 'MAC3': return "MACE 3-to-1";
case 'MAC6': return "MACE 6-to-1";
case 'QDMC': return "QDesign Music";
case 'Qclp': return "Qualcomm PureVoice";
case 'rt24': return "RT24 50:1";
case 'rt29': return "RT29 50:1";
default:
sprintf(s, "unknown compression %c%c%c%c",
(int)((compression >> 24) & 0xff),
(int)((compression >> 16) & 0xff),
(int)((compression >> 8) & 0xff),
(int)(compression & 0xff));
return s;
}
}
#endif
#endif
@@ -0,0 +1,413 @@
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <DataIO.h>
#include <ByteOrder.h>
#include <InterfaceDefs.h>
#include "aiff_reader.h"
#define TRACE_THIS 1
#if TRACE_THIS
#define TRACE printf
#else
#define TRACE(a...)
#endif
#define BUFFER_SIZE 16384
#define FOURCC(a,b,c,d) ((((uint32)(a)) << 24) | (((uint32)(b)) << 16) | (((uint32)(c)) << 8) | ((uint32)(d)))
#define UINT16(a) ((uint16)B_BENDIAN_TO_HOST_INT16((a)))
#define UINT32(a) ((uint32)B_BENDIAN_TO_HOST_INT32((a)))
aiffReader::aiffReader()
{
TRACE("aiffReader::aiffReader\n");
fBuffer = 0;
}
aiffReader::~aiffReader()
{
if (fBuffer)
free(fBuffer);
}
const char *
aiffReader::Copyright()
{
return ".au & .snd reader, " B_UTF8_COPYRIGHT " by Marcus Overhagen";
}
status_t
aiffReader::Sniff(int32 *streamCount)
{
TRACE("aiffReader::Sniff\n");
fSource = dynamic_cast<BPositionIO *>(Reader::Source());
if (!fSource) {
TRACE("aiffReader::Sniff: not a BPositionIO\n");
return B_ERROR;
}
int64 filesize = Source()->Seek(0, SEEK_END);
if (filesize < 42) {
TRACE("aiffReader::Sniff: File too small\n");
return B_ERROR;
}
aiff_chunk aiff;
if (sizeof(aiff) != Source()->ReadAt(0, &aiff, sizeof(aiff))) {
TRACE("aiffReader::Sniff: aiff_chunk reading failed\n");
return B_ERROR;
}
if (UINT32(aiff.chunk_id) != FOURCC('F','O','R','M')
|| (UINT32(aiff.aiff_id) != FOURCC('A','I','F','F')
&& UINT32(aiff.aiff_id) != FOURCC('A','I','F','C'))) {
TRACE("aiffReader::Sniff: aiff not recognized\n");
return B_ERROR;
}
comm_chunk comm;
ssnd_chunk ssnd;
int64 pos = sizeof(aiff);
fDataStart = 0;
fDataSize = 0;
// read all chunks and search for "COMM" and "SSND", everything else is ignored
bool foundCOMM = false;
bool foundSSND = false;
while (pos + sizeof(chunk_struct) <= filesize) {
chunk_struct chunk;
if (sizeof(chunk) != Source()->ReadAt(pos, &chunk, sizeof(chunk))) {
TRACE("aiffReader::Sniff: chunk header reading failed\n");
return B_ERROR;
}
pos += sizeof(chunk);
switch (UINT32(chunk.chunk_id)) {
case FOURCC('C','O','M','M'):
{
if (foundCOMM)
break;
uint32 size = UINT32(chunk.chunk_size);
if (size >= 18) {
size = min_c(size, sizeof(comm));
if (size != (uint32)Source()->ReadAt(pos, &comm, size)) {
TRACE("aiffReader::Sniff: COMM chunk reading failed\n");
break;
}
if (size < 22)
comm.compression_id = B_HOST_TO_BENDIAN_INT32(FOURCC('N','O','N','E'));
foundCOMM = true;
} else {
TRACE("aiffReader::Sniff: COMM chunk too small\n");
}
break;
}
case FOURCC('S','S','N','D'):
{
if (foundSSND)
break;
uint32 size = UINT32(chunk.chunk_size);
if (size >= sizeof(ssnd)) {
if (sizeof(ssnd) != Source()->ReadAt(pos, &ssnd, sizeof(ssnd))) {
TRACE("aiffReader::Sniff: SSND chunk reading failed\n");
break;
}
fDataStart = pos + sizeof(ssnd) + UINT32(ssnd.offset);
fDataSize = UINT32(chunk.chunk_size) - sizeof(ssnd);
if (pos + fDataSize + sizeof(ssnd) > filesize)
fDataSize = filesize - pos - sizeof(ssnd);
foundSSND = true;
} else {
TRACE("aiffReader::Sniff: SSND chunk too small\n");
}
break;
}
default:
TRACE("aiffReader::Sniff: ignoring chunk 0x%08lx of %lu bytes\n", UINT32(chunk.chunk_id), UINT32(chunk.chunk_size));
break;
}
pos += UINT32(chunk.chunk_size);
pos += (pos & 1);
}
if (!foundCOMM) {
TRACE("aiffReader::Sniff: couldn't find format chunk\n");
return B_ERROR;
}
if (!foundSSND) {
TRACE("aiffReader::Sniff: couldn't find data chunk\n");
return B_ERROR;
}
TRACE("aiffReader::Sniff: we found something that looks like:\n");
TRACE(" channel_count %d\n", UINT16(comm.channel_count));
TRACE(" frame_count %ld\n", UINT32(comm.frame_count));
TRACE(" bits_per_sample %d\n", UINT16(comm.bits_per_sample));
TRACE(" sample_rate %ld\n", DecodeFrameRate(comm.sample_rate));
TRACE(" compression_id %s\n", string_for_compression(UINT32(comm.compression_id)));
TRACE(" offset %ld\n", UINT32(ssnd.offset));
TRACE(" block_size %ld\n", UINT32(ssnd.block_size));
fChannelCount = UINT16(comm.channel_count);
fFrameCount = UINT32(comm.frame_count);
fFrameRate = DecodeFrameRate(comm.sample_rate);
fDuration = (1000000LL * fFrameCount) / fFrameRate;
fValidBitsPerSample = UINT16(comm.bits_per_sample);
fBytesPerSample = (fValidBitsPerSample + 7) / 8;
fBytesPerFrame = fBytesPerSample * fChannelCount;
fFormatCode = UINT32(comm.compression_id);
if (fChannelCount < 1)
fChannelCount = 1;
if (fFrameRate < 1)
fFrameRate = 44100;
if (fBytesPerSample == 0) {
TRACE("aiffReader::Sniff: sample format not recognized\n");
return B_ERROR;
}
switch (fFormatCode) {
case FOURCC('N','O','N','E'):
case FOURCC('F','L','3','2'):
case FOURCC('f','l','3','2'):
case FOURCC('f','l','6','4'):
fRaw = true;
break;
default:
fRaw = false;
break;
}
fPosition = 0;
fBufferSize = (BUFFER_SIZE / fBytesPerFrame) * fBytesPerFrame;
fBuffer = malloc(fBufferSize);
TRACE(" fDataStart %Ld\n", fDataStart);
TRACE(" fDataSize %Ld\n", fDataSize);
TRACE(" fFrameCount %Ld\n", fFrameCount);
TRACE(" fDuration %Ld\n", fDuration);
TRACE(" fChannelCount %d\n", fChannelCount);
TRACE(" fFrameRate %ld\n", fFrameRate);
TRACE(" fValidBitsPerSample %d\n", fValidBitsPerSample);
TRACE(" fBytesPerSample %d\n", fBytesPerSample);
TRACE(" fBytesPerFrame %d\n", fBytesPerFrame);
TRACE(" fFormatCode %ld\n", fFormatCode);
TRACE(" fRaw %d\n", fRaw);
BMediaFormats formats;
if (fRaw) {
// a raw PCM format
media_format_description description;
description.family = B_BEOS_FORMAT_FAMILY;
description.u.beos.format = B_BEOS_FORMAT_RAW_AUDIO;
formats.GetFormatFor(description, &fFormat);
fFormat.u.raw_audio.frame_rate = fFrameRate;
fFormat.u.raw_audio.channel_count = fChannelCount;
switch (fFormatCode) {
case FOURCC('N','O','N','E'):
switch (fBytesPerSample) {
case 1:
fFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_CHAR;
break;
case 2:
fFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_SHORT;
break;
case 3:
// XXX broken
fFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_INT;
break;
case 4:
fFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_INT;
break;
default:
TRACE("aiffReader::Sniff: unknown bytes per sample for raw format\n");
return B_ERROR;
}
break;
case FOURCC('F','L','3','2'):
fFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_FLOAT;
break;
case FOURCC('f','l','3','2'):
fFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_FLOAT;
break;
case FOURCC('f','l','6','4'):
// XXX broken
fFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_FLOAT;
break;
default:
TRACE("aiffReader::Sniff: unknown raw format\n");
return B_ERROR;
}
fFormat.u.raw_audio.byte_order = B_MEDIA_BIG_ENDIAN;
fFormat.u.raw_audio.buffer_size = fBufferSize;
} else {
// some encoded format
media_format_description description;
description.family = B_AIFF_FORMAT_FAMILY;
description.u.aiff.codec = fFormatCode;
formats.GetFormatFor(description, &fFormat);
fFormat.u.encoded_audio.output.frame_rate = fFrameRate;
fFormat.u.encoded_audio.output.channel_count = fChannelCount;
}
*streamCount = 1;
return B_OK;
}
void
aiffReader::GetFileFormatInfo(media_file_format *mff)
{
mff->capabilities = media_file_format::B_READABLE
| media_file_format::B_KNOWS_ENCODED_AUDIO
| media_file_format::B_IMPERFECTLY_SEEKABLE;
mff->family = B_MISC_FORMAT_FAMILY;
mff->version = 100;
strcpy(mff->mime_type, "audio/x-aiff");
strcpy(mff->file_extension, "aiff");
strcpy(mff->short_name, "AIFF / AIFF-C");
strcpy(mff->pretty_name, "Audio Interchange File Format");
}
status_t
aiffReader::AllocateCookie(int32 streamNumber, void **cookie)
{
return B_OK;
}
status_t
aiffReader::FreeCookie(void *cookie)
{
return B_OK;
}
status_t
aiffReader::GetStreamInfo(void *cookie, int64 *frameCount, bigtime_t *duration,
media_format *format, void **infoBuffer, int32 *infoSize)
{
*frameCount = fFrameCount;
*duration = fDuration;
*format = fFormat;
*infoBuffer = 0;
*infoSize = 0;
return B_OK;
}
status_t
aiffReader::Seek(void *cookie,
uint32 seekTo,
int64 *frame, bigtime_t *time)
{
int64 pos;
if (seekTo & B_MEDIA_SEEK_TO_FRAME) {
if (fRaw)
pos = *frame * fBytesPerFrame;
else
pos = (*frame * fDataSize) / fFrameCount;
pos = (pos / fBytesPerFrame) * fBytesPerFrame; // round down to a block start
TRACE("aiffReader::Seek to frame %Ld, pos %Ld\n", *frame, pos);
} else if (seekTo & B_MEDIA_SEEK_TO_TIME) {
if (fRaw)
pos = (*time * fFrameRate * fBytesPerFrame) / 1000000LL;
else
pos = (*time * fDataSize) / fDuration;
pos = (pos / fBytesPerFrame) * fBytesPerFrame; // round down to a block start
TRACE("aiffReader::Seek to time %Ld, pos %Ld\n", *time, pos);
} else {
return B_ERROR;
}
if (fRaw)
*frame = pos / fBytesPerFrame;
else
*frame = (pos * fFrameCount) / fDataSize;
*time = (*frame * 1000000LL) / fFrameRate;
TRACE("aiffReader::Seek newtime %Ld\n", *time);
TRACE("aiffReader::Seek newframe %Ld\n", *frame);
if (pos < 0 || pos > fDataSize) {
TRACE("aiffReader::Seek invalid position %Ld\n", pos);
return B_ERROR;
}
fPosition = pos;
return B_OK;
}
status_t
aiffReader::GetNextChunk(void *cookie,
void **chunkBuffer, int32 *chunkSize,
media_header *mediaHeader)
{
// XXX it might be much better to not return any start_time information for encoded formats here,
// XXX and instead use the last time returned from seek and count forward after decoding.
mediaHeader->start_time = ((fPosition / fBytesPerFrame) * 1000000LL) / fFrameRate;
mediaHeader->file_pos = fDataStart + fPosition;
int64 maxreadsize = fDataSize - fPosition;
int32 readsize = fBufferSize;
if (maxreadsize < readsize)
readsize = maxreadsize;
if (readsize == 0)
return B_LAST_BUFFER_ERROR;
if (readsize != Source()->ReadAt(fDataStart + fPosition, fBuffer, readsize)) {
TRACE("aiffReader::GetNextChunk: unexpected read error\n");
return B_ERROR;
}
// XXX if the stream has more than two channels, we need to reorder channel data here
fPosition += readsize;
*chunkBuffer = fBuffer;
*chunkSize = readsize;
return B_OK;
}
uint32
aiffReader::DecodeFrameRate(void *_80bit_float)
{
// algorithm from http://www.borg.com/~jglatt/tech/aiff.htm
uint32 mantissa;
uint32 last;
uint32 exp;
mantissa = (uint32)B_BENDIAN_TO_HOST_INT32(*(uint32 *)((char *)_80bit_float + 2));
exp = 30 - *(uint8 *)((char *)_80bit_float + 1);
if (exp > 32)
return 0;
last = 0;
while (exp--) {
last = mantissa;
mantissa >>= 1;
}
if (last & 1)
mantissa++;
return mantissa;
}
Reader *
aiffReaderPlugin::NewReader()
{
return new aiffReader;
}
MediaPlugin *instantiate_plugin()
{
return new aiffReaderPlugin;
}
@@ -0,0 +1,66 @@
#include "ReaderPlugin.h"
#include "aiff.h"
class aiffReader : public Reader
{
public:
aiffReader();
~aiffReader();
const char *Copyright();
status_t Sniff(int32 *streamCount);
void GetFileFormatInfo(media_file_format *mff);
status_t AllocateCookie(int32 streamNumber, void **cookie);
status_t FreeCookie(void *cookie);
status_t GetStreamInfo(void *cookie, int64 *frameCount, bigtime_t *duration,
media_format *format, void **infoBuffer, int32 *infoSize);
status_t Seek(void *cookie,
uint32 seekTo,
int64 *frame, bigtime_t *time);
status_t GetNextChunk(void *cookie,
void **chunkBuffer, int32 *chunkSize,
media_header *mediaHeader);
private:
uint32 DecodeFrameRate(void *_80bit_float);
BPositionIO *Source() { return fSource; }
private:
BPositionIO * fSource;
media_format fFormat;
int64 fDataStart;
int64 fDataSize;
int64 fFrameCount;
bigtime_t fDuration;
bool fRaw;
int64 fPosition;
int fChannelCount;
int32 fFrameRate;
int fValidBitsPerSample;
int fBytesPerSample;
int fBytesPerFrame;
uint32 fFormatCode;
void * fBuffer;
int32 fBufferSize;
};
class aiffReaderPlugin : public ReaderPlugin
{
public:
Reader *NewReader();
};
MediaPlugin *instantiate_plugin();