diff --git a/src/add-ons/media/plugins/Jamfile b/src/add-ons/media/plugins/Jamfile index d609443bcb..cd1958ed99 100644 --- a/src/add-ons/media/plugins/Jamfile +++ b/src/add-ons/media/plugins/Jamfile @@ -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 ; diff --git a/src/add-ons/media/plugins/aiff_reader/aiff.h b/src/add-ons/media/plugins/aiff_reader/aiff.h new file mode 100644 index 0000000000..4230a1b63b --- /dev/null +++ b/src/add-ons/media/plugins/aiff_reader/aiff.h @@ -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 diff --git a/src/add-ons/media/plugins/aiff_reader/aiff_reader.cpp b/src/add-ons/media/plugins/aiff_reader/aiff_reader.cpp new file mode 100644 index 0000000000..016f01e6d8 --- /dev/null +++ b/src/add-ons/media/plugins/aiff_reader/aiff_reader.cpp @@ -0,0 +1,413 @@ +#include +#include +#include +#include +#include +#include +#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(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; +} diff --git a/src/add-ons/media/plugins/aiff_reader/aiff_reader.h b/src/add-ons/media/plugins/aiff_reader/aiff_reader.h new file mode 100644 index 0000000000..20595990b4 --- /dev/null +++ b/src/add-ons/media/plugins/aiff_reader/aiff_reader.h @@ -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();