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:
@@ -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
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user