diff --git a/src/add-ons/media/plugins/ogg/Jamfile b/src/add-ons/media/plugins/ogg/Jamfile index 8ea6c2ab97..2fe2abc21e 100644 --- a/src/add-ons/media/plugins/ogg/Jamfile +++ b/src/add-ons/media/plugins/ogg/Jamfile @@ -6,7 +6,11 @@ SubDirHdrs [ FDirName $(SUBDIR) libogg ] ; Addon ogg : media plugins : OggReaderPlugin.cpp - OggReaderPluginCodecs.cpp + OggStream.cpp + OggSpeexStream.cpp + OggTheoraStream.cpp + OggTobiasStream.cpp + OggVorbisStream.cpp : false : libogg.a ; diff --git a/src/add-ons/media/plugins/ogg/OggReaderPlugin.cpp b/src/add-ons/media/plugins/ogg/OggReaderPlugin.cpp index 5bf2c0fa74..535312ddb3 100644 --- a/src/add-ons/media/plugins/ogg/OggReaderPlugin.cpp +++ b/src/add-ons/media/plugins/ogg/OggReaderPlugin.cpp @@ -7,6 +7,7 @@ #include #include #include "OggReaderPlugin.h" +#include "OggStream.h" #define TRACE_THIS 1 #if TRACE_THIS @@ -15,20 +16,19 @@ #define TRACE(a...) ((void)0) #endif -oggReader::oggReader() +OggReader::OggReader() { - TRACE("oggReader::oggReader\n"); + TRACE("OggReader::OggReader\n"); ogg_sync_init(&fSync); - fSeekable = 0; } -oggReader::~oggReader() +OggReader::~OggReader() { - TRACE("oggReader::~oggReader\n"); - ogg_stream_map::iterator i = fStreams.begin(); + TRACE("OggReader::~OggReader\n"); + serialno_OggStream_map::iterator i = fStreams.begin(); while (i != fStreams.end()) { - ogg_stream_map::iterator j = i; + serialno_OggStream_map::iterator j = i; i++; delete j->second; } @@ -37,102 +37,151 @@ oggReader::~oggReader() const char * -oggReader::Copyright() +OggReader::Copyright() { return "ogg reader, " B_UTF8_COPYRIGHT " by Andrew Bachmann"; } + status_t -oggReader::GetPage(ogg_page * page, int read_size, bool short_page) +OggReader::GetPage(ogg_page * page, int read_size, bool short_page) { -// TRACE("oggReader::GetPage\n"); + TRACE("OggReader::GetPage\n"); +retry: + static off_t next_position = (fSeekable ? fSeekable->Position() : -1); + off_t position = next_position; int result = ogg_sync_pageout(&fSync,page); // first read leftovers while (result == 0) { char * buffer = ogg_sync_buffer(&fSync,read_size); ssize_t bytes = Source()->Read(buffer,read_size); if (bytes == 0) { - TRACE("oggReader::GetPage: Read: no data\n"); + TRACE("OggReader::GetPage: Read: no data\n"); return B_LAST_BUFFER_ERROR; } if (bytes < 0) { - TRACE("oggReader::GetPage: Read: error\n"); + TRACE("OggReader::GetPage: Read: error\n"); return bytes; } if (ogg_sync_wrote(&fSync,bytes) != 0) { - TRACE("oggReader::GetPage: ogg_sync_wrote failed?: error\n"); + TRACE("OggReader::GetPage: ogg_sync_wrote failed?: error\n"); return B_ERROR; } result = ogg_sync_pageout(&fSync,page); if (short_page && (result != 1)) { - TRACE("oggReader::GetPage: short page not found: error\n"); + TRACE("OggReader::GetPage: short page not found: error\n"); return B_ERROR; } } if (result == -1) { - TRACE("oggReader::GetPage: ogg_sync_pageout: not synced: error\n"); + TRACE("OggReader::GetPage: ogg_sync_pageout: not synced: error\n"); return B_ERROR; } #ifdef STRICT_OGG if (ogg_page_version(page) != 0) { - TRACE("oggReader::GetPage: ogg_page_version: error in page encoding: error\n"); + TRACE("OggReader::GetPage: ogg_page_version: error in page encoding: error\n"); return B_ERROR; } #endif + next_position += page->header_len + page->body_len; long serialno = ogg_page_serialno(page); if (fStreams.find(serialno) == fStreams.end()) { // this is an unknown serialno if (ogg_page_bos(page) == 0) { - TRACE("oggReader::GetPage: non-bos packet with unknown serialno\n"); + TRACE("OggReader::GetPage: non-bos page with unknown serialno\n"); #ifdef STRICT_OGG return B_ERROR; +#else + // silently discard packets with unknown serialno + goto retry; #endif } - ogg_stream_state * stream = new ogg_stream_state; - if (ogg_stream_init(stream,serialno) != 0) { + // this is a beginning of stream page + ogg_stream_state stream; + if (ogg_stream_init(&stream, serialno) != 0) { TRACE("oggReader::GetPage: ogg_stream_init failed?: error\n"); return B_ERROR; } - fStreams[serialno] = stream; - } else if (ogg_page_bos(page) > 0) { - TRACE("oggReader::GetPage: bos packet with duplicate serialno\n"); -#ifdef STRICT_OGG - return B_ERROR; -#else - if (ogg_stream_reset(fStreams[serialno]) != 0) { - TRACE("oggReader::GetPage: ogg_stream_reset failed?: error\n"); + if (ogg_stream_pagein(&stream, page) != 0) { + TRACE("oggReader::GetPage: ogg_stream_pagein: failed: error\n"); return B_ERROR; } -#endif + ogg_packet packet; + if (ogg_stream_packetout(&stream, &packet) != 1) { +#ifdef STRICT_OGG + return B_ERROR; +#endif STRICT_OGG + } + class Interface : public GetPageInterface { + private: + OggReader * reader; + long serialno; + public: + Interface(OggReader * reader, long serialno) { + this->reader = reader; + this->serialno = serialno; + } + virtual status_t GetNextPage() { + ogg_page next_page; + do { + status_t result = reader->GetPage(&next_page); + if (result != B_OK) { + return result; + } + } while (ogg_page_serialno(&next_page) != serialno); + return B_OK; + } + }; + fStreams[serialno] = OggStream::makeOggStream(new Interface(this, serialno), serialno, packet); + fCookies.push_back(fStreams[serialno]); } - if (ogg_stream_pagein(fStreams[serialno],page) != 0) { - TRACE("oggReader::GetPage: ogg_stream_pagein: failed: error\n"); - return B_ERROR; + return fStreams[serialno]->AddPage(position,page); +} + + +static BPositionIO * +get_seekable(BDataIO * data) +{ + // try to cast to BPositionIO then perform a series of + // sanity checks to ensure that seeking is reliable + BPositionIO * seekable = dynamic_cast(data); + if (seekable == 0) { + return 0; } - return B_OK; + // first try to get our current location + off_t current = seekable->Seek(0,SEEK_CUR); + if (current < 0) { + return 0; + } + // check it against position + if (current != seekable->Position()) { + return 0; + } + // next try to seek to our current location (absolutely) + if (seekable->Seek(current,SEEK_SET) < 0) { + return 0; + } + // next try to seek to the start of the stream (absolutely) + if (seekable->Seek(0,SEEK_SET) < 0) { + return 0; + } + // then seek back to where we started + if (seekable->Seek(current,SEEK_SET) < 0) { + // really screwed + return 0; + } + return seekable; } status_t -oggReader::Sniff(int32 *streamCount) +OggReader::Sniff(int32 *streamCount) { - TRACE("oggReader::Sniff\n"); + TRACE("OggReader::Sniff\n"); #ifdef STRICT_OGG bool short_page = true; #else bool short_page = false; #endif - fSeekable = dynamic_cast(Source()); - - off_t current_position = 0; - if (fSeekable) { - current_position = fSeekable->Seek(0,SEEK_CUR); - if (current_position < 0) { - fSeekable = 0; - } else { - if (fSeekable->Seek(0,SEEK_SET) < 0) { - fSeekable = 0; - } - } - } + fSeekable = get_seekable(Source()); ogg_page page; if (GetPage(&page,4096,short_page) != B_OK) { @@ -141,79 +190,34 @@ oggReader::Sniff(int32 *streamCount) // page sanity checks if (ogg_page_version(&page) != 0) { - TRACE("oggReader::Sniff: ogg_page_version: error in page encoding: not ogg\n"); + TRACE("OggReader::Sniff: ogg_page_version: error in page encoding: not ogg\n"); return B_ERROR; } #ifdef STRICT_OGG if (ogg_page_bos(&page) == 0) { - TRACE("oggReader::Sniff: ogg_page_bos: not beginning of a bitstream: not ogg\n"); + TRACE("OggReader::Sniff: ogg_page_bos: not beginning of a bitstream: not ogg\n"); return B_ERROR; } if (ogg_page_continued(&page) != 0) { - TRACE("oggReader::Sniff: ogg_page_continued: continued page: not ogg\n"); + TRACE("OggReader::Sniff: ogg_page_continued: continued page: not ogg\n"); return B_ERROR; } #endif STRICT_OGG while (ogg_page_bos(&page) > 0) { - int serialno = ogg_page_serialno(&page); - ogg_stream_state * stream = fStreams[serialno]; - ogg_packet packet; - if (ogg_stream_packetout(stream,&packet) != 1) { -#ifdef STRICT_OGG - return B_ERROR; -#endif STRICT_OGG - } - // save a copy of the packet information - unsigned char * buffer = new unsigned char[packet.bytes]; - memcpy(buffer, packet.packet, packet.bytes); - packet.packet = buffer; - fHeaderPackets[serialno].push_back(packet); - fHeaderPacketsLeft[serialno] = GetExtraHeaderPacketCount(&packet); if (GetPage(&page) != B_OK) { return B_ERROR; } } - packet_count_map::iterator iter = fHeaderPacketsLeft.begin(); - while (iter != fHeaderPacketsLeft.end()) { - int current_serial = iter->first; - while (fHeaderPacketsLeft[current_serial] > 0) { - int serialno = ogg_page_serialno(&page); - if (fHeaderPacketsLeft[serialno] > 0) { - ogg_stream_state * stream = fStreams[serialno]; - ogg_packet packet; - ogg_stream_packetout(stream,&packet); - // save a copy of the packet information - unsigned char * buffer = new unsigned char[packet.bytes]; - memcpy(buffer, packet.packet, packet.bytes); - packet.packet = buffer; - fHeaderPackets[serialno].push_back(packet); - fHeaderPacketsLeft[serialno]--; - } - if (GetPage(&page) != B_OK) { - return B_ERROR; - } - } - iter++; - } - *streamCount = fStreams.size(); - if (fSeekable) { - fPostSniffPosition = fSeekable->Seek(0,SEEK_CUR); - if (fPostSniffPosition < 0) { - fSeekable = 0; - } else { - if (fSeekable->Seek(current_position,SEEK_SET) < 0) { - fSeekable = 0; - } - } - } + *streamCount = fCookies.size(); return B_OK; } + void -oggReader::GetFileFormatInfo(media_file_format *mff) +OggReader::GetFileFormatInfo(media_file_format *mff) { - TRACE("oggReader::GetFileFormatInfo\n"); + TRACE("OggReader::GetFileFormatInfo\n"); mff->capabilities = media_file_format::B_READABLE | media_file_format::B_IMPERFECTLY_SEEKABLE | media_file_format::B_KNOWS_ENCODED_VIDEO @@ -229,161 +233,71 @@ oggReader::GetFileFormatInfo(media_file_format *mff) status_t -oggReader::AllocateCookie(int32 streamNumber, void **cookie) +OggReader::AllocateCookie(int32 streamNumber, void **cookie) { - TRACE("oggReader::AllocateCookie\n"); - ogg_stream_map::iterator i = fStreams.begin(); - while (streamNumber > 0) { - if (i == fStreams.end()) { - TRACE("oggReader::AllocateCookie: invalid streamNumber: bail\n"); - return B_ERROR; - } - i++; - streamNumber--; + TRACE("OggReader::AllocateCookie\n"); + if (streamNumber < 0 || streamNumber > (signed)fCookies.size()) { + TRACE("OggReader::AllocateCookie: invalid streamNumber: bail\n"); + return B_ERROR; } + OggStream * stream = fCookies[streamNumber]; // store the cookie - *cookie = (void*)(i->second); + *cookie = stream; return B_OK; } status_t -oggReader::FreeCookie(void *cookie) +OggReader::FreeCookie(void *cookie) { - TRACE("oggReader::FreeCookie\n"); + TRACE("OggReader::FreeCookie\n"); return B_OK; } + status_t -oggReader::GetStreamInfo(void *cookie, int64 *frameCount, bigtime_t *duration, +OggReader::GetStreamInfo(void *cookie, int64 *frameCount, bigtime_t *duration, media_format *format, void **infoBuffer, int32 *infoSize) { - TRACE("oggReader::GetStreamInfo\n"); - ogg_stream_state * stream = static_cast(cookie); - memset(format, 0, sizeof(*format)); - std::vector * ogg_packets = &fHeaderPackets[stream->serialno]; - format->SetMetaData((void*)ogg_packets,sizeof(ogg_packets)); - GetCodecStreamInfo(stream,frameCount,duration,format); + TRACE("OggReader::GetStreamInfo\n"); *infoBuffer = 0; *infoSize = 0; - return B_OK; + OggStream * stream = static_cast(cookie); + return stream->GetStreamInfo(frameCount,duration,format); } status_t -oggReader::Seek(void *cookie, +OggReader::Seek(void *cookie, uint32 seekTo, int64 *frame, bigtime_t *time) { - TRACE("oggReader::Seek\n"); - if (!fSeekable) { - TRACE("oggReader::Seek: not a PositionIO: not seekable\n"); - return B_ERROR; - } - ogg_stream_state * stream = static_cast(cookie); - int position; - if (seekTo & B_MEDIA_SEEK_TO_FRAME) { - position = fPostSniffPosition+frameToPosition(stream,*frame); - } else if (seekTo & B_MEDIA_SEEK_TO_TIME) { - position = fPostSniffPosition+timeToPosition(stream,*time); - } else { - return B_ERROR; - } - // TODO: use B_MEDIA_SEEK_CLOSEST_BACKWARD, B_MEDIA_SEEK_CLOSEST_FORWARD to find key frame - if (ogg_sync_reset(&fSync) != 0) { - TRACE("oggReader::Seek: ogg_sync_reset failed?: error\n"); - return B_ERROR; - } - if (ogg_stream_reset(stream) != 0) { - TRACE("oggReader::Seek: ogg_stream_reset failed?: error\n"); - return B_ERROR; - } - if (fSeekable->Seek(position,SEEK_SET) < 0) { - TRACE("oggReader::Seek: Seek failed: error\n"); - return B_ERROR; - } - // align to page boundary - ogg_page page; - ogg_sync_state sync; - ogg_sync_init(&sync); - while (true) { - char * buffer = ogg_sync_buffer(&sync,4096); - ssize_t bytes = fSeekable->Read(buffer,4096); - if (bytes == 0) { - TRACE("oggReader::Seek: Read: no data\n"); - goto synced; - } - if (bytes < 0) { - TRACE("oggReader::Seek: Read: error\n"); - ogg_sync_clear(&sync); - return bytes; - } - if (ogg_sync_wrote(&sync,bytes) != 0) { - TRACE("oggReader::Seek: ogg_sync_wrote failed?: error\n"); - ogg_sync_clear(&sync); - return B_ERROR; - } - int offset; - while ((offset = ogg_sync_pageseek(&sync,&page)) != 0) { - if (offset > 0) { - goto synced; - } - position += -offset; - } - } -synced: - ogg_sync_clear(&sync); - if (fSeekable->Seek(position,SEEK_SET) < 0) { - TRACE("oggReader::Seek: Seek failed: error\n"); - return B_ERROR; - } - // output the values from where we regained sync - *frame = positionToFrame(stream,position); - *time = positionToTime(stream,position); - return B_OK; + TRACE("OggReader::Seek\n"); + OggStream * stream = static_cast(cookie); + return stream->Seek(seekTo,frame,time); } status_t -oggReader::GetNextChunk(void *cookie, +OggReader::GetNextChunk(void *cookie, void **chunkBuffer, int32 *chunkSize, media_header *mediaHeader) { -// TRACE("oggReader::GetNextChunk\n"); - if (fSeekable && (fSeekable->Seek(0,SEEK_CUR) < fPostSniffPosition)) { - fSeekable->Seek(fPostSniffPosition,SEEK_SET); - } - ogg_stream_state * stream = static_cast(cookie); - while (ogg_stream_packetpeek(stream,NULL) != 1) { - ogg_page page; - do { - status_t status = GetPage(&page); - if (status != B_OK) { - return status; - } - } while (ogg_page_serialno(&page) != stream->serialno); - } - ogg_packet packet; - if (ogg_stream_packetout(stream,&packet) != 1) { - return B_ERROR; - } - fPackets[stream->serialno] = packet; - *chunkBuffer = (void*)&fPackets[stream->serialno]; - *chunkSize = sizeof(ogg_packet); - - return B_OK; + TRACE("OggReader::GetNextChunk\n"); + OggStream * stream = static_cast(cookie); + return stream->GetNextChunk(chunkBuffer,chunkSize,mediaHeader); } Reader * -oggReaderPlugin::NewReader() +OggReaderPlugin::NewReader() { - return new oggReader; + return new OggReader; } MediaPlugin * instantiate_plugin() { - return new oggReaderPlugin; + return new OggReaderPlugin; } diff --git a/src/add-ons/media/plugins/ogg/OggReaderPlugin.h b/src/add-ons/media/plugins/ogg/OggReaderPlugin.h index 7f5129a1da..a6a830c0db 100644 --- a/src/add-ons/media/plugins/ogg/OggReaderPlugin.h +++ b/src/add-ons/media/plugins/ogg/OggReaderPlugin.h @@ -8,16 +8,16 @@ namespace BPrivate { namespace media { -typedef std::map ogg_stream_map; -typedef std::map ogg_packet_map; -typedef std::map > ogg_packets_map; -typedef std::map packet_count_map; +class OggStream; -class oggReader : public Reader +typedef std::map serialno_OggStream_map; +typedef std::vector OggStream_vector; + +class OggReader : public Reader { public: - oggReader(); - ~oggReader(); + OggReader(); + ~OggReader(); const char *Copyright(); @@ -27,43 +27,34 @@ public: status_t AllocateCookie(int32 streamNumber, void **cookie); status_t FreeCookie(void *cookie); - + + // delegated to OggStream 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: +protected: status_t GetPage(ogg_page * page, int read_size = 4*B_PAGE_SIZE, bool short_page = false); - int GetExtraHeaderPacketCount(ogg_packet * packet); - status_t GetCodecStreamInfo(ogg_stream_state * stream, - int64 *frameCount, bigtime_t *duration, - media_format *format); - - int64 positionToFrame(ogg_stream_state * stream, off_t position); - off_t frameToPosition(ogg_stream_state * stream, int64 frame); - bigtime_t positionToTime(ogg_stream_state * stream, off_t position); - off_t timeToPosition(ogg_stream_state * stream, bigtime_t time); - BPositionIO * fSeekable; - off_t fPostSniffPosition; - - ogg_sync_state fSync; - ogg_packets_map fHeaderPackets; - packet_count_map fHeaderPacketsLeft; - ogg_stream_map fStreams; - ogg_packet_map fPackets; + ogg_sync_state fSync; + serialno_OggStream_map fStreams; + OggStream_vector fCookies; + BPositionIO * fSeekable; + +private: + class GetPageInterface { + public: + virtual status_t GetNextPage() = 0; + }; }; - -class oggReaderPlugin : public ReaderPlugin +class OggReaderPlugin : public ReaderPlugin { public: Reader *NewReader(); diff --git a/src/add-ons/media/plugins/ogg/OggReaderPluginCodecs.cpp b/src/add-ons/media/plugins/ogg/OggReaderPluginCodecs.cpp deleted file mode 100644 index cd42e1c065..0000000000 --- a/src/add-ons/media/plugins/ogg/OggReaderPluginCodecs.cpp +++ /dev/null @@ -1,397 +0,0 @@ -#include -#include "OggReaderPlugin.h" - -#define TRACE_THIS 1 -#if TRACE_THIS - #define TRACE printf -#else - #define TRACE(a...) ((void)0) -#endif - -// codec identification - -static bool -findIdentifier(ogg_packet * packet, const char * id, uint location) -{ - uint length = strlen(id); - if ((unsigned)packet->bytes < location+length) { - return false; - } - return !memcmp(&packet->packet[location], id, length); -} - -inline size_t -AudioBufferSize(media_raw_audio_format * raf, bigtime_t buffer_duration = 50000 /* 50 ms */) -{ - return (raf->format & 0xf) * (raf->channel_count) - * (size_t)((raf->frame_rate * buffer_duration) / 1000000.0); -} - -/*---- vorbis ----*/ - -typedef struct vorbis_info{ - int version; - int channels; - long rate; - - /* The below bitrate declarations are *hints*. - Combinations of the three values carry the following implications: - - all three set to the same value: - implies a fixed rate bitstream - only nominal set: - implies a VBR stream that averages the nominal bitrate. No hard - upper/lower limit - upper and or lower set: - implies a VBR bitstream that obeys the bitrate limits. nominal - may also be set to give a nominal rate. - none set: - the coder does not care to speculate. - */ - - long bitrate_upper; - long bitrate_nominal; - long bitrate_lower; - long bitrate_window; - - void *codec_setup; -} vorbis_info; - -// based on libvorbis/info.c _vorbis_unpack_info -static int _vorbis_unpack_info(vorbis_info *vi,oggpack_buffer *opb){ - vi->version = oggpack_read(opb, 32); - if (vi->version != 0) { - return -1; - } - vi->channels = oggpack_read(opb, 8); - vi->rate = oggpack_read(opb, 32); - vi->bitrate_upper = oggpack_read(opb, 32); - vi->bitrate_nominal = oggpack_read(opb, 32); - vi->bitrate_lower = oggpack_read(opb, 32); - oggpack_read(opb, 4); // discard blocksizes 0 - oggpack_read(opb, 4); // discard blocksizes 1 - if (vi->rate < 1) { - return -1; - } - if (vi->channels < 1) { - return -1; - } - if (oggpack_read(opb, 1) != 1) { - return -1; - } /* EOP check */ - return 0; -} - -static bool -isVorbisPacket(ogg_packet * packet) -{ - return findIdentifier(packet,"vorbis",1); -} - -static int -GetVorbisExtraHeaderPacketCount(ogg_packet * packet) -{ - return 2; -} - -static status_t -GetVorbisCodecStreamInfo(std::vector * packets, - int64 *frameCount, bigtime_t *duration, - media_format *format) -{ - ogg_packet * packet = &((*packets)[0]); - - // based on libvorbis/info.c vorbis_synthesis_headerin(...) - oggpack_buffer opb; - oggpack_readinit(&opb, packet->packet, packet->bytes); - int packtype = oggpack_read(&opb, 8); - // discard vorbis string - for (uint i = 0 ; i < sizeof("vorbis") - 1 ; i++) { - oggpack_read(&opb, 8); - } - if (packtype != 0x01) { - return B_ERROR; // first packet was not an info packet - } - if (!packet->b_o_s) { - return B_ERROR; // first packet was not beginning of stream - } - vorbis_info info; - if (_vorbis_unpack_info(&info, &opb) != 0) { - return B_ERROR; // couldn't unpack info - } - - format->type = B_MEDIA_ENCODED_AUDIO; - format->user_data_type = B_CODEC_TYPE_INFO; - strncpy((char*)format->user_data, "vorb", 4); - // ToDo: that won't work any longer - //format->u.encoded_audio.encoding - // = (media_encoded_audio_format::audio_encoding)'vorb'; - if (info.bitrate_nominal > 0) { - format->u.encoded_audio.bit_rate = info.bitrate_nominal; - } else if (info.bitrate_upper > 0) { - format->u.encoded_audio.bit_rate = info.bitrate_upper; - } else if (info.bitrate_lower > 0) { - format->u.encoded_audio.bit_rate = info.bitrate_lower; - } - if (info.channels == 1) { - format->u.encoded_audio.multi_info.channel_mask = B_CHANNEL_LEFT; - *frameCount *= 2; - *duration *= 2; - } else { - format->u.encoded_audio.multi_info.channel_mask = B_CHANNEL_LEFT | B_CHANNEL_RIGHT; - } - format->u.encoded_audio.frame_size = sizeof(ogg_packet); - format->u.encoded_audio.output.frame_rate = (float)info.rate; - format->u.encoded_audio.output.channel_count = info.channels; - format->u.encoded_audio.output.format = media_raw_audio_format::B_AUDIO_FLOAT; - format->u.encoded_audio.output.byte_order = B_MEDIA_HOST_ENDIAN; - format->u.encoded_audio.output.buffer_size - = AudioBufferSize(&format->u.encoded_audio.output); - return B_OK; -} - - -/*---- speex ----*/ - - -// http://www.speex.org/manual/node7.html#SECTION00073000000000000000 -// libspeex/speex_header.h -typedef struct SpeexHeader { - char speex_string[8]; /**< Identifies a Speex bit-stream, always set to "Speex " */ - char speex_version[20]; /**< Speex version */ - int speex_version_id; /**< Version for Speex (for checking compatibility) */ - int header_size; /**< Total size of the header ( sizeof(SpeexHeader) ) */ - int rate; /**< Sampling rate used */ - int mode; /**< Mode used (0 for narrowband, 1 for wideband) */ - int mode_bitstream_version; /**< Version ID of the bit-stream */ - int nb_channels; /**< Number of channels encoded */ - int bitrate; /**< Bit-rate used */ - int frame_size; /**< Size of frames */ - int vbr; /**< 1 for a VBR encoding, 0 otherwise */ - int frames_per_packet; /**< Number of frames stored per Ogg packet */ - int extra_headers; /**< Number of additional headers after the comments */ - int reserved1; /**< Reserved for future use, must be zero */ - int reserved2; /**< Reserved for future use, must be zero */ -} SpeexHeader; - -static bool -isSpeexPacket(ogg_packet * packet) -{ - return findIdentifier(packet,"Speex ",0); -} - -static int -GetSpeexExtraHeaderPacketCount(ogg_packet * packet) -{ - if (packet->bytes < (signed)sizeof(SpeexHeader)) { - return -1; - } - void * data = &(packet->packet[0]); - SpeexHeader * header = (SpeexHeader *)data; - return 1 + header->extra_headers; -} - -static status_t -GetSpeexCodecStreamInfo(std::vector * packets, - int64 *frameCount, bigtime_t *duration, - media_format *format) -{ - ogg_packet * packet = &((*packets)[0]); - if (packet->bytes < (signed)sizeof(SpeexHeader)) { - return B_ERROR; - } - void * data = &(packet->packet[0]); - SpeexHeader * header = (SpeexHeader *)data; - - format->type = B_MEDIA_ENCODED_AUDIO; - format->user_data_type = B_CODEC_TYPE_INFO; - strncpy((char*)format->user_data,"Spee",4); - // ToDo: that won't work any longer - //format->u.encoded_audio.encoding - // = (media_encoded_audio_format::audio_encoding)'Spee'; - if (header->bitrate > 0) { - format->u.encoded_audio.bit_rate = header->bitrate; - } else { - // TODO: manually compute it where possible - } - if (header->nb_channels == 1) { - format->u.encoded_audio.multi_info.channel_mask = B_CHANNEL_LEFT; - *frameCount *= 2; - *duration *= 2; - } else { - format->u.encoded_audio.multi_info.channel_mask = B_CHANNEL_LEFT | B_CHANNEL_RIGHT; - } - format->u.encoded_audio.frame_size = sizeof(ogg_packet); - format->u.encoded_audio.output.frame_rate = header->rate; - format->u.encoded_audio.output.channel_count = header->nb_channels; - format->u.encoded_audio.output.format = media_raw_audio_format::B_AUDIO_FLOAT; - format->u.encoded_audio.output.byte_order = B_MEDIA_HOST_ENDIAN; - // allocate buffer, round up to nearest speex output_length size - int buffer_size = AudioBufferSize(&format->u.encoded_audio.output); - int output_length = header->frame_size * header->nb_channels * - (format->u.encoded_audio.output.format & 0xf); - buffer_size = ((buffer_size - 1) / output_length + 1) * output_length; - format->u.encoded_audio.output.buffer_size = buffer_size; - return B_OK; -} - - -/*---- tobias ----*/ - - -// http://tobias.everwicked.com/packfmt.htm -typedef struct tobias_stream_header_video -{ - ogg_int32_t width; - ogg_int32_t height; -} tobias_stream_header_video; - -typedef struct tobias_stream_header_audio -{ - ogg_int16_t channels; - ogg_int16_t blockalign; - ogg_int32_t avgbytespersec; -} tobias_stream_header_audio; - -typedef struct tobias_stream_header -{ - char streamtype[8]; - char subtype[4]; - - ogg_int32_t size; // size of the structure - - ogg_int64_t time_unit; // in reference time - ogg_int64_t samples_per_unit; - ogg_int32_t default_len; // in media time - - ogg_int32_t buffersize; - ogg_int16_t bits_per_sample; - - union { - // Video specific - tobias_stream_header_video video; - // Audio specific - tobias_stream_header_audio audio; - }; -} tobias_stream_header; - - -static bool -isTobiasPacket(ogg_packet * packet) -{ - return findIdentifier(packet,"video",1); -} - -static int -GetTobiasExtraHeaderPacketCount(ogg_packet * packet) -{ - return 1; -} - -static status_t -GetTobiasCodecStreamInfo(std::vector * packets, - int64 *frameCount, bigtime_t *duration, - media_format *format) -{ - ogg_packet * packet = &((*packets)[0]); - if (packet->bytes < (signed)sizeof(tobias_stream_header)) { - return B_ERROR; - } - void * data = &(packet->packet[1]); - tobias_stream_header * header = (tobias_stream_header *)data; - - format->type = B_MEDIA_ENCODED_VIDEO; - format->user_data_type = B_CODEC_TYPE_INFO; - strncpy((char*)format->user_data,header->subtype,4); - //int32 encoding = header->subtype[0] << 24 | header->subtype[1] << 16 - // | header->subtype[2] << 8 | header->subtype[3]; - // ToDo: that won't work any longer - //format->u.encoded_video.encoding - // = (media_encoded_video_format::video_encoding)encoding; - format->u.encoded_video.frame_size - = header->video.width * header->video.height; - format->u.encoded_video.output.display.line_width = header->video.width; - format->u.encoded_video.output.display.line_count = header->video.height; - // TODO: wring more info out of the headers - return B_OK; -} - -/*---- generic routines ----*/ - - -int -oggReader::GetExtraHeaderPacketCount(ogg_packet * packet) -{ - if (isVorbisPacket(packet)) { - return GetVorbisExtraHeaderPacketCount(packet); - } - if (isTobiasPacket(packet)) { - return GetTobiasExtraHeaderPacketCount(packet); - } - if (isSpeexPacket(packet)) { - return GetSpeexExtraHeaderPacketCount(packet); - } - return 0; -} - -status_t -oggReader::GetCodecStreamInfo(ogg_stream_state * stream, - int64 *frameCount, bigtime_t *duration, - media_format *format) -{ - std::vector * packets = &fHeaderPackets[stream->serialno]; - if (fSeekable) { - off_t input_length; - { - off_t current_position = fSeekable->Seek(0,SEEK_CUR); - input_length = fSeekable->Seek(0,SEEK_END)-fPostSniffPosition; - fSeekable->Seek(current_position,SEEK_SET); - } - *frameCount = positionToFrame(stream,input_length); - *duration = positionToTime(stream,input_length); - } else { - assert(sizeof(bigtime_t)==sizeof(uint64_t)); - // if the input is not seekable, we return really large sizes - *frameCount = INT64_MAX; - *duration = INT64_MAX; - } - if (!packets || packets->size() < 1) { - return B_BAD_VALUE; - } - ogg_packet * packet = &((*packets)[0]); - if (isVorbisPacket(packet)) { - return GetVorbisCodecStreamInfo(packets,frameCount,duration,format); - } - if (isTobiasPacket(packet)) { - return GetTobiasCodecStreamInfo(packets,frameCount,duration,format); - } - if (isSpeexPacket(packet)) { - return GetSpeexCodecStreamInfo(packets,frameCount,duration,format); - } - return B_OK; -} - -// estimate: 1 frame = 256 bytes -int64 -oggReader::positionToFrame(ogg_stream_state * stream, off_t position) -{ - return position/256; -} - -off_t -oggReader::frameToPosition(ogg_stream_state * stream, int64 frame) -{ - return frame*256; -} - -bigtime_t -oggReader::positionToTime(ogg_stream_state * stream, off_t position) -{ - return position*1000/8; -} - -off_t -oggReader::timeToPosition(ogg_stream_state * stream, bigtime_t time) -{ - return time*8/1000; -} diff --git a/src/add-ons/media/plugins/ogg/OggReaderStreamInterface.h b/src/add-ons/media/plugins/ogg/OggReaderStreamInterface.h new file mode 100644 index 0000000000..75077fadf3 --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggReaderStreamInterface.h @@ -0,0 +1,7 @@ +#ifndef _OGG_READER_STREAM_INTERFACE_H_ +#define _OGG_READER_STREAM_INTERFACE_H_ + +#include + + +#endif // _OGG_READER_STREAM_INTERFACE_H_ diff --git a/src/add-ons/media/plugins/ogg/OggSpeexStream.cpp b/src/add-ons/media/plugins/ogg/OggSpeexStream.cpp new file mode 100644 index 0000000000..7a91a01e3a --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggSpeexStream.cpp @@ -0,0 +1,160 @@ +#include "OggSpeexStream.h" +#include + +#define TRACE_THIS 1 +#if TRACE_THIS + #define TRACE printf +#else + #define TRACE(a...) ((void)0) +#endif + +inline size_t +AudioBufferSize(media_raw_audio_format * raf, bigtime_t buffer_duration = 50000 /* 50 ms */) +{ + return (raf->format & 0xf) * (raf->channel_count) + * (size_t)((raf->frame_rate * buffer_duration) / 1000000.0); +} + +/* + * speex header from libspeex/speex_header.h + * also documented at http://www.speex.org/manual/node7.html#SECTION00073000000000000000 + */ + +typedef struct SpeexHeader { + char speex_string[8]; /**< Identifies a Speex bit-stream, always set to "Speex " */ + char speex_version[20]; /**< Speex version */ + int speex_version_id; /**< Version for Speex (for checking compatibility) */ + int header_size; /**< Total size of the header ( sizeof(SpeexHeader) ) */ + int rate; /**< Sampling rate used */ + int mode; /**< Mode used (0 for narrowband, 1 for wideband) */ + int mode_bitstream_version; /**< Version ID of the bit-stream */ + int nb_channels; /**< Number of channels encoded */ + int bitrate; /**< Bit-rate used */ + int frame_size; /**< Size of frames */ + int vbr; /**< 1 for a VBR encoding, 0 otherwise */ + int frames_per_packet; /**< Number of frames stored per Ogg packet */ + int extra_headers; /**< Number of additional headers after the comments */ + int reserved1; /**< Reserved for future use, must be zero */ + int reserved2; /**< Reserved for future use, must be zero */ +} SpeexHeader; + + +/* + * OggSpeexStream implementations + */ + +/* static */ bool +OggSpeexStream::IsValidHeader(const ogg_packet & packet) +{ + return findIdentifier(packet,"Speex ",0); +} + +OggSpeexStream::OggSpeexStream(long serialno) + : OggStream(serialno) +{ + +} + +OggSpeexStream::~OggSpeexStream() +{ + +} + +status_t +OggSpeexStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration, + media_format *format) +{ + TRACE("OggSpeexStream::GetStreamInfo\n"); + status_t result = B_OK; + ogg_packet packet; + + // get header packet + if (fHeaderPackets.size() < 1) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + packet = fHeaderPackets[0]; + if (!packet.b_o_s) { + return B_ERROR; // first packet was not beginning of stream + } + + // parse header packet, check size against struct minus optional fields + if (packet.bytes < 1 + (signed)sizeof(SpeexHeader) - 2*(signed)sizeof(int)) { + return B_ERROR; + } + void * data = &(packet.packet[0]); + SpeexHeader * header = (SpeexHeader *)data; + + // get the format for the description + media_format_description description; + description.family = B_MISC_FORMAT_FAMILY; + description.u.misc.file_format = 'OggS'; + description.u.misc.codec = 'Spee'; + BMediaFormats formats; + result = formats.InitCheck(); + if (result != B_OK) { + return result; + } + if (!formats.Lock()) { + return B_ERROR; + } + result = formats.GetFormatFor(description, format); + formats.Unlock(); + if (result != B_OK) { + return result; + } + + // fill out format from header packet + format->type = B_MEDIA_ENCODED_AUDIO; + format->user_data_type = B_CODEC_TYPE_INFO; + strncpy((char*)format->user_data, "Spee", 4); + if (header->bitrate > 0) { + format->u.encoded_audio.bit_rate = header->bitrate; + } else { + // TODO: manually compute it where possible + } + if (header->nb_channels == 1) { + format->u.encoded_audio.multi_info.channel_mask = B_CHANNEL_LEFT; + *frameCount *= 2; + *duration *= 2; + } else { + format->u.encoded_audio.multi_info.channel_mask = B_CHANNEL_LEFT | B_CHANNEL_RIGHT; + } + format->u.encoded_audio.frame_size = sizeof(ogg_packet); + format->u.encoded_audio.output.frame_rate = header->rate; + format->u.encoded_audio.output.channel_count = header->nb_channels; + format->u.encoded_audio.output.format = media_raw_audio_format::B_AUDIO_FLOAT; + format->u.encoded_audio.output.byte_order = B_MEDIA_HOST_ENDIAN; + // allocate buffer, round up to nearest speex output_length size + int buffer_size = AudioBufferSize(&format->u.encoded_audio.output); + int output_length = header->frame_size * header->nb_channels * + (format->u.encoded_audio.output.format & 0xf); + buffer_size = ((buffer_size - 1) / output_length + 1) * output_length; + format->u.encoded_audio.output.buffer_size = buffer_size; + + // get comment packet + if (fHeaderPackets.size() < 2) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + + // get extra headers + while ((signed)fHeaderPackets.size() < header->extra_headers) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + + format->SetMetaData((void*)&fHeaderPackets,sizeof(&fHeaderPackets)); + *duration = 80000000; + *frameCount = 60000; + return B_OK; +} diff --git a/src/add-ons/media/plugins/ogg/OggSpeexStream.h b/src/add-ons/media/plugins/ogg/OggSpeexStream.h new file mode 100644 index 0000000000..b63f91a5ec --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggSpeexStream.h @@ -0,0 +1,23 @@ +#ifndef _OGG_SPEEX_STREAM_H +#define _OGG_SPEEX_STREAM_H + +#include "OggStream.h" + +namespace BPrivate { namespace media { + +class OggSpeexStream : public OggStream { +public: + static bool IsValidHeader(const ogg_packet & packet); +public: + OggSpeexStream(long serialno); + virtual ~OggSpeexStream(); + + virtual status_t GetStreamInfo(int64 *frameCount, bigtime_t *duration, + media_format *format); +}; + +} } // namespace BPrivate::media + +using namespace BPrivate::media; + +#endif // _OGG_SPEEX_STREAM_H diff --git a/src/add-ons/media/plugins/ogg/OggStream.cpp b/src/add-ons/media/plugins/ogg/OggStream.cpp new file mode 100644 index 0000000000..4bde687df5 --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggStream.cpp @@ -0,0 +1,217 @@ +#include "OggStream.h" +#include "OggSpeexStream.h" +#include "OggTheoraStream.h" +#include "OggTobiasStream.h" +#include "OggVorbisStream.h" +#include + +#define TRACE_THIS 1 +#if TRACE_THIS + #define TRACE printf +#else + #define TRACE(a...) ((void)0) +#endif + +/* + * OggStream codec identification and instantiation + */ + +/* static */ OggStream * +OggStream::makeOggStream(OggReader::GetPageInterface * interface, + long serialno, const ogg_packet & packet) +{ + TRACE("OggStream::makeOggStream\n"); + OggStream * stream; + if (OggVorbisStream::IsValidHeader(packet)) { + stream = new OggVorbisStream(serialno); + } else if (OggTobiasStream::IsValidHeader(packet)) { + stream = new OggTobiasStream(serialno); + } else if (OggSpeexStream::IsValidHeader(packet)) { + stream = new OggSpeexStream(serialno); + } else if (OggTheoraStream::IsValidHeader(packet)) { + stream = new OggTheoraStream(serialno); + } else { + stream = new OggStream(serialno); + } + stream->fReaderInterface = interface; + return stream; +} + + +/* static */ bool +OggStream::findIdentifier(const ogg_packet & packet, const char * id, uint pos) +{ + uint length = strlen(id); + if ((unsigned)packet.bytes < pos+length) { + return false; + } + return !memcmp(&packet.packet[pos], id, length); +} + + +/* + * OggStream generic functions + */ + +OggStream::OggStream(long serialno) +{ + TRACE("OggStream::OggStream\n"); + this->fSerialno = serialno; + ogg_stream_init(&fStreamState,serialno); +} + + +OggStream::~OggStream() +{ + +} + + +long +OggStream::GetSerial() const +{ + return fSerialno; +} + + +status_t +OggStream::AddPage(off_t position, ogg_page * page) +{ + TRACE("OggStream::AddPage\n"); + if (position >= 0) { + fPagePositions.push_back(position); + } + ogg_stream_pagein(&fStreamState,page); + return B_OK; +} + + +status_t +OggStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration, + media_format *format) +{ + TRACE("OggStream::GetStreamInfo\n"); + debugger("OggStream::GetStreamInfo"); + status_t result = B_OK; + ogg_packet packet; + if (fHeaderPackets.size() < 1) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + packet = fHeaderPackets[0]; + if (!packet.b_o_s) { + return B_ERROR; // first packet was not beginning of stream + } + + // parse header packet + uint32 four_bytes = *(uint32*)(&packet); + + // get the format for the description + media_format_description description; + description.family = B_MISC_FORMAT_FAMILY; + description.u.misc.file_format = 'OggS'; + description.u.misc.codec = four_bytes; + BMediaFormats formats; + result = formats.InitCheck(); + if (result != B_OK) { + return result; + } + if (!formats.Lock()) { + return B_ERROR; + } + result = formats.GetFormatFor(description, format); + formats.Unlock(); + if (result != B_OK) { + return result; + } + + // fill out format from header packet + format->user_data_type = B_CODEC_TYPE_INFO; + strncpy((char*)format->user_data, "vorb", 4); + + format->SetMetaData((void*)&fHeaderPackets,sizeof(&fHeaderPackets)); + *duration = 80000000; + *frameCount = 60000; + return B_OK; +} + + +status_t +OggStream::Seek(uint32 seekTo, int64 *frame, bigtime_t *time) +{ + debugger("OggStream::Seek"); + return B_OK; +} + + +status_t +OggStream::GetNextChunk(void **chunkBuffer, int32 *chunkSize, + media_header *mediaHeader) +{ + static ogg_packet packet; + status_t result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + *chunkBuffer = &packet; + *chunkSize = sizeof(packet); + return B_OK; +} + + +// subclass pull input function +status_t +OggStream::GetPacket(ogg_packet * packet) +{ + while (ogg_stream_packetpeek(&fStreamState,NULL) != 1) { + fReaderInterface->GetNextPage(); + } + if (ogg_stream_packetout(&fStreamState,packet) != 1) { + return B_ERROR; + } + return B_OK; +} + + +// GetStreamInfo helpers +void +OggStream::SaveHeaderPacket(ogg_packet packet) +{ + uint8 * buffer = new uint8[packet.bytes]; + memcpy(buffer, packet.packet, packet.bytes); + packet.packet = buffer; + fHeaderPackets.push_back(packet); +} + + +// estimate 1 frame = 256 bytes +int64 +OggStream::PositionToFrame(off_t position) +{ + return position/256; +} + + +off_t +OggStream::FrameToPosition(int64 frame) +{ + return frame*256; +} + + +// estimate 1 byte = 125 microseconds +bigtime_t +OggStream::PositionToTime(off_t position) +{ + return position*125; +} + + +off_t +OggStream::TimeToPosition(bigtime_t time) +{ + return time/125; +} diff --git a/src/add-ons/media/plugins/ogg/OggStream.h b/src/add-ons/media/plugins/ogg/OggStream.h new file mode 100644 index 0000000000..489c98fc7a --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggStream.h @@ -0,0 +1,59 @@ +#ifndef _OGG_STREAM_H +#define _OGG_STREAM_H + +#include +#include +#include "ogg/ogg.h" +#include "OggReaderPlugin.h" +#include + +namespace BPrivate { namespace media { + +class OggStream { +public: + static OggStream * makeOggStream(OggReader::GetPageInterface * interface, + long serialno, const ogg_packet & packet); + +protected: + static bool findIdentifier(const ogg_packet & packet, + const char * id, uint pos); + OggStream(long serialno); +public: + virtual ~OggStream(); + long GetSerial() const; + + // reader push input function + status_t AddPage(off_t position, ogg_page * page); + + // reader pull output functions + virtual status_t GetStreamInfo(int64 *frameCount, bigtime_t *duration, + media_format *format); + virtual status_t Seek(uint32 seekTo, int64 *frame, bigtime_t *time); + virtual status_t GetNextChunk(void **chunkBuffer, int32 *chunkSize, + media_header *mediaHeader); + +protected: + // subclass pull input function + status_t GetPacket(ogg_packet * packet); + + // GetStreamInfo helpers + void SaveHeaderPacket(ogg_packet packet); + std::vector fHeaderPackets; + + // Seek helpers + virtual int64 PositionToFrame(off_t position); + virtual off_t FrameToPosition(int64 frame); + virtual bigtime_t PositionToTime(off_t position); + virtual off_t TimeToPosition(bigtime_t time); +private: + long fSerialno; + std::vector fPagePositions; + ogg_stream_state fStreamState; + OggReader::GetPageInterface * fReaderInterface; +}; + +} } // namespace BPrivate::media + +using namespace BPrivate::media; + +#endif // _OGG_STREAM_H diff --git a/src/add-ons/media/plugins/ogg/OggTheoraStream.cpp b/src/add-ons/media/plugins/ogg/OggTheoraStream.cpp new file mode 100644 index 0000000000..9a1bbae931 --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggTheoraStream.cpp @@ -0,0 +1,215 @@ +#include "OggTheoraStream.h" +#include +#include + +#define TRACE_THIS 1 +#if TRACE_THIS + #define TRACE printf +#else + #define TRACE(a...) ((void)0) +#endif + +inline size_t +AudioBufferSize(media_raw_audio_format * raf, bigtime_t buffer_duration = 50000 /* 50 ms */) +{ + return (raf->format & 0xf) * (raf->channel_count) + * (size_t)((raf->frame_rate * buffer_duration) / 1000000.0); +} + +/* + * theora header parsing code from theora/theara.h + */ + +typedef enum { + OC_CS_UNSPECIFIED, + OC_CS_ITU_REC_470M, + OC_CS_ITU_REC_470BG, +} theora_colorspace; + +typedef struct { + ogg_uint32_t width; + ogg_uint32_t height; + ogg_uint32_t frame_width; + ogg_uint32_t frame_height; + ogg_uint32_t offset_x; + ogg_uint32_t offset_y; + ogg_uint32_t fps_numerator; + ogg_uint32_t fps_denominator; + ogg_uint32_t aspect_numerator; + ogg_uint32_t aspect_denominator; + theora_colorspace colorspace; + int target_bitrate; + int quality; + int quick_p; /* quick encode/decode */ + + /* decode only */ + unsigned char version_major; + unsigned char version_minor; + unsigned char version_subminor; + + void *codec_setup; + + /* encode only */ + int dropframes_p; + int keyframe_auto_p; + ogg_uint32_t keyframe_frequency; + ogg_uint32_t keyframe_frequency_force; /* also used for decode init to + get granpos shift correct */ + ogg_uint32_t keyframe_data_target_bitrate; + ogg_int32_t keyframe_auto_threshold; + ogg_uint32_t keyframe_mindistance; + ogg_int32_t noise_sensitivity; + ogg_int32_t sharpness; + +} theora_info; + +// based on theora/lib/toplevel.c _theora_unpack_info + +#define theora_read(x,y,z) ( *z = oggpack_read(x,y) ) + +#define OC_BADHEADER -1 + +static int _theora_unpack_info(theora_info *ci, oggpack_buffer *opb){ + long ret; + + theora_read(opb,8,&ret); + ci->version_major=(unsigned char)ret; + theora_read(opb,8,&ret); + ci->version_minor=(unsigned char)ret; + theora_read(opb,8,&ret); + ci->version_subminor=(unsigned char)ret; + +// if(ci->version_major!=VERSION_MAJOR)return(OC_VERSION); +// if(ci->version_minor>VERSION_MINOR)return(OC_VERSION); + + theora_read(opb,16,&ret); + ci->width=ret<<4; + theora_read(opb,16,&ret); + ci->height=ret<<4; + theora_read(opb,24,&ret); + ci->frame_width=ret; + theora_read(opb,24,&ret); + ci->frame_height=ret; + theora_read(opb,8,&ret); + ci->offset_x=ret; + theora_read(opb,8,&ret); + ci->offset_y=ret; + + theora_read(opb,32,&ret); + ci->fps_numerator=ret; + theora_read(opb,32,&ret); + ci->fps_denominator=ret; + theora_read(opb,24,&ret); + ci->aspect_numerator=ret; + theora_read(opb,24,&ret); + ci->aspect_denominator=ret; + + theora_read(opb,8,&ret); + ci->colorspace=(theora_colorspace)ret; + theora_read(opb,24,&ret); + ci->target_bitrate=ret; + theora_read(opb,6,&ret); + ci->quality=ret=ret; + + theora_read(opb,5,&ret); + ci->keyframe_frequency_force=1<type = B_MEDIA_ENCODED_VIDEO; + format->user_data_type = B_CODEC_TYPE_INFO; + strncpy((char*)format->user_data, "theo", 4); + format->u.encoded_video.frame_size = info.frame_width * info.frame_height ; + format->u.encoded_video.output.display.line_width = info.frame_width; + format->u.encoded_video.output.display.line_count = info.frame_height; + // TODO: wring more info out of the headers + + format->SetMetaData((void*)&fHeaderPackets,sizeof(&fHeaderPackets)); + *duration = 80000000; + *frameCount = 60000; + return B_OK; +} diff --git a/src/add-ons/media/plugins/ogg/OggTheoraStream.h b/src/add-ons/media/plugins/ogg/OggTheoraStream.h new file mode 100644 index 0000000000..338fec39ef --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggTheoraStream.h @@ -0,0 +1,23 @@ +#ifndef _OGG_THEORA_STREAM_H +#define _OGG_THEORA_STREAM_H + +#include "OggStream.h" + +namespace BPrivate { namespace media { + +class OggTheoraStream : public OggStream { +public: + static bool IsValidHeader(const ogg_packet & packet); +public: + OggTheoraStream(long serialno); + virtual ~OggTheoraStream(); + + virtual status_t GetStreamInfo(int64 *frameCount, bigtime_t *duration, + media_format *format); +}; + +} } // namespace BPrivate::media + +using namespace BPrivate::media; + +#endif // _OGG_THEORA_STREAM_H diff --git a/src/add-ons/media/plugins/ogg/OggTobiasStream.cpp b/src/add-ons/media/plugins/ogg/OggTobiasStream.cpp new file mode 100644 index 0000000000..b882234f15 --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggTobiasStream.cpp @@ -0,0 +1,148 @@ +#include "OggTobiasStream.h" +#include + +#define TRACE_THIS 1 +#if TRACE_THIS + #define TRACE printf +#else + #define TRACE(a...) ((void)0) +#endif + +inline size_t +AudioBufferSize(media_raw_audio_format * raf, bigtime_t buffer_duration = 50000 /* 50 ms */) +{ + return (raf->format & 0xf) * (raf->channel_count) + * (size_t)((raf->frame_rate * buffer_duration) / 1000000.0); +} + +/* + * tobias header structs from http://tobias.everwicked.com/packfmt.htm + */ + +typedef struct tobias_stream_header_video +{ + ogg_int32_t width; + ogg_int32_t height; +} tobias_stream_header_video; + +typedef struct tobias_stream_header_audio +{ + ogg_int16_t channels; + ogg_int16_t blockalign; + ogg_int32_t avgbytespersec; +} tobias_stream_header_audio; + +typedef struct tobias_stream_header +{ + char streamtype[8]; + char subtype[4]; + + ogg_int32_t size; // size of the structure + + ogg_int64_t time_unit; // in reference time + ogg_int64_t samples_per_unit; + ogg_int32_t default_len; // in media time + + ogg_int32_t buffersize; + ogg_int16_t bits_per_sample; + + union { + // Video specific + tobias_stream_header_video video; + // Audio specific + tobias_stream_header_audio audio; + }; +} tobias_stream_header; + +/* + * OggTobiasStream implementations + */ + +/* static */ bool +OggTobiasStream::IsValidHeader(const ogg_packet & packet) +{ + return findIdentifier(packet,"video",1); +} + +OggTobiasStream::OggTobiasStream(long serialno) + : OggStream(serialno) +{ + +} + +OggTobiasStream::~OggTobiasStream() +{ + +} + +status_t +OggTobiasStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration, + media_format *format) +{ + TRACE("OggTobiasStream::GetStreamInfo\n"); + status_t result = B_OK; + ogg_packet packet; + + // get header packet + if (fHeaderPackets.size() < 1) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + packet = fHeaderPackets[0]; + if (!packet.b_o_s) { + return B_ERROR; // first packet was not beginning of stream + } + + // parse header packet + if (packet.bytes < 1+(signed)sizeof(tobias_stream_header)) { + return B_ERROR; + } + void * data = &(packet.packet[1]); + tobias_stream_header * header = (tobias_stream_header *)data; + + // get the format for the description + media_format_description description; + description.family = B_AVI_FORMAT_FAMILY; + description.u.avi.codec = header->subtype[0] << 24 | header->subtype[1] << 16 + | header->subtype[2] << 8 | header->subtype[3]; + BMediaFormats formats; + result = formats.InitCheck(); + if (result != B_OK) { + return result; + } + if (!formats.Lock()) { + return B_ERROR; + } + result = formats.GetFormatFor(description, format); + formats.Unlock(); + if (result != B_OK) { + return result; + } + + // fill out format from header packet + format->type = B_MEDIA_ENCODED_VIDEO; + format->user_data_type = B_CODEC_TYPE_INFO; + strncpy((char*)format->user_data, header->subtype, 4); + format->u.encoded_video.frame_size + = header->video.width * header->video.height; + format->u.encoded_video.output.display.line_width = header->video.width; + format->u.encoded_video.output.display.line_count = header->video.height; + // TODO: wring more info out of the headers + + // get comment packet + if (fHeaderPackets.size() < 2) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + + format->SetMetaData((void*)&fHeaderPackets,sizeof(&fHeaderPackets)); + *duration = 80000000; + *frameCount = 60000; + return B_OK; +} diff --git a/src/add-ons/media/plugins/ogg/OggTobiasStream.h b/src/add-ons/media/plugins/ogg/OggTobiasStream.h new file mode 100644 index 0000000000..8e150bd18c --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggTobiasStream.h @@ -0,0 +1,23 @@ +#ifndef _OGG_TOBIAS_STREAM_H +#define _OGG_TOBIAS_STREAM_H + +#include "OggStream.h" + +namespace BPrivate { namespace media { + +class OggTobiasStream : public OggStream { +public: + static bool IsValidHeader(const ogg_packet & packet); +public: + OggTobiasStream(long serialno); + virtual ~OggTobiasStream(); + + virtual status_t GetStreamInfo(int64 *frameCount, bigtime_t *duration, + media_format *format); +}; + +} } // namespace BPrivate::media + +using namespace BPrivate::media; + +#endif // _OGG_TOBIAS_STREAM_H diff --git a/src/add-ons/media/plugins/ogg/OggVorbisStream.cpp b/src/add-ons/media/plugins/ogg/OggVorbisStream.cpp new file mode 100644 index 0000000000..8c0c6969c2 --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggVorbisStream.cpp @@ -0,0 +1,199 @@ +#include "OggVorbisStream.h" +#include + +#define TRACE_THIS 1 +#if TRACE_THIS + #define TRACE printf +#else + #define TRACE(a...) ((void)0) +#endif + +inline size_t +AudioBufferSize(media_raw_audio_format * raf, bigtime_t buffer_duration = 50000 /* 50 ms */) +{ + return (raf->format & 0xf) * (raf->channel_count) + * (size_t)((raf->frame_rate * buffer_duration) / 1000000.0); +} + +/* + * vorbis header parsing code from libvorbis/info.c + */ + +typedef struct vorbis_info{ + int version; + int channels; + long rate; + + /* The below bitrate declarations are *hints*. + Combinations of the three values carry the following implications: + + all three set to the same value: + implies a fixed rate bitstream + only nominal set: + implies a VBR stream that averages the nominal bitrate. No hard + upper/lower limit + upper and or lower set: + implies a VBR bitstream that obeys the bitrate limits. nominal + may also be set to give a nominal rate. + none set: + the coder does not care to speculate. + */ + + long bitrate_upper; + long bitrate_nominal; + long bitrate_lower; + long bitrate_window; + + void *codec_setup; +} vorbis_info; + +// based on libvorbis/info.c _vorbis_unpack_info +static int _vorbis_unpack_info(vorbis_info *vi,oggpack_buffer *opb){ + vi->version = oggpack_read(opb, 32); + if (vi->version != 0) { + return -1; + } + vi->channels = oggpack_read(opb, 8); + vi->rate = oggpack_read(opb, 32); + vi->bitrate_upper = oggpack_read(opb, 32); + vi->bitrate_nominal = oggpack_read(opb, 32); + vi->bitrate_lower = oggpack_read(opb, 32); + oggpack_read(opb, 4); // discard blocksizes 0 + oggpack_read(opb, 4); // discard blocksizes 1 + if (vi->rate < 1) { + return -1; + } + if (vi->channels < 1) { + return -1; + } + if (oggpack_read(opb, 1) != 1) { + return -1; + } /* EOP check */ + return 0; +} + +/* + * OggVorbisStream implementations + */ + +/* static */ bool +OggVorbisStream::IsValidHeader(const ogg_packet & packet) +{ + return findIdentifier(packet,"vorbis",1); +} + +OggVorbisStream::OggVorbisStream(long serialno) + : OggStream(serialno) +{ + +} + +OggVorbisStream::~OggVorbisStream() +{ + +} + +status_t +OggVorbisStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration, + media_format *format) +{ + TRACE("OggVorbisStream::GetStreamInfo\n"); + status_t result = B_OK; + ogg_packet packet; + + // get header packet + if (fHeaderPackets.size() < 1) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + packet = fHeaderPackets[0]; + if (!packet.b_o_s) { + return B_ERROR; // first packet was not beginning of stream + } + + // parse header packet + // based on libvorbis/info.c vorbis_synthesis_headerin(...) + oggpack_buffer opb; + oggpack_readinit(&opb, packet.packet, packet.bytes); + int packtype = oggpack_read(&opb, 8); + if (packtype != 0x01) { + return B_ERROR; // first packet was not an info packet + } + // discard vorbis string + for (uint i = 0 ; i < sizeof("vorbis") - 1 ; i++) { + oggpack_read(&opb, 8); + } + vorbis_info info; + if (_vorbis_unpack_info(&info, &opb) != 0) { + return B_ERROR; // couldn't unpack info + } + + // get the format for the description + media_format_description description; + description.family = B_MISC_FORMAT_FAMILY; + description.u.misc.file_format = 'OggS'; + description.u.misc.codec = 'vorb'; + BMediaFormats formats; + result = formats.InitCheck(); + if (result != B_OK) { + return result; + } + if (!formats.Lock()) { + return B_ERROR; + } + result = formats.GetFormatFor(description, format); + formats.Unlock(); + if (result != B_OK) { + return result; + } + + // fill out format from header packet + format->type = B_MEDIA_ENCODED_AUDIO; + format->user_data_type = B_CODEC_TYPE_INFO; + strncpy((char*)format->user_data, "vorb", 4); + if (info.bitrate_nominal > 0) { + format->u.encoded_audio.bit_rate = info.bitrate_nominal; + } else if (info.bitrate_upper > 0) { + format->u.encoded_audio.bit_rate = info.bitrate_upper; + } else if (info.bitrate_lower > 0) { + format->u.encoded_audio.bit_rate = info.bitrate_lower; + } + if (info.channels == 1) { + format->u.encoded_audio.multi_info.channel_mask = B_CHANNEL_LEFT; + } else { + format->u.encoded_audio.multi_info.channel_mask = B_CHANNEL_LEFT | B_CHANNEL_RIGHT; + } + format->u.encoded_audio.frame_size = sizeof(ogg_packet); + format->u.encoded_audio.output.frame_rate = (float)info.rate; + format->u.encoded_audio.output.channel_count = info.channels; + format->u.encoded_audio.output.format = media_raw_audio_format::B_AUDIO_FLOAT; + format->u.encoded_audio.output.byte_order = B_MEDIA_HOST_ENDIAN; + format->u.encoded_audio.output.buffer_size + = AudioBufferSize(&format->u.encoded_audio.output); + + // get comment packet + if (fHeaderPackets.size() < 2) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + + // get codebook packet + if (fHeaderPackets.size() < 3) { + result = GetPacket(&packet); + if (result != B_OK) { + return result; + } + SaveHeaderPacket(packet); + } + + format->SetMetaData((void*)&fHeaderPackets,sizeof(&fHeaderPackets)); + *duration = 80000000; + *frameCount = 60000; + return B_OK; +} diff --git a/src/add-ons/media/plugins/ogg/OggVorbisStream.h b/src/add-ons/media/plugins/ogg/OggVorbisStream.h new file mode 100644 index 0000000000..7d13c7712a --- /dev/null +++ b/src/add-ons/media/plugins/ogg/OggVorbisStream.h @@ -0,0 +1,23 @@ +#ifndef _OGG_VORBIS_STREAM_H +#define _OGG_VORBIS_STREAM_H + +#include "OggStream.h" + +namespace BPrivate { namespace media { + +class OggVorbisStream : public OggStream { +public: + static bool IsValidHeader(const ogg_packet & packet); +public: + OggVorbisStream(long serialno); + virtual ~OggVorbisStream(); + + virtual status_t GetStreamInfo(int64 *frameCount, bigtime_t *duration, + media_format *format); +}; + +} } // namespace BPrivate::media + +using namespace BPrivate::media; + +#endif // _OGG_VORBIS_STREAM_H