Ogg Reader 2.0d0

git-svn-id: file:///srv/svn/repos/haiku/trunk/current@6257 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
shatty
2004-01-24 21:50:32 +00:00
parent a3d7ce0637
commit f14460063c
15 changed files with 1251 additions and 642 deletions
+5 -1
View File
@@ -6,7 +6,11 @@ SubDirHdrs [ FDirName $(SUBDIR) libogg ] ;
Addon ogg : media plugins : Addon ogg : media plugins :
OggReaderPlugin.cpp OggReaderPlugin.cpp
OggReaderPluginCodecs.cpp OggStream.cpp
OggSpeexStream.cpp
OggTheoraStream.cpp
OggTobiasStream.cpp
OggVorbisStream.cpp
: false : libogg.a : false : libogg.a
; ;
+129 -215
View File
@@ -7,6 +7,7 @@
#include <InterfaceDefs.h> #include <InterfaceDefs.h>
#include <MediaFormats.h> #include <MediaFormats.h>
#include "OggReaderPlugin.h" #include "OggReaderPlugin.h"
#include "OggStream.h"
#define TRACE_THIS 1 #define TRACE_THIS 1
#if TRACE_THIS #if TRACE_THIS
@@ -15,20 +16,19 @@
#define TRACE(a...) ((void)0) #define TRACE(a...) ((void)0)
#endif #endif
oggReader::oggReader() OggReader::OggReader()
{ {
TRACE("oggReader::oggReader\n"); TRACE("OggReader::OggReader\n");
ogg_sync_init(&fSync); ogg_sync_init(&fSync);
fSeekable = 0;
} }
oggReader::~oggReader() OggReader::~OggReader()
{ {
TRACE("oggReader::~oggReader\n"); TRACE("OggReader::~OggReader\n");
ogg_stream_map::iterator i = fStreams.begin(); serialno_OggStream_map::iterator i = fStreams.begin();
while (i != fStreams.end()) { while (i != fStreams.end()) {
ogg_stream_map::iterator j = i; serialno_OggStream_map::iterator j = i;
i++; i++;
delete j->second; delete j->second;
} }
@@ -37,102 +37,151 @@ oggReader::~oggReader()
const char * const char *
oggReader::Copyright() OggReader::Copyright()
{ {
return "ogg reader, " B_UTF8_COPYRIGHT " by Andrew Bachmann"; return "ogg reader, " B_UTF8_COPYRIGHT " by Andrew Bachmann";
} }
status_t 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 int result = ogg_sync_pageout(&fSync,page); // first read leftovers
while (result == 0) { while (result == 0) {
char * buffer = ogg_sync_buffer(&fSync,read_size); char * buffer = ogg_sync_buffer(&fSync,read_size);
ssize_t bytes = Source()->Read(buffer,read_size); ssize_t bytes = Source()->Read(buffer,read_size);
if (bytes == 0) { if (bytes == 0) {
TRACE("oggReader::GetPage: Read: no data\n"); TRACE("OggReader::GetPage: Read: no data\n");
return B_LAST_BUFFER_ERROR; return B_LAST_BUFFER_ERROR;
} }
if (bytes < 0) { if (bytes < 0) {
TRACE("oggReader::GetPage: Read: error\n"); TRACE("OggReader::GetPage: Read: error\n");
return bytes; return bytes;
} }
if (ogg_sync_wrote(&fSync,bytes) != 0) { 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; return B_ERROR;
} }
result = ogg_sync_pageout(&fSync,page); result = ogg_sync_pageout(&fSync,page);
if (short_page && (result != 1)) { 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; return B_ERROR;
} }
} }
if (result == -1) { 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; return B_ERROR;
} }
#ifdef STRICT_OGG #ifdef STRICT_OGG
if (ogg_page_version(page) != 0) { 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; return B_ERROR;
} }
#endif #endif
next_position += page->header_len + page->body_len;
long serialno = ogg_page_serialno(page); long serialno = ogg_page_serialno(page);
if (fStreams.find(serialno) == fStreams.end()) { if (fStreams.find(serialno) == fStreams.end()) {
// this is an unknown serialno // this is an unknown serialno
if (ogg_page_bos(page) == 0) { 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;
#endif
}
ogg_stream_state * stream = new ogg_stream_state;
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 #ifdef STRICT_OGG
return B_ERROR; return B_ERROR;
#else #else
if (ogg_stream_reset(fStreams[serialno]) != 0) { // silently discard packets with unknown serialno
TRACE("oggReader::GetPage: ogg_stream_reset failed?: error\n"); goto retry;
return B_ERROR;
}
#endif #endif
} }
if (ogg_stream_pagein(fStreams[serialno],page) != 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;
}
if (ogg_stream_pagein(&stream, page) != 0) {
TRACE("oggReader::GetPage: ogg_stream_pagein: failed: error\n"); TRACE("oggReader::GetPage: ogg_stream_pagein: failed: error\n");
return B_ERROR; return B_ERROR;
} }
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; return B_OK;
} }
};
fStreams[serialno] = OggStream::makeOggStream(new Interface(this, serialno), serialno, packet);
fCookies.push_back(fStreams[serialno]);
}
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<BPositionIO *>(data);
if (seekable == 0) {
return 0;
}
// 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 status_t
oggReader::Sniff(int32 *streamCount) OggReader::Sniff(int32 *streamCount)
{ {
TRACE("oggReader::Sniff\n"); TRACE("OggReader::Sniff\n");
#ifdef STRICT_OGG #ifdef STRICT_OGG
bool short_page = true; bool short_page = true;
#else #else
bool short_page = false; bool short_page = false;
#endif #endif
fSeekable = dynamic_cast<BPositionIO*>(Source()); fSeekable = get_seekable(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;
}
}
}
ogg_page page; ogg_page page;
if (GetPage(&page,4096,short_page) != B_OK) { if (GetPage(&page,4096,short_page) != B_OK) {
@@ -141,79 +190,34 @@ oggReader::Sniff(int32 *streamCount)
// page sanity checks // page sanity checks
if (ogg_page_version(&page) != 0) { 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; return B_ERROR;
} }
#ifdef STRICT_OGG #ifdef STRICT_OGG
if (ogg_page_bos(&page) == 0) { 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; return B_ERROR;
} }
if (ogg_page_continued(&page) != 0) { 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; return B_ERROR;
} }
#endif STRICT_OGG #endif STRICT_OGG
while (ogg_page_bos(&page) > 0) { 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) { if (GetPage(&page) != B_OK) {
return B_ERROR; return B_ERROR;
} }
} }
packet_count_map::iterator iter = fHeaderPacketsLeft.begin(); *streamCount = fCookies.size();
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;
}
}
}
return B_OK; return B_OK;
} }
void 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 mff->capabilities = media_file_format::B_READABLE
| media_file_format::B_IMPERFECTLY_SEEKABLE | media_file_format::B_IMPERFECTLY_SEEKABLE
| media_file_format::B_KNOWS_ENCODED_VIDEO | media_file_format::B_KNOWS_ENCODED_VIDEO
@@ -229,161 +233,71 @@ oggReader::GetFileFormatInfo(media_file_format *mff)
status_t status_t
oggReader::AllocateCookie(int32 streamNumber, void **cookie) OggReader::AllocateCookie(int32 streamNumber, void **cookie)
{ {
TRACE("oggReader::AllocateCookie\n"); TRACE("OggReader::AllocateCookie\n");
ogg_stream_map::iterator i = fStreams.begin(); if (streamNumber < 0 || streamNumber > (signed)fCookies.size()) {
while (streamNumber > 0) { TRACE("OggReader::AllocateCookie: invalid streamNumber: bail\n");
if (i == fStreams.end()) {
TRACE("oggReader::AllocateCookie: invalid streamNumber: bail\n");
return B_ERROR; return B_ERROR;
} }
i++; OggStream * stream = fCookies[streamNumber];
streamNumber--;
}
// store the cookie // store the cookie
*cookie = (void*)(i->second); *cookie = stream;
return B_OK; return B_OK;
} }
status_t status_t
oggReader::FreeCookie(void *cookie) OggReader::FreeCookie(void *cookie)
{ {
TRACE("oggReader::FreeCookie\n"); TRACE("OggReader::FreeCookie\n");
return B_OK; return B_OK;
} }
status_t 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) media_format *format, void **infoBuffer, int32 *infoSize)
{ {
TRACE("oggReader::GetStreamInfo\n"); TRACE("OggReader::GetStreamInfo\n");
ogg_stream_state * stream = static_cast<ogg_stream_state *>(cookie);
memset(format, 0, sizeof(*format));
std::vector<ogg_packet> * ogg_packets = &fHeaderPackets[stream->serialno];
format->SetMetaData((void*)ogg_packets,sizeof(ogg_packets));
GetCodecStreamInfo(stream,frameCount,duration,format);
*infoBuffer = 0; *infoBuffer = 0;
*infoSize = 0; *infoSize = 0;
return B_OK; OggStream * stream = static_cast<OggStream*>(cookie);
return stream->GetStreamInfo(frameCount,duration,format);
} }
status_t status_t
oggReader::Seek(void *cookie, OggReader::Seek(void *cookie,
uint32 seekTo, uint32 seekTo,
int64 *frame, bigtime_t *time) int64 *frame, bigtime_t *time)
{ {
TRACE("oggReader::Seek\n"); TRACE("OggReader::Seek\n");
if (!fSeekable) { OggStream * stream = static_cast<OggStream*>(cookie);
TRACE("oggReader::Seek: not a PositionIO: not seekable\n"); return stream->Seek(seekTo,frame,time);
return B_ERROR;
}
ogg_stream_state * stream = static_cast<ogg_stream_state *>(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;
} }
status_t status_t
oggReader::GetNextChunk(void *cookie, OggReader::GetNextChunk(void *cookie,
void **chunkBuffer, int32 *chunkSize, void **chunkBuffer, int32 *chunkSize,
media_header *mediaHeader) media_header *mediaHeader)
{ {
// TRACE("oggReader::GetNextChunk\n"); TRACE("OggReader::GetNextChunk\n");
if (fSeekable && (fSeekable->Seek(0,SEEK_CUR) < fPostSniffPosition)) { OggStream * stream = static_cast<OggStream*>(cookie);
fSeekable->Seek(fPostSniffPosition,SEEK_SET); return stream->GetNextChunk(chunkBuffer,chunkSize,mediaHeader);
}
ogg_stream_state * stream = static_cast<ogg_stream_state *>(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;
} }
Reader * Reader *
oggReaderPlugin::NewReader() OggReaderPlugin::NewReader()
{ {
return new oggReader; return new OggReader;
} }
MediaPlugin * MediaPlugin *
instantiate_plugin() instantiate_plugin()
{ {
return new oggReaderPlugin; return new OggReaderPlugin;
} }
+19 -28
View File
@@ -8,16 +8,16 @@
namespace BPrivate { namespace media { namespace BPrivate { namespace media {
typedef std::map<int,ogg_stream_state*> ogg_stream_map; class OggStream;
typedef std::map<int,ogg_packet> ogg_packet_map;
typedef std::map<int,std::vector<ogg_packet> > ogg_packets_map;
typedef std::map<int,int> packet_count_map;
class oggReader : public Reader typedef std::map<long,OggStream*> serialno_OggStream_map;
typedef std::vector<OggStream*> OggStream_vector;
class OggReader : public Reader
{ {
public: public:
oggReader(); OggReader();
~oggReader(); ~OggReader();
const char *Copyright(); const char *Copyright();
@@ -28,42 +28,33 @@ public:
status_t AllocateCookie(int32 streamNumber, void **cookie); status_t AllocateCookie(int32 streamNumber, void **cookie);
status_t FreeCookie(void *cookie); status_t FreeCookie(void *cookie);
// delegated to OggStream
status_t GetStreamInfo(void *cookie, int64 *frameCount, bigtime_t *duration, status_t GetStreamInfo(void *cookie, int64 *frameCount, bigtime_t *duration,
media_format *format, void **infoBuffer, int32 *infoSize); media_format *format, void **infoBuffer, int32 *infoSize);
status_t Seek(void *cookie, status_t Seek(void *cookie,
uint32 seekTo, uint32 seekTo,
int64 *frame, bigtime_t *time); int64 *frame, bigtime_t *time);
status_t GetNextChunk(void *cookie, status_t GetNextChunk(void *cookie,
void **chunkBuffer, int32 *chunkSize, void **chunkBuffer, int32 *chunkSize,
media_header *mediaHeader); media_header *mediaHeader);
private: protected:
status_t GetPage(ogg_page * page, int read_size = 4*B_PAGE_SIZE, status_t GetPage(ogg_page * page, int read_size = 4*B_PAGE_SIZE,
bool short_page = false); 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_sync_state fSync;
ogg_packets_map fHeaderPackets; serialno_OggStream_map fStreams;
packet_count_map fHeaderPacketsLeft; OggStream_vector fCookies;
ogg_stream_map fStreams; BPositionIO * fSeekable;
ogg_packet_map fPackets;
private:
class GetPageInterface {
public:
virtual status_t GetNextPage() = 0;
};
}; };
class OggReaderPlugin : public ReaderPlugin
class oggReaderPlugin : public ReaderPlugin
{ {
public: public:
Reader *NewReader(); Reader *NewReader();
@@ -1,397 +0,0 @@
#include <unistd.h>
#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<ogg_packet> * 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<ogg_packet> * 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<ogg_packet> * 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<ogg_packet> * 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;
}
@@ -0,0 +1,7 @@
#ifndef _OGG_READER_STREAM_INTERFACE_H_
#define _OGG_READER_STREAM_INTERFACE_H_
#include <ogg/ogg.h>
#endif // _OGG_READER_STREAM_INTERFACE_H_
@@ -0,0 +1,160 @@
#include "OggSpeexStream.h"
#include <stdio.h>
#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;
}
@@ -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
+217
View File
@@ -0,0 +1,217 @@
#include "OggStream.h"
#include "OggSpeexStream.h"
#include "OggTheoraStream.h"
#include "OggTobiasStream.h"
#include "OggVorbisStream.h"
#include <stdio.h>
#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;
}
+59
View File
@@ -0,0 +1,59 @@
#ifndef _OGG_STREAM_H
#define _OGG_STREAM_H
#include <SupportDefs.h>
#include <MediaDefs.h>
#include "ogg/ogg.h"
#include "OggReaderPlugin.h"
#include <vector>
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<ogg_packet> 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<off_t> fPagePositions;
ogg_stream_state fStreamState;
OggReader::GetPageInterface * fReaderInterface;
};
} } // namespace BPrivate::media
using namespace BPrivate::media;
#endif // _OGG_STREAM_H
@@ -0,0 +1,215 @@
#include "OggTheoraStream.h"
#include <ogg/ogg.h>
#include <stdio.h>
#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<<ret;
/* spare configuration bits */
if ( theora_read(opb,5,&ret) == -1 )
return (OC_BADHEADER);
return(0);
}
/*
* OggTheoraStream implementations
*/
/* static */ bool
OggTheoraStream::IsValidHeader(const ogg_packet & packet)
{
return findIdentifier(packet,"theora",1);
}
OggTheoraStream::OggTheoraStream(long serialno)
: OggStream(serialno)
{
}
OggTheoraStream::~OggTheoraStream()
{
}
status_t
OggTheoraStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration,
media_format *format)
{
TRACE("OggTheoraStream::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 typeflag = oggpack_read(&opb, 8);
if (typeflag != 0x80) {
return B_ERROR; // first packet was not an info packet
}
// discard theora string
for (uint i = 0 ; i < sizeof("theora") - 1 ; i++) {
oggpack_read(&opb, 8);
}
theora_info info;
if (_theora_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 = 'theo';
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, "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;
}
@@ -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
@@ -0,0 +1,148 @@
#include "OggTobiasStream.h"
#include <stdio.h>
#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;
}
@@ -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
@@ -0,0 +1,199 @@
#include "OggVorbisStream.h"
#include <stdio.h>
#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;
}
@@ -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