stream-only version of shared seekable/stream implementation
git-svn-id: file:///srv/svn/repos/haiku/trunk/current@6749 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -0,0 +1,238 @@
|
|||||||
|
#include "OggCodecs.h"
|
||||||
|
#include <MediaFormats.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#define TRACE_THIS 0
|
||||||
|
#if TRACE_THIS
|
||||||
|
#define TRACE printf
|
||||||
|
#else
|
||||||
|
#define TRACE(a...) ((void)0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Useful ogg_packet functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool
|
||||||
|
BPrivate::media::findIdentifier(const ogg_packet & packet, const char * id, uint pos)
|
||||||
|
{
|
||||||
|
TRACE("findIdentifier\n");
|
||||||
|
uint length = strlen(id);
|
||||||
|
if ((unsigned)packet.bytes < pos+length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !memcmp(&packet.packet[pos], id, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OggCodec implementations
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
OggCodec::OggCodec()
|
||||||
|
: fCurrentFrame(-1),
|
||||||
|
fCurrentTime(0),
|
||||||
|
fOldFrame(-1),
|
||||||
|
fOldGranulePos(-1),
|
||||||
|
fInitCheck(B_NO_INIT),
|
||||||
|
fPacketData(NULL)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */
|
||||||
|
OggCodec::~OggCodec()
|
||||||
|
{
|
||||||
|
delete fPacketData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ bool
|
||||||
|
OggCodec::IsHeaderPacket(const ogg_packet & packet, uint packetno) const
|
||||||
|
{
|
||||||
|
return (packetno == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ status_t
|
||||||
|
OggCodec::HandlePacket(const ogg_packet & packet)
|
||||||
|
{
|
||||||
|
TRACE("OggCodec::HandlePacket\n");
|
||||||
|
if (!IsHeaderPacket(packet, (fInitCheck == B_OK ? 1 : 0))) {
|
||||||
|
return B_BAD_VALUE;
|
||||||
|
}
|
||||||
|
if (!packet.b_o_s) {
|
||||||
|
TRACE("OggCodec::HandlePacket failed : not beginning of stream\n");
|
||||||
|
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;
|
||||||
|
if (formats.InitCheck() == B_OK) {
|
||||||
|
formats.GetFormatFor(description, &fMediaFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill out format from header packet
|
||||||
|
fMediaFormat.user_data_type = B_CODEC_TYPE_INFO;
|
||||||
|
strncpy((char*)fMediaFormat.user_data, (char*)(&packet), 4);
|
||||||
|
|
||||||
|
fPacketData = new unsigned char[packet.bytes];
|
||||||
|
if (fPacketData == NULL) {
|
||||||
|
return B_NO_MEMORY;
|
||||||
|
}
|
||||||
|
memcpy(fPacketData, packet.packet, packet.bytes);
|
||||||
|
ogg_packet copy = packet;
|
||||||
|
copy.packet = fPacketData;
|
||||||
|
|
||||||
|
fMediaFormat.SetMetaData(©,sizeof(copy));
|
||||||
|
fInitCheck = B_OK;
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ status_t
|
||||||
|
OggCodec::GetFormat(media_format * format) const
|
||||||
|
{
|
||||||
|
if (format == 0) {
|
||||||
|
return B_BAD_VALUE;
|
||||||
|
}
|
||||||
|
if (fInitCheck != B_OK) {
|
||||||
|
return fInitCheck;
|
||||||
|
}
|
||||||
|
*format = fMediaFormat;
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ int64
|
||||||
|
OggCodec::GranulesToFrames(int64 granules) const
|
||||||
|
{
|
||||||
|
return granules;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ int64
|
||||||
|
OggCodec::FramesToGranules(int64 frames) const
|
||||||
|
{
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ status_t
|
||||||
|
OggCodec::GetChunk(ogg_stream_state * stream, bool streaming,
|
||||||
|
void **chunkBuffer, int32 *chunkSize,
|
||||||
|
media_header *mediaHeader)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
while ((result = ogg_stream_packetout(stream, &fChunkPacket)) < 0) {
|
||||||
|
// ignore previous packet fragments
|
||||||
|
}
|
||||||
|
if (result == 0) {
|
||||||
|
return B_BUFFER_NOT_AVAILABLE;
|
||||||
|
}
|
||||||
|
*chunkBuffer = &fChunkPacket;
|
||||||
|
*chunkSize = sizeof(fChunkPacket);
|
||||||
|
mediaHeader->start_time = fCurrentTime;
|
||||||
|
ogg_int64_t granulepos = fChunkPacket.granulepos;
|
||||||
|
if (streaming) {
|
||||||
|
if (granulepos < 0) {
|
||||||
|
fCurrentFrame = -1;
|
||||||
|
} else {
|
||||||
|
fCurrentFrame = fOldFrame + GranulesToFrames(granulepos)
|
||||||
|
- GranulesToFrames(fOldGranulePos);
|
||||||
|
fOldFrame = fCurrentFrame;
|
||||||
|
fOldGranulePos = granulepos;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debugger("explode");
|
||||||
|
}
|
||||||
|
// default (silly) 1 frame/sec
|
||||||
|
fCurrentTime = 1000000LL * fCurrentFrame;
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OggCodecTest implementations
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
OggCodecTest::OggCodecTest()
|
||||||
|
{
|
||||||
|
TRACE("OggCodecTest::OggCodecTest\n");
|
||||||
|
OggCodecRoster * roster = OggCodecRoster::Roster();
|
||||||
|
if (roster != NULL) {
|
||||||
|
roster->AddCodecTest(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */
|
||||||
|
OggCodecTest::~OggCodecTest()
|
||||||
|
{
|
||||||
|
TRACE("OggCodecTest::~OggCodecTest\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OggCodecRoster implementations
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/* static */ OggCodecRoster *
|
||||||
|
OggCodecRoster::Roster(status_t * outError)
|
||||||
|
{
|
||||||
|
TRACE("OggCodecRoster::Roster\n");
|
||||||
|
static OggCodecRoster * ogg_codec_roster = NULL;
|
||||||
|
if (ogg_codec_roster == NULL) {
|
||||||
|
ogg_codec_roster = new OggCodecRoster();
|
||||||
|
if ((ogg_codec_roster == NULL) && (outError != NULL)) {
|
||||||
|
*outError = B_NO_MEMORY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ogg_codec_roster;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
OggCodecRoster::AddCodecTest(const OggCodecTest * test)
|
||||||
|
{
|
||||||
|
TRACE("OggCodecRoster::AddCodecTest\n");
|
||||||
|
fCodecs.push_back(test);
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
OggCodec *
|
||||||
|
OggCodecRoster::CodecFor(const ogg_packet & packet) const
|
||||||
|
{
|
||||||
|
TRACE("OggCodecRoster::CodecFor\n");
|
||||||
|
std::vector<const OggCodecTest *>::const_iterator iter;
|
||||||
|
for(iter = fCodecs.begin() ; iter != fCodecs.end() ; iter++) {
|
||||||
|
const OggCodecTest * test = *iter;
|
||||||
|
if (test->RecognizesInitialPacket(packet)) {
|
||||||
|
return test->InstantiateCodec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
OggCodecRoster::OggCodecRoster()
|
||||||
|
{
|
||||||
|
TRACE("OggCodecRoster::OggCodecRoster\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
OggCodecRoster::~OggCodecRoster()
|
||||||
|
{
|
||||||
|
TRACE("OggCodecRoster::~OggCodecRoster\n");
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
#ifndef _OGG_CODECS_H
|
||||||
|
#define _OGG_CODECS_H
|
||||||
|
|
||||||
|
#include <SupportDefs.h>
|
||||||
|
#include <MediaDefs.h>
|
||||||
|
#include "ogg/ogg.h"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace BPrivate { namespace media {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Useful ogg_packet functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool findIdentifier(const ogg_packet & packet, const char * id, uint pos);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OggCodec
|
||||||
|
*
|
||||||
|
* An OggCodec is instantiated to assist an OggTrack with interacting with the codecs.
|
||||||
|
* It helps with seeking, timing, and transforming the ogg_packets into chunks.
|
||||||
|
*/
|
||||||
|
class OggCodec {
|
||||||
|
public:
|
||||||
|
OggCodec();
|
||||||
|
virtual ~OggCodec();
|
||||||
|
|
||||||
|
virtual bool IsHeaderPacket(const ogg_packet & packet, uint packetno) const;
|
||||||
|
virtual status_t HandlePacket(const ogg_packet & packet);
|
||||||
|
virtual status_t GetFormat(media_format * format) const;
|
||||||
|
virtual int64 GranulesToFrames(int64 granules) const;
|
||||||
|
virtual int64 FramesToGranules(int64 frames) const;
|
||||||
|
virtual status_t GetChunk(ogg_stream_state * stream, bool streaming,
|
||||||
|
void **chunkBuffer, int32 *chunkSize,
|
||||||
|
media_header *mediaHeader);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ogg_packet fChunkPacket;
|
||||||
|
int64 fCurrentFrame;
|
||||||
|
bigtime_t fCurrentTime;
|
||||||
|
int64 fOldFrame;
|
||||||
|
ogg_int64_t fOldGranulePos;
|
||||||
|
media_format fMediaFormat;
|
||||||
|
status_t fInitCheck;
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned char * fPacketData;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OggCodecTest
|
||||||
|
*
|
||||||
|
* An OggCodecTest is capable of determining if an initial packet belongs to a
|
||||||
|
* particular codec. It can instantiate that particular codec.
|
||||||
|
*/
|
||||||
|
class OggCodecTest {
|
||||||
|
protected:
|
||||||
|
OggCodecTest();
|
||||||
|
public:
|
||||||
|
virtual ~OggCodecTest();
|
||||||
|
virtual bool RecognizesInitialPacket(const ogg_packet & packet) const = 0;
|
||||||
|
virtual OggCodec * InstantiateCodec() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OggCodecRoster
|
||||||
|
*
|
||||||
|
* An OggCodecRoster provides a single location to register OggCodecTests
|
||||||
|
* and retrieve an appropriate OggCodec for an ogg_packet.
|
||||||
|
*/
|
||||||
|
class OggCodecRoster {
|
||||||
|
public:
|
||||||
|
static OggCodecRoster * Roster(status_t * outError = NULL);
|
||||||
|
|
||||||
|
status_t AddCodecTest(const OggCodecTest * wrapper);
|
||||||
|
OggCodec * CodecFor(const ogg_packet & packet) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
OggCodecRoster();
|
||||||
|
~OggCodecRoster();
|
||||||
|
|
||||||
|
std::vector<const OggCodecTest *> fCodecs;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} } // BPrivate::media
|
||||||
|
|
||||||
|
using namespace BPrivate::media;
|
||||||
|
|
||||||
|
#endif // _OGG_CODECS_H
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
#include "OggSpeexFormats.h"
|
||||||
|
#include "OggCodecs.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;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* OggSpeexCodec implementations
|
||||||
|
*/
|
||||||
|
|
||||||
|
class OggSpeexCodec : public OggCodec {
|
||||||
|
public:
|
||||||
|
OggSpeexCodec();
|
||||||
|
virtual ~OggSpeexCodec();
|
||||||
|
|
||||||
|
virtual bool IsHeaderPacket(const ogg_packet & packet, uint packetno) const;
|
||||||
|
virtual status_t HandlePacket(const ogg_packet & packet);
|
||||||
|
|
||||||
|
virtual status_t GetChunk(ogg_stream_state * stream, bool streaming,
|
||||||
|
void **chunkBuffer, int32 *chunkSize,
|
||||||
|
media_header *mediaHeader);
|
||||||
|
private:
|
||||||
|
std::vector<ogg_packet> fMetaDataPackets;
|
||||||
|
unsigned char * fHeaderPacketData;
|
||||||
|
unsigned char * fCommentPacketData;
|
||||||
|
std::vector<uchar *> fExtraPacketData;
|
||||||
|
unsigned int fPacketCount;
|
||||||
|
unsigned int fExtraHeadersField;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
OggSpeexCodec::OggSpeexCodec()
|
||||||
|
{
|
||||||
|
TRACE("OggSpeexCodec::OggSpeexCodec\n");
|
||||||
|
fHeaderPacketData = NULL;
|
||||||
|
fCommentPacketData = NULL;
|
||||||
|
fPacketCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
OggSpeexCodec::~OggSpeexCodec()
|
||||||
|
{
|
||||||
|
TRACE("OggSpeexCodec::~OggSpeexCodec\n");
|
||||||
|
delete fHeaderPacketData;
|
||||||
|
delete fCommentPacketData;
|
||||||
|
std::vector<uchar *>::iterator iter;
|
||||||
|
for (iter = fExtraPacketData.begin() ; iter != fExtraPacketData.end() ; iter++) {
|
||||||
|
uchar * data = *iter;
|
||||||
|
delete data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ bool
|
||||||
|
OggSpeexCodec::IsHeaderPacket(const ogg_packet & packet, uint packetno) const
|
||||||
|
{
|
||||||
|
if (packetno < 2) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (fPacketCount == 0) {
|
||||||
|
debugger("IsHeaderPacket called for packetno >= 2, before receiving the header packet");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (packetno < 2 + fExtraHeadersField);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ status_t
|
||||||
|
OggSpeexCodec::HandlePacket(const ogg_packet & packet)
|
||||||
|
{
|
||||||
|
TRACE("OggSpeexCodec::HandlePacket\n");
|
||||||
|
if (!IsHeaderPacket(packet, fPacketCount)) {
|
||||||
|
return B_BAD_VALUE;
|
||||||
|
}
|
||||||
|
switch (fPacketCount) {
|
||||||
|
case 0: {
|
||||||
|
// header packet
|
||||||
|
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;
|
||||||
|
|
||||||
|
// store how many extra header packets
|
||||||
|
fExtraHeadersField = header->extra_headers;
|
||||||
|
|
||||||
|
// get the format for the description
|
||||||
|
media_format_description description = speex_description();
|
||||||
|
BMediaFormats formats;
|
||||||
|
if ((formats.InitCheck() != B_OK) ||
|
||||||
|
(formats.GetFormatFor(description, &fMediaFormat) != B_OK)) {
|
||||||
|
fMediaFormat = speex_encoded_media_format();
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill out format from header packet
|
||||||
|
if (header->bitrate > 0) {
|
||||||
|
fMediaFormat.u.encoded_audio.bit_rate = header->bitrate;
|
||||||
|
} else {
|
||||||
|
// TODO: manually compute it where possible
|
||||||
|
}
|
||||||
|
if (header->nb_channels == 1) {
|
||||||
|
fMediaFormat.u.encoded_audio.multi_info.channel_mask = B_CHANNEL_LEFT;
|
||||||
|
} else {
|
||||||
|
fMediaFormat.u.encoded_audio.multi_info.channel_mask = B_CHANNEL_LEFT | B_CHANNEL_RIGHT;
|
||||||
|
}
|
||||||
|
fMediaFormat.u.encoded_audio.output.frame_rate = header->rate;
|
||||||
|
fMediaFormat.u.encoded_audio.output.channel_count = header->nb_channels;
|
||||||
|
// allocate buffer, round up to nearest speex output_length size
|
||||||
|
int buffer_size = AudioBufferSize(&fMediaFormat.u.encoded_audio.output);
|
||||||
|
int output_length = header->frame_size * header->nb_channels *
|
||||||
|
(fMediaFormat.u.encoded_audio.output.format & 0xf);
|
||||||
|
buffer_size = ((buffer_size - 1) / output_length + 1) * output_length;
|
||||||
|
fMediaFormat.u.encoded_audio.output.buffer_size = buffer_size;
|
||||||
|
|
||||||
|
fHeaderPacketData = new unsigned char[packet.bytes];
|
||||||
|
memcpy(fHeaderPacketData, packet.packet, packet.bytes);
|
||||||
|
fMetaDataPackets.push_back(packet);
|
||||||
|
fMetaDataPackets[fPacketCount].packet = fHeaderPacketData;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
// comment packet
|
||||||
|
fCommentPacketData = new unsigned char[packet.bytes];
|
||||||
|
memcpy(fCommentPacketData, packet.packet, packet.bytes);
|
||||||
|
fMetaDataPackets.push_back(packet);
|
||||||
|
fMetaDataPackets[fPacketCount].packet = fCommentPacketData;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// extra headers
|
||||||
|
uchar * extraHeaderData = new unsigned char[packet.bytes];
|
||||||
|
memcpy(extraHeaderData, packet.packet, packet.bytes);
|
||||||
|
fExtraPacketData.push_back(extraHeaderData);
|
||||||
|
fMetaDataPackets.push_back(packet);
|
||||||
|
fMetaDataPackets[fPacketCount].packet = extraHeaderData;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (fPacketCount == 1 + fExtraHeadersField) {
|
||||||
|
fMediaFormat.SetMetaData(&fMetaDataPackets,sizeof(fMetaDataPackets));
|
||||||
|
fInitCheck = B_OK;
|
||||||
|
}
|
||||||
|
fPacketCount++;
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ status_t
|
||||||
|
OggSpeexCodec::GetChunk(ogg_stream_state * stream, bool streaming,
|
||||||
|
void **chunkBuffer, int32 *chunkSize,
|
||||||
|
media_header *mediaHeader)
|
||||||
|
{
|
||||||
|
if (fCurrentFrame == -1) {
|
||||||
|
fCurrentTime = -1;
|
||||||
|
} else {
|
||||||
|
fCurrentTime = (1000000LL * fCurrentFrame)
|
||||||
|
/ (long long)fMediaFormat.u.encoded_audio.output.frame_rate;
|
||||||
|
}
|
||||||
|
status_t result = OggCodec::GetChunk(stream, streaming, chunkBuffer, chunkSize, mediaHeader);
|
||||||
|
if (result != B_OK) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
*chunkBuffer = fChunkPacket.packet;
|
||||||
|
*chunkSize = fChunkPacket.bytes;
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* OggSpeexCodecTest
|
||||||
|
*/
|
||||||
|
|
||||||
|
static class OggSpeexCodecTest : public OggCodecTest {
|
||||||
|
public:
|
||||||
|
OggSpeexCodecTest() {}
|
||||||
|
virtual ~OggSpeexCodecTest() {}
|
||||||
|
virtual bool RecognizesInitialPacket(const ogg_packet & packet) const
|
||||||
|
{
|
||||||
|
return findIdentifier(packet, "Speex ", 0);
|
||||||
|
}
|
||||||
|
virtual OggCodec * InstantiateCodec() const
|
||||||
|
{
|
||||||
|
return new OggSpeexCodec();
|
||||||
|
}
|
||||||
|
} ogg_speex_codec_test;
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
#include "OggSpeexFormats.h"
|
|
||||||
#include "OggSpeexSeekable.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;
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* OggSpeexSeekable implementations
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* static */ bool
|
|
||||||
OggSpeexSeekable::IsValidHeader(const ogg_packet & packet)
|
|
||||||
{
|
|
||||||
return findIdentifier(packet,"Speex ",0);
|
|
||||||
}
|
|
||||||
|
|
||||||
OggSpeexSeekable::OggSpeexSeekable(long serialno)
|
|
||||||
: OggSeekable(serialno)
|
|
||||||
{
|
|
||||||
TRACE("OggSpeexSeekable::OggSpeexSeekable\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
OggSpeexSeekable::~OggSpeexSeekable()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
status_t
|
|
||||||
OggSpeexSeekable::GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
|
||||||
media_format *format)
|
|
||||||
{
|
|
||||||
TRACE("OggSpeexSeekable::GetStreamInfo\n");
|
|
||||||
status_t result = B_OK;
|
|
||||||
ogg_packet packet;
|
|
||||||
|
|
||||||
// get header packet
|
|
||||||
if (GetHeaderPackets().size() < 1) {
|
|
||||||
result = GetPacket(&packet);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
SaveHeaderPacket(packet);
|
|
||||||
}
|
|
||||||
packet = GetHeaderPackets()[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 = speex_description();
|
|
||||||
BMediaFormats formats;
|
|
||||||
result = formats.InitCheck();
|
|
||||||
if (result == B_OK) {
|
|
||||||
result = formats.GetFormatFor(description, format);
|
|
||||||
}
|
|
||||||
if (result != B_OK) {
|
|
||||||
*format = speex_encoded_media_format();
|
|
||||||
// ignore error, allow user to use ReadChunk interface
|
|
||||||
}
|
|
||||||
|
|
||||||
// fill out format from header packet
|
|
||||||
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;
|
|
||||||
} else {
|
|
||||||
format->u.encoded_audio.multi_info.channel_mask = B_CHANNEL_LEFT | B_CHANNEL_RIGHT;
|
|
||||||
}
|
|
||||||
fFrameRate = format->u.encoded_audio.output.frame_rate = header->rate;
|
|
||||||
format->u.encoded_audio.output.channel_count = header->nb_channels;
|
|
||||||
// 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 (GetHeaderPackets().size() < 2) {
|
|
||||||
result = GetPacket(&packet);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
SaveHeaderPacket(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get extra headers
|
|
||||||
while ((signed)GetHeaderPackets().size() < header->extra_headers) {
|
|
||||||
result = GetPacket(&packet);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
SaveHeaderPacket(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
format->SetMetaData((void*)&GetHeaderPackets(),sizeof(GetHeaderPackets()));
|
|
||||||
|
|
||||||
// TODO: count the frames in the first page.. somehow.. :-/
|
|
||||||
int64 frames = 0;
|
|
||||||
|
|
||||||
ogg_page page;
|
|
||||||
// read the first page
|
|
||||||
result = ReadPage(&page);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
int64 fFirstGranulepos = ogg_page_granulepos(&page);
|
|
||||||
TRACE("OggVorbisSeekable::GetStreamInfo: first granulepos: %lld\n", fFirstGranulepos);
|
|
||||||
// read our last page
|
|
||||||
off_t last = inherited::Seek(GetLastPagePosition(), SEEK_SET);
|
|
||||||
if (last < 0) {
|
|
||||||
return last;
|
|
||||||
}
|
|
||||||
result = ReadPage(&page);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
int64 last_granulepos = ogg_page_granulepos(&page);
|
|
||||||
|
|
||||||
// seek back to the start
|
|
||||||
int64 frame = 0;
|
|
||||||
bigtime_t time = 0;
|
|
||||||
result = Seek(B_MEDIA_SEEK_TO_TIME, &frame, &time);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// compute frame count and duration from sample count
|
|
||||||
frames = last_granulepos - fFirstGranulepos;
|
|
||||||
|
|
||||||
*frameCount = frames;
|
|
||||||
*duration = (1000000LL * frames) / (long long)fFrameRate;
|
|
||||||
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
status_t
|
|
||||||
OggSpeexSeekable::GetNextChunk(void **chunkBuffer, int32 *chunkSize,
|
|
||||||
media_header *mediaHeader)
|
|
||||||
{
|
|
||||||
status_t result = inherited::GetNextChunk(chunkBuffer, chunkSize, mediaHeader);
|
|
||||||
if (result != B_OK) {
|
|
||||||
TRACE("OggSpeexSeekable::GetNextChunk failed: GetNextChunk = %s\n", strerror(result));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
*chunkSize = ((ogg_packet*)*chunkBuffer)->bytes;
|
|
||||||
*chunkBuffer = ((ogg_packet*)*chunkBuffer)->packet;
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
#ifndef _OGG_SPEEX_SEEKABLE_H
|
|
||||||
#define _OGG_SPEEX_SEEKABLE_H
|
|
||||||
|
|
||||||
#include "OggSeekable.h"
|
|
||||||
|
|
||||||
namespace BPrivate { namespace media {
|
|
||||||
|
|
||||||
class OggSpeexSeekable : public OggSeekable {
|
|
||||||
private:
|
|
||||||
typedef OggSeekable inherited;
|
|
||||||
public:
|
|
||||||
static bool IsValidHeader(const ogg_packet & packet);
|
|
||||||
public:
|
|
||||||
OggSpeexSeekable(long serialno);
|
|
||||||
virtual ~OggSpeexSeekable();
|
|
||||||
|
|
||||||
virtual status_t GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
|
||||||
media_format *format);
|
|
||||||
virtual status_t GetNextChunk(void **chunkBuffer, int32 *chunkSize,
|
|
||||||
media_header *mediaHeader);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
} } // namespace BPrivate::media
|
|
||||||
|
|
||||||
using namespace BPrivate::media;
|
|
||||||
|
|
||||||
#endif // _OGG_SPEEX_SEEKABLE_H
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
#include "OggSpeexFormats.h"
|
|
||||||
#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)
|
|
||||||
{
|
|
||||||
TRACE("OggSpeexStream::OggSpeexStream\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (GetHeaderPackets().size() < 1) {
|
|
||||||
result = GetPacket(&packet);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
SaveHeaderPacket(packet);
|
|
||||||
}
|
|
||||||
packet = GetHeaderPackets()[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 = speex_description();
|
|
||||||
BMediaFormats formats;
|
|
||||||
result = formats.InitCheck();
|
|
||||||
if (result == B_OK) {
|
|
||||||
result = formats.GetFormatFor(description, format);
|
|
||||||
}
|
|
||||||
if (result != B_OK) {
|
|
||||||
*format = speex_encoded_media_format();
|
|
||||||
// ignore error, allow user to use ReadChunk interface
|
|
||||||
}
|
|
||||||
|
|
||||||
// fill out format from header packet
|
|
||||||
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;
|
|
||||||
} else {
|
|
||||||
format->u.encoded_audio.multi_info.channel_mask = B_CHANNEL_LEFT | B_CHANNEL_RIGHT;
|
|
||||||
}
|
|
||||||
format->u.encoded_audio.output.frame_rate = header->rate;
|
|
||||||
format->u.encoded_audio.output.channel_count = header->nb_channels;
|
|
||||||
// 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 (GetHeaderPackets().size() < 2) {
|
|
||||||
result = GetPacket(&packet);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
SaveHeaderPacket(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get extra headers
|
|
||||||
while ((signed)GetHeaderPackets().size() < header->extra_headers) {
|
|
||||||
result = GetPacket(&packet);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
SaveHeaderPacket(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
format->SetMetaData((void*)&GetHeaderPackets(),sizeof(GetHeaderPackets()));
|
|
||||||
*duration = 100000000;
|
|
||||||
*frameCount = 60000;
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
status_t
|
|
||||||
OggSpeexStream::GetNextChunk(void **chunkBuffer, int32 *chunkSize,
|
|
||||||
media_header *mediaHeader)
|
|
||||||
{
|
|
||||||
status_t result = GetPacket(&fChunkPacket);
|
|
||||||
if (result != B_OK) {
|
|
||||||
TRACE("OggSpeexStream::GetNextChunk failed: GetPacket = %s\n", strerror(result));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
*chunkBuffer = fChunkPacket.packet;
|
|
||||||
*chunkSize = fChunkPacket.bytes;
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
#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);
|
|
||||||
virtual status_t GetNextChunk(void **chunkBuffer, int32 *chunkSize,
|
|
||||||
media_header *mediaHeader);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
} } // namespace BPrivate::media
|
|
||||||
|
|
||||||
using namespace BPrivate::media;
|
|
||||||
|
|
||||||
#endif // _OGG_SPEEX_STREAM_H
|
|
||||||
@@ -0,0 +1,327 @@
|
|||||||
|
#include "OggTheoraFormats.h"
|
||||||
|
#include "OggCodecs.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#define TRACE_THIS 1
|
||||||
|
#if TRACE_THIS
|
||||||
|
#define TRACE printf
|
||||||
|
#else
|
||||||
|
#define TRACE(a...) ((void)0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
typedef struct theora_comment{
|
||||||
|
char **user_comments;
|
||||||
|
int *comment_lengths;
|
||||||
|
int comments;
|
||||||
|
char *vendor;
|
||||||
|
|
||||||
|
} theora_comment;
|
||||||
|
|
||||||
|
// based on theora/lib/toplevel.c _theora_unpack_info
|
||||||
|
|
||||||
|
#define theora_read(x,y,z) ( *z = oggpackB_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* OggTheoraCodec
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
class OggTheoraCodec : public OggCodec {
|
||||||
|
public:
|
||||||
|
OggTheoraCodec();
|
||||||
|
virtual ~OggTheoraCodec();
|
||||||
|
|
||||||
|
virtual bool IsHeaderPacket(const ogg_packet & packet, uint packetno) const;
|
||||||
|
virtual status_t HandlePacket(const ogg_packet & packet);
|
||||||
|
|
||||||
|
virtual status_t GetChunk(ogg_stream_state * stream, bool streaming,
|
||||||
|
void **chunkBuffer, int32 *chunkSize,
|
||||||
|
media_header *mediaHeader);
|
||||||
|
private:
|
||||||
|
std::vector<ogg_packet> fMetaDataPackets;
|
||||||
|
unsigned char * fHeaderPacketData;
|
||||||
|
unsigned char * fCommentPacketData;
|
||||||
|
unsigned char * fCodecPacketData;
|
||||||
|
unsigned int fPacketCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
OggTheoraCodec::OggTheoraCodec()
|
||||||
|
{
|
||||||
|
TRACE("OggTheoraCodec::OggTheoraCodec\n");
|
||||||
|
fHeaderPacketData = NULL;
|
||||||
|
fCommentPacketData = NULL;
|
||||||
|
fCodecPacketData = NULL;
|
||||||
|
fPacketCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
OggTheoraCodec::~OggTheoraCodec()
|
||||||
|
{
|
||||||
|
delete fHeaderPacketData;
|
||||||
|
delete fCommentPacketData;
|
||||||
|
delete fCodecPacketData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ bool
|
||||||
|
OggTheoraCodec::IsHeaderPacket(const ogg_packet & packet, uint packetno) const
|
||||||
|
{
|
||||||
|
oggpack_buffer opb;
|
||||||
|
oggpackB_readinit(&opb, packet.packet, packet.bytes);
|
||||||
|
uint packtype = oggpackB_read(&opb, 8);
|
||||||
|
return (packetno == 0 && packtype == 0x80)
|
||||||
|
|| (packetno == 1 && packtype == 0x81)
|
||||||
|
|| (packetno == 2 && packtype == 0x82);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ status_t
|
||||||
|
OggTheoraCodec::HandlePacket(const ogg_packet & packet)
|
||||||
|
{
|
||||||
|
TRACE("OggTheoraCodec::HandlePacket\n");
|
||||||
|
if (!IsHeaderPacket(packet, fPacketCount)) {
|
||||||
|
return B_BAD_VALUE;
|
||||||
|
}
|
||||||
|
switch (fPacketCount) {
|
||||||
|
case 0: {
|
||||||
|
// header packet
|
||||||
|
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;
|
||||||
|
oggpackB_readinit(&opb, packet.packet, packet.bytes);
|
||||||
|
// discard packet type (already validated in IsHeaderPacket)
|
||||||
|
oggpackB_read(&opb, 8);
|
||||||
|
// discard theora string
|
||||||
|
for (uint i = 0 ; i < sizeof("theora") - 1 ; i++) {
|
||||||
|
oggpackB_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 = theora_description();
|
||||||
|
BMediaFormats formats;
|
||||||
|
if ((formats.InitCheck() != B_OK) ||
|
||||||
|
(formats.GetFormatFor(description, &fMediaFormat) != B_OK)) {
|
||||||
|
fMediaFormat = theora_encoded_media_format();
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill out format from header packet
|
||||||
|
// sanity check
|
||||||
|
if (info.fps_denominator != 0) {
|
||||||
|
fMediaFormat.u.encoded_video.output.field_rate =
|
||||||
|
(double)info.fps_numerator / (double)info.fps_denominator;
|
||||||
|
}
|
||||||
|
fMediaFormat.u.encoded_video.output.first_active = info.offset_y;
|
||||||
|
fMediaFormat.u.encoded_video.output.last_active = info.offset_y + info.height;
|
||||||
|
fMediaFormat.u.encoded_video.output.pixel_width_aspect = info.aspect_numerator;
|
||||||
|
fMediaFormat.u.encoded_video.output.pixel_height_aspect = info.aspect_denominator;
|
||||||
|
fMediaFormat.u.encoded_video.output.display.line_width = info.frame_width;
|
||||||
|
fMediaFormat.u.encoded_video.output.display.line_count = info.frame_height;
|
||||||
|
fMediaFormat.u.encoded_video.avg_bit_rate = info.target_bitrate; // advisory only
|
||||||
|
fMediaFormat.u.encoded_video.frame_size = info.width * info.height;
|
||||||
|
// TODO: wring more info out of the headers
|
||||||
|
|
||||||
|
fHeaderPacketData = new unsigned char[packet.bytes];
|
||||||
|
memcpy(fHeaderPacketData, packet.packet, packet.bytes);
|
||||||
|
fMetaDataPackets.push_back(packet);
|
||||||
|
fMetaDataPackets[fPacketCount].packet = fHeaderPacketData;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
// comment packet
|
||||||
|
fCommentPacketData = new unsigned char[packet.bytes];
|
||||||
|
memcpy(fCommentPacketData, packet.packet, packet.bytes);
|
||||||
|
fMetaDataPackets.push_back(packet);
|
||||||
|
fMetaDataPackets[fPacketCount].packet = fCommentPacketData;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
// codec setup packet
|
||||||
|
fCodecPacketData = new unsigned char[packet.bytes];
|
||||||
|
memcpy(fCodecPacketData, packet.packet, packet.bytes);
|
||||||
|
fMetaDataPackets.push_back(packet);
|
||||||
|
fMetaDataPackets[fPacketCount].packet = fCodecPacketData;
|
||||||
|
|
||||||
|
fMediaFormat.SetMetaData(&fMetaDataPackets,sizeof(fMetaDataPackets));
|
||||||
|
fInitCheck = B_OK;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// huh?
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fPacketCount++;
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ status_t
|
||||||
|
OggTheoraCodec::GetChunk(ogg_stream_state * stream, bool streaming,
|
||||||
|
void **chunkBuffer, int32 *chunkSize,
|
||||||
|
media_header *mediaHeader)
|
||||||
|
{
|
||||||
|
if (streaming) {
|
||||||
|
if (fChunkPacket.granulepos == -1) {
|
||||||
|
fCurrentTime = -1;
|
||||||
|
} else {
|
||||||
|
fCurrentTime = (bigtime_t) ((1000000LL * fCurrentFrame)
|
||||||
|
/ (fMediaFormat.u.encoded_video.output.field_rate *
|
||||||
|
fMediaFormat.u.encoded_video.output.interlace));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debugger("kaboom");
|
||||||
|
}
|
||||||
|
status_t result = OggCodec::GetChunk(stream, streaming, chunkBuffer, chunkSize, mediaHeader);
|
||||||
|
if (result != B_OK) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
oggpack_buffer opb;
|
||||||
|
oggpackB_readinit(&opb, fChunkPacket.packet, fChunkPacket.bytes);
|
||||||
|
// Read 1 bit. This must be 0 for all data packets.
|
||||||
|
uint packtype = oggpackB_read(&opb, 1);
|
||||||
|
if (packtype != 0) {
|
||||||
|
// packet was not a data packet
|
||||||
|
return B_ERROR;
|
||||||
|
}
|
||||||
|
// read 1 bit (frametype) this is 0 for keyframes, 1 for P frames.
|
||||||
|
uint keyframe = oggpackB_read(&opb, 1);
|
||||||
|
// read 6 bits (quality mask) this is the quality index for this frame.
|
||||||
|
// uint quality = oggpackB_read(&opb, 6);
|
||||||
|
/*
|
||||||
|
if the frametype is 0 (keyframe):
|
||||||
|
read 1 bit (keyframetype) this indicates the coding variety of the keyframe.
|
||||||
|
read 2 bits (spare) these bits are unused.
|
||||||
|
*/
|
||||||
|
mediaHeader->u.encoded_video.field_number = fCurrentFrame;
|
||||||
|
mediaHeader->u.encoded_video.field_flags = (keyframe == 1 ? B_MEDIA_KEY_FRAME : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* OggTheoraCodecWrapper
|
||||||
|
*/
|
||||||
|
|
||||||
|
static class OggTheoraCodecTest : public OggCodecTest {
|
||||||
|
public:
|
||||||
|
OggTheoraCodecTest() {}
|
||||||
|
virtual ~OggTheoraCodecTest() {}
|
||||||
|
virtual bool RecognizesInitialPacket(const ogg_packet & packet) const
|
||||||
|
{
|
||||||
|
return findIdentifier(packet, "theora", 1);
|
||||||
|
}
|
||||||
|
virtual OggCodec * InstantiateCodec() const
|
||||||
|
{
|
||||||
|
return new OggTheoraCodec();
|
||||||
|
}
|
||||||
|
} ogg_theora_codec_test;
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
#include "OggTheoraFormats.h"
|
|
||||||
#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)
|
|
||||||
{
|
|
||||||
TRACE("OggTheoraStream::OggTheoraStream\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (GetHeaderPackets().size() < 1) {
|
|
||||||
result = GetPacket(&packet);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
SaveHeaderPacket(packet);
|
|
||||||
}
|
|
||||||
packet = GetHeaderPackets()[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 = theora_description();
|
|
||||||
BMediaFormats formats;
|
|
||||||
result = formats.InitCheck();
|
|
||||||
if (result == B_OK) {
|
|
||||||
result = formats.GetFormatFor(description, format);
|
|
||||||
}
|
|
||||||
if (result != B_OK) {
|
|
||||||
*format = theora_encoded_media_format();
|
|
||||||
// ignore error, allow user to use ReadChunk interface
|
|
||||||
}
|
|
||||||
|
|
||||||
// fill out format from header packet
|
|
||||||
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*)&GetHeaderPackets(),sizeof(GetHeaderPackets()));
|
|
||||||
*duration = 80000000;
|
|
||||||
*frameCount = 60000;
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
#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,465 @@
|
|||||||
|
#include "OggTobiasFormats.h"
|
||||||
|
#include "OggCodecs.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#define TRACE_THIS 1
|
||||||
|
#if TRACE_THIS
|
||||||
|
#define TRACE printf
|
||||||
|
#else
|
||||||
|
#define TRACE(a...) ((void)0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 (100 ns units)
|
||||||
|
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 int64
|
||||||
|
count_granules(const ogg_packet & packet)
|
||||||
|
{
|
||||||
|
// thanks to Marcus:
|
||||||
|
int lenbytes = ((packet.packet[0] & 0xc0) >> 6) | ((packet.packet[0] & 0x02) << 1);
|
||||||
|
if (lenbytes == 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int64 granules = 0;
|
||||||
|
int count = 0;
|
||||||
|
// fprintf(stderr, "lenbytes = %d, ", lenbytes);
|
||||||
|
while (lenbytes-- > 0) {
|
||||||
|
granules += ((uint8*)packet.packet)[count+1] * (1LL << 8 * count);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
// fprintf(stderr, "granules = %lld\n", granules);
|
||||||
|
return granules;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* OggTobiasCodec
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
class OggTobiasCodec : public OggCodec {
|
||||||
|
public:
|
||||||
|
OggTobiasCodec() {}
|
||||||
|
virtual ~OggTobiasCodec() {}
|
||||||
|
|
||||||
|
virtual bool IsHeaderPacket(const ogg_packet & packet, uint packetno) const;
|
||||||
|
virtual status_t HandlePacket(const ogg_packet & packet);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual status_t HeaderToFormat(const tobias_stream_header & header) = 0;
|
||||||
|
|
||||||
|
std::vector<ogg_packet> fMetaDataPackets;
|
||||||
|
unsigned char * fHeaderPacketData;
|
||||||
|
unsigned char * fCommentPacketData;
|
||||||
|
unsigned int fPacketCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ bool
|
||||||
|
OggTobiasCodec::IsHeaderPacket(const ogg_packet & packet, uint packetno) const
|
||||||
|
{
|
||||||
|
oggpack_buffer opb;
|
||||||
|
oggpack_readinit(&opb, packet.packet, packet.bytes);
|
||||||
|
uint packtype = oggpack_read(&opb, 8);
|
||||||
|
return (packetno == 0 && packtype == 0x01)
|
||||||
|
|| (packetno == 1 && packtype == 0x03);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ status_t
|
||||||
|
OggTobiasCodec::HandlePacket(const ogg_packet & packet)
|
||||||
|
{
|
||||||
|
TRACE("OggTobiasCodec::HandlePacket\n");
|
||||||
|
if (!IsHeaderPacket(packet, fPacketCount)) {
|
||||||
|
return B_ERROR;
|
||||||
|
}
|
||||||
|
switch (fPacketCount) {
|
||||||
|
case 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;
|
||||||
|
|
||||||
|
status_t result = HeaderToFormat(*header);
|
||||||
|
if (result != B_OK) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize codec info
|
||||||
|
fMediaFormat.user_data_type = B_CODEC_TYPE_INFO;
|
||||||
|
strncpy((char*)fMediaFormat.user_data, header->subtype, 4);
|
||||||
|
|
||||||
|
// save the header packet for use in meta data
|
||||||
|
fHeaderPacketData = new unsigned char[packet.bytes];
|
||||||
|
memcpy(fHeaderPacketData, packet.packet, packet.bytes);
|
||||||
|
fMetaDataPackets.push_back(packet);
|
||||||
|
fMetaDataPackets[fPacketCount].packet = fHeaderPacketData;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
// save the comment packet for use in meta data
|
||||||
|
fCommentPacketData = new unsigned char[packet.bytes];
|
||||||
|
memcpy(fCommentPacketData, packet.packet, packet.bytes);
|
||||||
|
fMetaDataPackets.push_back(packet);
|
||||||
|
fMetaDataPackets[fPacketCount].packet = fCommentPacketData;
|
||||||
|
|
||||||
|
// ready for showtime
|
||||||
|
fInitCheck = B_OK;
|
||||||
|
fMediaFormat.SetMetaData(&fMetaDataPackets,sizeof(fMetaDataPackets));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// huh?
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fPacketCount++;
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* OggTobiasVideoCodec
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
class OggTobiasVideoCodec : public OggTobiasCodec {
|
||||||
|
public:
|
||||||
|
OggTobiasVideoCodec();
|
||||||
|
virtual ~OggTobiasVideoCodec();
|
||||||
|
|
||||||
|
virtual status_t GetChunk(ogg_stream_state * stream, bool streaming,
|
||||||
|
void **chunkBuffer, int32 *chunkSize,
|
||||||
|
media_header *mediaHeader);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual status_t HeaderToFormat(const tobias_stream_header & header);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
OggTobiasVideoCodec::OggTobiasVideoCodec()
|
||||||
|
{
|
||||||
|
TRACE("OggTobiasVideoCodec::OggTobiasVideoCodec\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
OggTobiasVideoCodec::~OggTobiasVideoCodec()
|
||||||
|
{
|
||||||
|
TRACE("OggTobiasVideoCodec::~OggTobiasVideoCodec\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ status_t
|
||||||
|
OggTobiasVideoCodec::HeaderToFormat(const tobias_stream_header & header)
|
||||||
|
{
|
||||||
|
TRACE("OggTobiasVideoCodec::HeaderToFormat\n");
|
||||||
|
|
||||||
|
// get the format for the description
|
||||||
|
media_format_description description = tobias_video_description();
|
||||||
|
description.u.avi.codec = header.subtype[3] << 24 | header.subtype[2] << 16
|
||||||
|
| header.subtype[1] << 8 | header.subtype[0];
|
||||||
|
BMediaFormats formats;
|
||||||
|
if ((formats.InitCheck() != B_OK) ||
|
||||||
|
(formats.GetFormatFor(description, &fMediaFormat) != B_OK)) {
|
||||||
|
fMediaFormat = tobias_video_encoded_media_format();
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill out format from header packet
|
||||||
|
fMediaFormat.u.encoded_video.frame_size
|
||||||
|
= header.video.width * header.video.height;
|
||||||
|
fMediaFormat.u.encoded_video.output.field_rate = 10000000.0 / header.time_unit;
|
||||||
|
fMediaFormat.u.encoded_video.output.interlace = 1;
|
||||||
|
fMediaFormat.u.encoded_video.output.first_active = 0;
|
||||||
|
fMediaFormat.u.encoded_video.output.last_active = header.video.height - 1;
|
||||||
|
fMediaFormat.u.encoded_video.output.orientation = B_VIDEO_TOP_LEFT_RIGHT;
|
||||||
|
fMediaFormat.u.encoded_video.output.pixel_width_aspect = 1;
|
||||||
|
fMediaFormat.u.encoded_video.output.pixel_height_aspect = 1;
|
||||||
|
fMediaFormat.u.encoded_video.output.display.line_width = header.video.width;
|
||||||
|
fMediaFormat.u.encoded_video.output.display.line_count = header.video.height;
|
||||||
|
fMediaFormat.u.encoded_video.output.display.bytes_per_row = 0;
|
||||||
|
fMediaFormat.u.encoded_video.output.display.pixel_offset = 0;
|
||||||
|
fMediaFormat.u.encoded_video.output.display.line_offset = 0;
|
||||||
|
fMediaFormat.u.encoded_video.output.display.flags = 0;
|
||||||
|
|
||||||
|
// TODO: wring more info out of the headers?
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ status_t
|
||||||
|
OggTobiasVideoCodec::GetChunk(ogg_stream_state * stream, bool streaming,
|
||||||
|
void **chunkBuffer, int32 *chunkSize,
|
||||||
|
media_header *mediaHeader)
|
||||||
|
{
|
||||||
|
status_t result = OggCodec::GetChunk(stream, streaming, chunkBuffer, chunkSize, mediaHeader);
|
||||||
|
if (result != B_OK) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (streaming) {
|
||||||
|
if (fChunkPacket.granulepos == -1) {
|
||||||
|
int new_granulepos = fOldGranulePos + 1;
|
||||||
|
fCurrentFrame = fOldFrame + GranulesToFrames(new_granulepos)
|
||||||
|
- GranulesToFrames(fOldGranulePos);
|
||||||
|
fOldFrame = fCurrentFrame;
|
||||||
|
fOldGranulePos = new_granulepos;
|
||||||
|
} else {
|
||||||
|
// fprintf(stderr, "granulepos: %lld ", fChunkPacket.granulepos);
|
||||||
|
// fprintf(stderr, "out: frame = %lld\n", fCurrentFrame);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debugger("kaboom");
|
||||||
|
}
|
||||||
|
fCurrentTime = (bigtime_t) ((1000000LL * fCurrentFrame)
|
||||||
|
/ (fMediaFormat.u.encoded_video.output.field_rate *
|
||||||
|
fMediaFormat.u.encoded_video.output.interlace));
|
||||||
|
// fprintf(stderr, "video in: frame = %lld time = %lld\n", fCurrentFrame, fCurrentTime);
|
||||||
|
int lenbytes = ((fChunkPacket.packet[0] & 0xc0) >> 6) | ((fChunkPacket.packet[0] & 0x02) << 1);
|
||||||
|
*chunkBuffer = (void*)&(fChunkPacket.packet[lenbytes+1]);
|
||||||
|
*chunkSize = fChunkPacket.bytes-(lenbytes+1);
|
||||||
|
bool keyframe = (fChunkPacket.packet[0] & (1 << 3));
|
||||||
|
mediaHeader->u.encoded_video.field_flags = (keyframe ? B_MEDIA_KEY_FRAME : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// OggTobiasVideoCodecTest
|
||||||
|
|
||||||
|
static class OggTobiasVideoCodecTest : public OggCodecTest {
|
||||||
|
public:
|
||||||
|
OggTobiasVideoCodecTest() {}
|
||||||
|
virtual ~OggTobiasVideoCodecTest() {}
|
||||||
|
virtual bool RecognizesInitialPacket(const ogg_packet & packet) const
|
||||||
|
{
|
||||||
|
return findIdentifier(packet, "video", 1);
|
||||||
|
}
|
||||||
|
virtual OggCodec * InstantiateCodec() const
|
||||||
|
{
|
||||||
|
return new OggTobiasVideoCodec();
|
||||||
|
}
|
||||||
|
} ogg_tobias_video_test;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* OggTobiasAudioCodec
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
class OggTobiasAudioCodec : public OggTobiasCodec {
|
||||||
|
public:
|
||||||
|
OggTobiasAudioCodec();
|
||||||
|
virtual ~OggTobiasAudioCodec();
|
||||||
|
|
||||||
|
virtual status_t GetChunk(ogg_stream_state * stream, bool streaming,
|
||||||
|
void **chunkBuffer, int32 *chunkSize,
|
||||||
|
media_header *mediaHeader);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual status_t HeaderToFormat(const tobias_stream_header & header);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
OggTobiasAudioCodec::OggTobiasAudioCodec()
|
||||||
|
{
|
||||||
|
TRACE("OggTobiasAudioCodec::OggTobiasAudioCodec\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
OggTobiasAudioCodec::~OggTobiasAudioCodec()
|
||||||
|
{
|
||||||
|
TRACE("OggTobiasAudioCodec::~OggTobiasAudioCodec\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ status_t
|
||||||
|
OggTobiasAudioCodec::HeaderToFormat(const tobias_stream_header & header)
|
||||||
|
{
|
||||||
|
TRACE("OggTobiasAudioCodec::HeaderToFormat\n");
|
||||||
|
|
||||||
|
debugger("get_audio_format");
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ status_t
|
||||||
|
OggTobiasAudioCodec::GetChunk(ogg_stream_state * stream, bool streaming,
|
||||||
|
void **chunkBuffer, int32 *chunkSize,
|
||||||
|
media_header *mediaHeader)
|
||||||
|
{
|
||||||
|
if (fCurrentFrame == -1) {
|
||||||
|
fCurrentFrame = fOldFrame + 1;
|
||||||
|
fOldFrame = fCurrentFrame;
|
||||||
|
fOldGranulePos++;
|
||||||
|
}
|
||||||
|
fCurrentTime = (bigtime_t) (1000000LL * fCurrentFrame)
|
||||||
|
/ (long long)fMediaFormat.u.encoded_audio.output.frame_rate;
|
||||||
|
status_t result = OggCodec::GetChunk(stream, streaming, chunkBuffer, chunkSize, mediaHeader);
|
||||||
|
if (result != B_OK) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
int lenbytes = ((fChunkPacket.packet[0] & 0xc0) >> 6) | ((fChunkPacket.packet[0] & 0x02) << 1);
|
||||||
|
*chunkBuffer = (void*)&(fChunkPacket.packet[lenbytes+1]);
|
||||||
|
*chunkSize = fChunkPacket.bytes-(lenbytes+1);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// OggTobiasAudioCodecTest
|
||||||
|
|
||||||
|
static class OggTobiasAudioCodecTest : public OggCodecTest {
|
||||||
|
public:
|
||||||
|
OggTobiasAudioCodecTest() {}
|
||||||
|
virtual ~OggTobiasAudioCodecTest() {}
|
||||||
|
virtual bool RecognizesInitialPacket(const ogg_packet & packet) const
|
||||||
|
{
|
||||||
|
return findIdentifier(packet, "audio", 1);
|
||||||
|
}
|
||||||
|
virtual OggCodec * InstantiateCodec() const
|
||||||
|
{
|
||||||
|
return new OggTobiasAudioCodec();
|
||||||
|
}
|
||||||
|
} ogg_tobias_audio_test;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* OggTobiasTextCodec
|
||||||
|
*/
|
||||||
|
|
||||||
|
class OggTobiasTextCodec : public OggTobiasCodec {
|
||||||
|
public:
|
||||||
|
OggTobiasTextCodec();
|
||||||
|
virtual ~OggTobiasTextCodec();
|
||||||
|
|
||||||
|
virtual status_t GetChunk(ogg_stream_state * stream, bool streaming,
|
||||||
|
void **chunkBuffer, int32 *chunkSize,
|
||||||
|
media_header *mediaHeader);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual status_t HeaderToFormat(const tobias_stream_header & header);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
OggTobiasTextCodec::OggTobiasTextCodec()
|
||||||
|
{
|
||||||
|
TRACE("OggTobiasTextCodec::OggTobiasTextCodec\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
OggTobiasTextCodec::~OggTobiasTextCodec()
|
||||||
|
{
|
||||||
|
TRACE("OggTobiasTextCodec::~OggTobiasTextCodec\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ status_t
|
||||||
|
OggTobiasTextCodec::HeaderToFormat(const tobias_stream_header & header)
|
||||||
|
{
|
||||||
|
TRACE("OggTobiasTextCodec::HeaderToFormat\n");
|
||||||
|
|
||||||
|
// get the format for the description
|
||||||
|
media_format_description description = tobias_text_description();
|
||||||
|
BMediaFormats formats;
|
||||||
|
if ((formats.InitCheck() != B_OK) ||
|
||||||
|
(formats.GetFormatFor(description, &fMediaFormat) != B_OK)) {
|
||||||
|
fMediaFormat = tobias_text_encoded_media_format();
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill out format from header packet
|
||||||
|
(void)header;
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ status_t
|
||||||
|
OggTobiasTextCodec::GetChunk(ogg_stream_state * stream, bool streaming,
|
||||||
|
void **chunkBuffer, int32 *chunkSize,
|
||||||
|
media_header *mediaHeader)
|
||||||
|
{
|
||||||
|
if (fCurrentFrame == -1) {
|
||||||
|
fCurrentFrame = fOldFrame + 1;
|
||||||
|
fOldFrame = fCurrentFrame;
|
||||||
|
fOldGranulePos++;
|
||||||
|
}
|
||||||
|
fCurrentTime = (bigtime_t) (1000000LL * fCurrentFrame)
|
||||||
|
/ (long long)fMediaFormat.u.encoded_audio.output.frame_rate;
|
||||||
|
status_t result = OggCodec::GetChunk(stream, streaming, chunkBuffer, chunkSize, mediaHeader);
|
||||||
|
if (result != B_OK) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
// thanks to Marcus:
|
||||||
|
int lenbytes = ((fChunkPacket.packet[0] & 0xc0) >> 6) | ((fChunkPacket.packet[0] & 0x02) << 1);
|
||||||
|
*chunkBuffer = (void*)&(fChunkPacket.packet[lenbytes+1]);
|
||||||
|
*chunkSize = fChunkPacket.bytes-(lenbytes+1);
|
||||||
|
bool keyframe = (fChunkPacket.packet[0] & (1 << 3));
|
||||||
|
mediaHeader->u.encoded_video.field_flags = (keyframe ? B_MEDIA_KEY_FRAME : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// OggTobiasTextCodecTest
|
||||||
|
|
||||||
|
static class OggTobiasTextCodecTest : public OggCodecTest {
|
||||||
|
public:
|
||||||
|
OggTobiasTextCodecTest() {}
|
||||||
|
virtual ~OggTobiasTextCodecTest() {}
|
||||||
|
virtual bool RecognizesInitialPacket(const ogg_packet & packet) const
|
||||||
|
{
|
||||||
|
return findIdentifier(packet,"text",1);
|
||||||
|
}
|
||||||
|
virtual OggCodec * InstantiateCodec() const
|
||||||
|
{
|
||||||
|
return new OggTobiasTextCodec();
|
||||||
|
}
|
||||||
|
} ogg_tobias_text_test;
|
||||||
|
|
||||||
|
|
||||||
@@ -1,287 +0,0 @@
|
|||||||
#include "OggTobiasFormats.h"
|
|
||||||
#include "OggTobiasSeekable.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 (100 ns units)
|
|
||||||
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;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* OggTobiasSeekable implementations
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* static */ bool
|
|
||||||
OggTobiasSeekable::IsValidHeader(const ogg_packet & packet)
|
|
||||||
{
|
|
||||||
return findIdentifier(packet,"video",1)
|
|
||||||
|| findIdentifier(packet,"audio",1)
|
|
||||||
|| findIdentifier(packet,"text",1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
OggTobiasSeekable::OggTobiasSeekable(long serialno)
|
|
||||||
: OggSeekable(serialno)
|
|
||||||
{
|
|
||||||
TRACE("OggTobiasSeekable::OggTobiasSeekable\n");
|
|
||||||
fMicrosecPerFrame = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
OggTobiasSeekable::~OggTobiasSeekable()
|
|
||||||
{
|
|
||||||
TRACE("OggTobiasSeekable::~OggTobiasSeekable\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static status_t
|
|
||||||
get_video_format(tobias_stream_header * header, media_format * format)
|
|
||||||
{
|
|
||||||
TRACE(" get_video_format\n");
|
|
||||||
// get the format for the description
|
|
||||||
media_format_description description = tobias_video_description();
|
|
||||||
description.u.avi.codec = header->subtype[3] << 24 | header->subtype[2] << 16
|
|
||||||
| header->subtype[1] << 8 | header->subtype[0];
|
|
||||||
BMediaFormats formats;
|
|
||||||
status_t result = formats.InitCheck();
|
|
||||||
if (result == B_OK) {
|
|
||||||
result = formats.GetFormatFor(description, format);
|
|
||||||
}
|
|
||||||
if (result != B_OK) {
|
|
||||||
*format = tobias_video_encoded_media_format();
|
|
||||||
// ignore error, allow user to use ReadChunk interface
|
|
||||||
}
|
|
||||||
|
|
||||||
// fill out format from header packet
|
|
||||||
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.field_rate = 10000000.0 / header->time_unit;
|
|
||||||
format->u.encoded_video.output.interlace = 1;
|
|
||||||
format->u.encoded_video.output.first_active = 0;
|
|
||||||
format->u.encoded_video.output.last_active = header->video.height - 1;
|
|
||||||
format->u.encoded_video.output.orientation = B_VIDEO_TOP_LEFT_RIGHT;
|
|
||||||
format->u.encoded_video.output.pixel_width_aspect = 1;
|
|
||||||
format->u.encoded_video.output.pixel_height_aspect = 1;
|
|
||||||
format->u.encoded_video.output.display.line_width = header->video.width;
|
|
||||||
format->u.encoded_video.output.display.line_count = header->video.height;
|
|
||||||
format->u.encoded_video.output.display.bytes_per_row = 0;
|
|
||||||
format->u.encoded_video.output.display.pixel_offset = 0;
|
|
||||||
format->u.encoded_video.output.display.line_offset = 0;
|
|
||||||
format->u.encoded_video.output.display.flags = 0;
|
|
||||||
|
|
||||||
// TODO: wring more info out of the headers
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static status_t
|
|
||||||
get_audio_format(tobias_stream_header * header, media_format * format)
|
|
||||||
{
|
|
||||||
TRACE(" get_audio_format\n");
|
|
||||||
debugger("get_audio_format");
|
|
||||||
return B_UNSUPPORTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static status_t
|
|
||||||
get_text_format(tobias_stream_header * header, media_format * format)
|
|
||||||
{
|
|
||||||
TRACE(" get_text_format\n");
|
|
||||||
// get the format for the description
|
|
||||||
media_format_description description = tobias_text_description();
|
|
||||||
BMediaFormats formats;
|
|
||||||
status_t result = formats.InitCheck();
|
|
||||||
if (result == B_OK) {
|
|
||||||
result = formats.GetFormatFor(description, format);
|
|
||||||
}
|
|
||||||
if (result != B_OK) {
|
|
||||||
*format = tobias_text_encoded_media_format();
|
|
||||||
// ignore error, allow user to use ReadChunk interface
|
|
||||||
}
|
|
||||||
|
|
||||||
// fill out format from header packet
|
|
||||||
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
status_t
|
|
||||||
OggTobiasSeekable::GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
|
||||||
media_format *format)
|
|
||||||
{
|
|
||||||
TRACE("OggTobiasSeekable::GetStreamInfo\n");
|
|
||||||
status_t result = B_OK;
|
|
||||||
ogg_packet packet;
|
|
||||||
|
|
||||||
// get header packet
|
|
||||||
if (GetHeaderPackets().size() < 1) {
|
|
||||||
result = GetPacket(&packet);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
SaveHeaderPacket(packet);
|
|
||||||
}
|
|
||||||
packet = GetHeaderPackets()[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;
|
|
||||||
|
|
||||||
if (strcmp(header->streamtype, "video") == 0) {
|
|
||||||
result = get_video_format(header, format);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
} else if (strcmp(header->streamtype, "audio") == 0) {
|
|
||||||
result = get_audio_format(header, format);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
} else if (strcmp(header->streamtype, "text") == 0) {
|
|
||||||
result = get_text_format(header, format);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
*frameCount = 0;
|
|
||||||
// unknown streamtype
|
|
||||||
return B_BAD_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get comment packet
|
|
||||||
if (GetHeaderPackets().size() < 2) {
|
|
||||||
result = GetPacket(&packet);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
SaveHeaderPacket(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
format->SetMetaData((void*)&GetHeaderPackets(),sizeof(GetHeaderPackets()));
|
|
||||||
fMediaFormat = *format;
|
|
||||||
fMicrosecPerFrame = header->time_unit / 10.0;
|
|
||||||
fFrameRate = 1000000.0 / fMicrosecPerFrame;
|
|
||||||
|
|
||||||
// TODO: count the frames in the first page.. somehow.. :-/
|
|
||||||
int64 frames = 0;
|
|
||||||
|
|
||||||
ogg_page page;
|
|
||||||
// read the first page
|
|
||||||
result = ReadPage(&page);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
int64 fFirstGranulepos = ogg_page_granulepos(&page);
|
|
||||||
TRACE("OggVorbisSeekable::GetStreamInfo: first granulepos: %lld\n", fFirstGranulepos);
|
|
||||||
// read our last page
|
|
||||||
off_t last = inherited::Seek(GetLastPagePosition(), SEEK_SET);
|
|
||||||
if (last < 0) {
|
|
||||||
return last;
|
|
||||||
}
|
|
||||||
result = ReadPage(&page);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
int64 last_granulepos = ogg_page_granulepos(&page);
|
|
||||||
|
|
||||||
// seek back to the start
|
|
||||||
int64 frame = 0;
|
|
||||||
bigtime_t time = 0;
|
|
||||||
result = Seek(B_MEDIA_SEEK_TO_TIME, &frame, &time);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// compute frame count and duration from sample count
|
|
||||||
frames = last_granulepos - fFirstGranulepos;
|
|
||||||
|
|
||||||
*frameCount = frames;
|
|
||||||
*duration = (1000000LL * frames) / (long long)fFrameRate;
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
status_t
|
|
||||||
OggTobiasSeekable::GetNextChunk(void **chunkBuffer, int32 *chunkSize,
|
|
||||||
media_header *mediaHeader)
|
|
||||||
{
|
|
||||||
status_t result = inherited::GetNextChunk(chunkBuffer, chunkSize, mediaHeader);
|
|
||||||
if (result != B_OK) {
|
|
||||||
TRACE("OggTobiasSeekable::GetNextChunk failed: GetNextChunk = %s\n", strerror(result));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
*chunkSize = ((ogg_packet*)*chunkBuffer)->bytes;
|
|
||||||
*chunkBuffer = ((ogg_packet*)*chunkBuffer)->packet;
|
|
||||||
bool keyframe = ((uint*)chunkBuffer)[0] & (1 << 3); // ??
|
|
||||||
if (fMediaFormat.type == B_MEDIA_ENCODED_VIDEO) {
|
|
||||||
mediaHeader->type = fMediaFormat.type;
|
|
||||||
mediaHeader->u.encoded_video.field_flags = (keyframe ? B_MEDIA_KEY_FRAME : 0);
|
|
||||||
mediaHeader->u.encoded_video.first_active_line
|
|
||||||
= fMediaFormat.u.encoded_video.output.first_active;
|
|
||||||
mediaHeader->u.encoded_video.line_count
|
|
||||||
= fMediaFormat.u.encoded_video.output.display.line_count;
|
|
||||||
}
|
|
||||||
if (mediaHeader->start_time < 0) {
|
|
||||||
fCurrentFrame++;
|
|
||||||
fCurrentTime = (bigtime_t)((fCurrentFrame * 1000000LL) / fFrameRate);
|
|
||||||
mediaHeader->start_time = fCurrentTime;
|
|
||||||
}
|
|
||||||
// fprintf(stderr, "current frame = %lld, time = %lld\n", fCurrentFrame, fCurrentTime);
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
#ifndef _OGG_TOBIAS_SEEKABLE_H
|
|
||||||
#define _OGG_TOBIAS_SEEKABLE_H
|
|
||||||
|
|
||||||
#include "OggSeekable.h"
|
|
||||||
|
|
||||||
namespace BPrivate { namespace media {
|
|
||||||
|
|
||||||
class OggTobiasSeekable : public OggSeekable {
|
|
||||||
private:
|
|
||||||
typedef OggSeekable inherited;
|
|
||||||
public:
|
|
||||||
static bool IsValidHeader(const ogg_packet & packet);
|
|
||||||
public:
|
|
||||||
OggTobiasSeekable(long serialno);
|
|
||||||
virtual ~OggTobiasSeekable();
|
|
||||||
|
|
||||||
virtual status_t GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
|
||||||
media_format *format);
|
|
||||||
virtual status_t GetNextChunk(void **chunkBuffer, int32 *chunkSize,
|
|
||||||
media_header *mediaHeader);
|
|
||||||
|
|
||||||
private:
|
|
||||||
media_format fMediaFormat;
|
|
||||||
double fMicrosecPerFrame;
|
|
||||||
};
|
|
||||||
|
|
||||||
} } // namespace BPrivate::media
|
|
||||||
|
|
||||||
using namespace BPrivate::media;
|
|
||||||
|
|
||||||
#endif // _OGG_TOBIAS_SEEKABLE_H
|
|
||||||
@@ -1,266 +0,0 @@
|
|||||||
#include "OggTobiasFormats.h"
|
|
||||||
#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 (100 ns units)
|
|
||||||
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)
|
|
||||||
|| findIdentifier(packet,"audio",1)
|
|
||||||
|| findIdentifier(packet,"text",1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
OggTobiasStream::OggTobiasStream(long serialno)
|
|
||||||
: OggStream(serialno)
|
|
||||||
{
|
|
||||||
TRACE("OggTobiasStream::OggTobiasStream\n");
|
|
||||||
fMicrosecPerFrame = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
OggTobiasStream::~OggTobiasStream()
|
|
||||||
{
|
|
||||||
TRACE("OggTobiasStream::~OggTobiasStream\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static status_t
|
|
||||||
get_video_format(tobias_stream_header * header, media_format * format)
|
|
||||||
{
|
|
||||||
TRACE(" get_video_format\n");
|
|
||||||
// get the format for the description
|
|
||||||
media_format_description description = tobias_video_description();
|
|
||||||
description.u.avi.codec = header->subtype[3] << 24 | header->subtype[2] << 16
|
|
||||||
| header->subtype[1] << 8 | header->subtype[0];
|
|
||||||
BMediaFormats formats;
|
|
||||||
status_t result = formats.InitCheck();
|
|
||||||
if (result == B_OK) {
|
|
||||||
result = formats.GetFormatFor(description, format);
|
|
||||||
}
|
|
||||||
if (result != B_OK) {
|
|
||||||
*format = tobias_video_encoded_media_format();
|
|
||||||
// ignore error, allow user to use ReadChunk interface
|
|
||||||
}
|
|
||||||
|
|
||||||
// fill out format from header packet
|
|
||||||
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.field_rate = 10000000.0 / header->time_unit;
|
|
||||||
format->u.encoded_video.output.interlace = 1;
|
|
||||||
format->u.encoded_video.output.first_active = 0;
|
|
||||||
format->u.encoded_video.output.last_active = header->video.height - 1;
|
|
||||||
format->u.encoded_video.output.orientation = B_VIDEO_TOP_LEFT_RIGHT;
|
|
||||||
format->u.encoded_video.output.pixel_width_aspect = 1;
|
|
||||||
format->u.encoded_video.output.pixel_height_aspect = 1;
|
|
||||||
format->u.encoded_video.output.display.line_width = header->video.width;
|
|
||||||
format->u.encoded_video.output.display.line_count = header->video.height;
|
|
||||||
format->u.encoded_video.output.display.bytes_per_row = 0;
|
|
||||||
format->u.encoded_video.output.display.pixel_offset = 0;
|
|
||||||
format->u.encoded_video.output.display.line_offset = 0;
|
|
||||||
format->u.encoded_video.output.display.flags = 0;
|
|
||||||
|
|
||||||
// TODO: wring more info out of the headers
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static status_t
|
|
||||||
get_audio_format(tobias_stream_header * header, media_format * format)
|
|
||||||
{
|
|
||||||
TRACE(" get_audio_format\n");
|
|
||||||
debugger("get_audio_format");
|
|
||||||
return B_UNSUPPORTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static status_t
|
|
||||||
get_text_format(tobias_stream_header * header, media_format * format)
|
|
||||||
{
|
|
||||||
TRACE(" get_text_format\n");
|
|
||||||
// get the format for the description
|
|
||||||
media_format_description description = tobias_text_description();
|
|
||||||
BMediaFormats formats;
|
|
||||||
status_t result = formats.InitCheck();
|
|
||||||
if (result == B_OK) {
|
|
||||||
result = formats.GetFormatFor(description, format);
|
|
||||||
}
|
|
||||||
if (result != B_OK) {
|
|
||||||
*format = tobias_text_encoded_media_format();
|
|
||||||
// ignore error, allow user to use ReadChunk interface
|
|
||||||
}
|
|
||||||
|
|
||||||
// fill out format from header packet
|
|
||||||
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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 (GetHeaderPackets().size() < 1) {
|
|
||||||
result = GetPacket(&packet);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
SaveHeaderPacket(packet);
|
|
||||||
}
|
|
||||||
packet = GetHeaderPackets()[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;
|
|
||||||
|
|
||||||
if (strcmp(header->streamtype, "video") == 0) {
|
|
||||||
result = get_video_format(header, format);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
*frameCount = (bigtime_t)(3 * 3600 * format->u.encoded_video.output.field_rate);
|
|
||||||
} else if (strcmp(header->streamtype, "audio") == 0) {
|
|
||||||
result = get_audio_format(header, format);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
*frameCount = 2000000;
|
|
||||||
} else if (strcmp(header->streamtype, "text") == 0) {
|
|
||||||
result = get_text_format(header, format);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
*frameCount = 2000000;
|
|
||||||
} else {
|
|
||||||
*frameCount = 0;
|
|
||||||
// unknown streamtype
|
|
||||||
return B_BAD_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get comment packet
|
|
||||||
if (GetHeaderPackets().size() < 2) {
|
|
||||||
result = GetPacket(&packet);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
SaveHeaderPacket(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
format->SetMetaData((void*)&GetHeaderPackets(),sizeof(GetHeaderPackets()));
|
|
||||||
fMediaFormat = *format;
|
|
||||||
fMicrosecPerFrame = header->time_unit / 10.0;
|
|
||||||
*duration = (bigtime_t)(*frameCount * fMicrosecPerFrame);
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
status_t
|
|
||||||
OggTobiasStream::GetNextChunk(void **chunkBuffer, int32 *chunkSize,
|
|
||||||
media_header *mediaHeader)
|
|
||||||
{
|
|
||||||
status_t result = GetPacket(&fChunkPacket);
|
|
||||||
if (result != B_OK) {
|
|
||||||
TRACE("OggTobiasStream::GetNextChunk failed: GetPacket = %s\n", strerror(result));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
*chunkBuffer = fChunkPacket.packet;
|
|
||||||
*chunkSize = fChunkPacket.bytes;
|
|
||||||
bool keyframe = fChunkPacket.packet[0] & (1 << 3); // ??
|
|
||||||
if (fMediaFormat.type == B_MEDIA_ENCODED_VIDEO) {
|
|
||||||
mediaHeader->type = fMediaFormat.type;
|
|
||||||
mediaHeader->start_time = fCurrentTime;
|
|
||||||
mediaHeader->u.encoded_video.field_flags = (keyframe ? B_MEDIA_KEY_FRAME : 0);
|
|
||||||
mediaHeader->u.encoded_video.first_active_line
|
|
||||||
= fMediaFormat.u.encoded_video.output.first_active;
|
|
||||||
mediaHeader->u.encoded_video.line_count
|
|
||||||
= fMediaFormat.u.encoded_video.output.display.line_count;
|
|
||||||
}
|
|
||||||
fCurrentFrame++;
|
|
||||||
fCurrentTime = (bigtime_t)(fCurrentFrame * fMicrosecPerFrame);
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
status_t
|
|
||||||
OggTobiasStream::AddPage(off_t position, const ogg_page & page)
|
|
||||||
{
|
|
||||||
status_t status = OggStream::AddPage(position, page);
|
|
||||||
if (fMediaFormat.type == B_MEDIA_HTML) {
|
|
||||||
ogg_packet packet;
|
|
||||||
GetPacket(&packet);
|
|
||||||
if (packet.bytes > 4) {
|
|
||||||
fprintf(stderr, "%s\n", &(packet.packet[3]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
#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);
|
|
||||||
virtual status_t GetNextChunk(void **chunkBuffer, int32 *chunkSize,
|
|
||||||
media_header *mediaHeader);
|
|
||||||
|
|
||||||
// reader push input function
|
|
||||||
virtual status_t AddPage(off_t position, const ogg_page & page);
|
|
||||||
|
|
||||||
private:
|
|
||||||
media_format fMediaFormat;
|
|
||||||
double fMicrosecPerFrame;
|
|
||||||
};
|
|
||||||
|
|
||||||
} } // namespace BPrivate::media
|
|
||||||
|
|
||||||
using namespace BPrivate::media;
|
|
||||||
|
|
||||||
#endif // _OGG_TOBIAS_STREAM_H
|
|
||||||
@@ -0,0 +1,262 @@
|
|||||||
|
#include "OggVorbisFormats.h"
|
||||||
|
#include "OggCodecs.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);
|
||||||
|
long blocksizes0 = oggpack_read(opb, 4);
|
||||||
|
long blocksizes1 = oggpack_read(opb, 4);
|
||||||
|
if (vi->rate < 1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (vi->channels < 1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (blocksizes0 < 8) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (blocksizes1 < blocksizes0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (oggpack_read(opb, 1) != 1) {
|
||||||
|
return -1;
|
||||||
|
} /* EOP check */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* OggVorbisCodec
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
class OggVorbisCodec : public OggCodec {
|
||||||
|
public:
|
||||||
|
OggVorbisCodec();
|
||||||
|
virtual ~OggVorbisCodec();
|
||||||
|
|
||||||
|
virtual bool IsHeaderPacket(const ogg_packet & packet, uint packetno) const;
|
||||||
|
virtual status_t HandlePacket(const ogg_packet & packet);
|
||||||
|
|
||||||
|
virtual status_t GetChunk(ogg_stream_state * stream, bool streaming,
|
||||||
|
void **chunkBuffer, int32 *chunkSize,
|
||||||
|
media_header *mediaHeader);
|
||||||
|
private:
|
||||||
|
std::vector<ogg_packet> fMetaDataPackets;
|
||||||
|
unsigned char * fHeaderPacketData;
|
||||||
|
unsigned char * fCommentPacketData;
|
||||||
|
unsigned char * fCodebookPacketData;
|
||||||
|
unsigned int fPacketCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
OggVorbisCodec::OggVorbisCodec()
|
||||||
|
{
|
||||||
|
TRACE("OggVorbisCodec::OggVorbisCodec\n");
|
||||||
|
fHeaderPacketData = NULL;
|
||||||
|
fCommentPacketData = NULL;
|
||||||
|
fCodebookPacketData = NULL;
|
||||||
|
fPacketCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
OggVorbisCodec::~OggVorbisCodec()
|
||||||
|
{
|
||||||
|
delete fHeaderPacketData;
|
||||||
|
delete fCommentPacketData;
|
||||||
|
delete fCodebookPacketData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ bool
|
||||||
|
OggVorbisCodec::IsHeaderPacket(const ogg_packet & packet, uint packetno) const
|
||||||
|
{
|
||||||
|
oggpack_buffer opb;
|
||||||
|
oggpack_readinit(&opb, packet.packet, packet.bytes);
|
||||||
|
uint packtype = oggpack_read(&opb, 8);
|
||||||
|
return (packetno == 0 && packtype == 0x01)
|
||||||
|
|| (packetno == 1 && packtype == 0x03)
|
||||||
|
|| (packetno == 2 && packtype == 0x05);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ status_t
|
||||||
|
OggVorbisCodec::HandlePacket(const ogg_packet & packet)
|
||||||
|
{
|
||||||
|
TRACE("OggVorbisCodec::HandlePacket\n");
|
||||||
|
if (!IsHeaderPacket(packet, fPacketCount)) {
|
||||||
|
return B_BAD_VALUE;
|
||||||
|
}
|
||||||
|
switch (fPacketCount) {
|
||||||
|
case 0: {
|
||||||
|
// header packet
|
||||||
|
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);
|
||||||
|
// discard packet type (already validated in IsHeaderPacket)
|
||||||
|
oggpack_read(&opb, 8);
|
||||||
|
// 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 = vorbis_description();
|
||||||
|
BMediaFormats formats;
|
||||||
|
if ((formats.InitCheck() != B_OK) ||
|
||||||
|
(formats.GetFormatFor(description, &fMediaFormat) != B_OK)) {
|
||||||
|
fMediaFormat = vorbis_encoded_media_format();
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill out format from header packet
|
||||||
|
if (info.bitrate_nominal > 0) {
|
||||||
|
fMediaFormat.u.encoded_audio.bit_rate = info.bitrate_nominal;
|
||||||
|
} else if (info.bitrate_upper > 0) {
|
||||||
|
fMediaFormat.u.encoded_audio.bit_rate = info.bitrate_upper;
|
||||||
|
} else if (info.bitrate_lower > 0) {
|
||||||
|
fMediaFormat.u.encoded_audio.bit_rate = info.bitrate_lower;
|
||||||
|
}
|
||||||
|
if (info.channels == 1) {
|
||||||
|
fMediaFormat.u.encoded_audio.multi_info.channel_mask = B_CHANNEL_LEFT;
|
||||||
|
} else {
|
||||||
|
fMediaFormat.u.encoded_audio.multi_info.channel_mask = B_CHANNEL_LEFT | B_CHANNEL_RIGHT;
|
||||||
|
}
|
||||||
|
fMediaFormat.u.encoded_audio.output.frame_rate = (float)info.rate;
|
||||||
|
fMediaFormat.u.encoded_audio.output.channel_count = info.channels;
|
||||||
|
fMediaFormat.u.encoded_audio.output.buffer_size
|
||||||
|
= AudioBufferSize(&fMediaFormat.u.encoded_audio.output);
|
||||||
|
|
||||||
|
fHeaderPacketData = new unsigned char[packet.bytes];
|
||||||
|
memcpy(fHeaderPacketData, packet.packet, packet.bytes);
|
||||||
|
fMetaDataPackets.push_back(packet);
|
||||||
|
fMetaDataPackets[fPacketCount].packet = fHeaderPacketData;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
// comment packet
|
||||||
|
fCommentPacketData = new unsigned char[packet.bytes];
|
||||||
|
memcpy(fCommentPacketData, packet.packet, packet.bytes);
|
||||||
|
fMetaDataPackets.push_back(packet);
|
||||||
|
fMetaDataPackets[fPacketCount].packet = fCommentPacketData;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2: {
|
||||||
|
// codebook packet
|
||||||
|
fCodebookPacketData = new unsigned char[packet.bytes];
|
||||||
|
memcpy(fCodebookPacketData, packet.packet, packet.bytes);
|
||||||
|
fMetaDataPackets.push_back(packet);
|
||||||
|
fMetaDataPackets[fPacketCount].packet = fCodebookPacketData;
|
||||||
|
|
||||||
|
fInitCheck = B_OK;
|
||||||
|
fMediaFormat.SetMetaData(&fMetaDataPackets,sizeof(fMetaDataPackets));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// huh?
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fPacketCount++;
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* virtual */ status_t
|
||||||
|
OggVorbisCodec::GetChunk(ogg_stream_state * stream, bool streaming,
|
||||||
|
void **chunkBuffer, int32 *chunkSize,
|
||||||
|
media_header *mediaHeader)
|
||||||
|
{
|
||||||
|
if (fCurrentFrame == -1) {
|
||||||
|
fCurrentTime = -1;
|
||||||
|
} else {
|
||||||
|
fCurrentTime = (1000000LL * fCurrentFrame)
|
||||||
|
/ (long long)fMediaFormat.u.encoded_audio.output.frame_rate;
|
||||||
|
}
|
||||||
|
return OggCodec::GetChunk(stream, streaming, chunkBuffer, chunkSize, mediaHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* OggVorbisCodecTest
|
||||||
|
*/
|
||||||
|
|
||||||
|
static class OggVorbisCodecTest : public OggCodecTest {
|
||||||
|
public:
|
||||||
|
OggVorbisCodecTest() {}
|
||||||
|
virtual ~OggVorbisCodecTest() {}
|
||||||
|
virtual bool RecognizesInitialPacket(const ogg_packet & packet) const
|
||||||
|
{
|
||||||
|
return findIdentifier(packet, "vorbis", 1);
|
||||||
|
}
|
||||||
|
virtual OggCodec * InstantiateCodec() const
|
||||||
|
{
|
||||||
|
return new OggVorbisCodec();
|
||||||
|
}
|
||||||
|
} ogg_vorbis_codec_test;
|
||||||
@@ -1,230 +0,0 @@
|
|||||||
#include "OggVorbisFormats.h"
|
|
||||||
#include "OggVorbisSeekable.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);
|
|
||||||
long blocksizes0 = oggpack_read(opb, 4);
|
|
||||||
long blocksizes1 = oggpack_read(opb, 4);
|
|
||||||
if (vi->rate < 1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (vi->channels < 1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (blocksizes0 < 8) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (blocksizes1 < blocksizes0) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (oggpack_read(opb, 1) != 1) {
|
|
||||||
return -1;
|
|
||||||
} /* EOP check */
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* OggVorbisSeekable implementations
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* static */ bool
|
|
||||||
OggVorbisSeekable::IsValidHeader(const ogg_packet & packet)
|
|
||||||
{
|
|
||||||
return findIdentifier(packet,"vorbis",1);
|
|
||||||
}
|
|
||||||
|
|
||||||
OggVorbisSeekable::OggVorbisSeekable(long serialno)
|
|
||||||
: OggSeekable(serialno)
|
|
||||||
{
|
|
||||||
TRACE("OggVorbisSeekable::OggVorbisSeekable\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
OggVorbisSeekable::~OggVorbisSeekable()
|
|
||||||
{
|
|
||||||
TRACE("OggVorbisSeekable::~OggVorbisSeekable\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
status_t
|
|
||||||
OggVorbisSeekable::GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
|
||||||
media_format *format)
|
|
||||||
{
|
|
||||||
TRACE("OggVorbisSeekable::GetStreamInfo\n");
|
|
||||||
status_t result = B_OK;
|
|
||||||
ogg_packet packet;
|
|
||||||
|
|
||||||
// get header packet
|
|
||||||
if (GetHeaderPackets().size() < 1) {
|
|
||||||
result = GetPacket(&packet);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
SaveHeaderPacket(packet);
|
|
||||||
}
|
|
||||||
packet = GetHeaderPackets()[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 = vorbis_description();
|
|
||||||
BMediaFormats formats;
|
|
||||||
result = formats.InitCheck();
|
|
||||||
if (result == B_OK) {
|
|
||||||
result = formats.GetFormatFor(description, format);
|
|
||||||
}
|
|
||||||
if (result != B_OK) {
|
|
||||||
*format = vorbis_encoded_media_format();
|
|
||||||
// ignore error, allow user to use ReadChunk interface
|
|
||||||
}
|
|
||||||
|
|
||||||
// fill out format from header packet
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
fFrameRate = format->u.encoded_audio.output.frame_rate = (float)info.rate;
|
|
||||||
format->u.encoded_audio.output.channel_count = info.channels;
|
|
||||||
format->u.encoded_audio.output.buffer_size
|
|
||||||
= AudioBufferSize(&format->u.encoded_audio.output);
|
|
||||||
|
|
||||||
// get comment packet
|
|
||||||
if (GetHeaderPackets().size() < 2) {
|
|
||||||
result = GetPacket(&packet);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
SaveHeaderPacket(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get codebook packet
|
|
||||||
if (GetHeaderPackets().size() < 3) {
|
|
||||||
result = GetPacket(&packet);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
SaveHeaderPacket(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
format->SetMetaData((void*)&GetHeaderPackets(),sizeof(GetHeaderPackets()));
|
|
||||||
|
|
||||||
// TODO: count the frames in the first page.. somehow.. :-/
|
|
||||||
int64 frames = 0;
|
|
||||||
|
|
||||||
ogg_page page;
|
|
||||||
// read the first page
|
|
||||||
result = ReadPage(&page);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
int64 fFirstGranulepos = ogg_page_granulepos(&page);
|
|
||||||
TRACE("OggVorbisSeekable::GetStreamInfo: first granulepos: %lld\n", fFirstGranulepos);
|
|
||||||
// read our last page
|
|
||||||
off_t last = inherited::Seek(GetLastPagePosition(), SEEK_SET);
|
|
||||||
if (last < 0) {
|
|
||||||
return last;
|
|
||||||
}
|
|
||||||
result = ReadPage(&page);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
int64 last_granulepos = ogg_page_granulepos(&page);
|
|
||||||
|
|
||||||
// seek back to the start
|
|
||||||
int64 frame = 0;
|
|
||||||
bigtime_t time = 0;
|
|
||||||
result = Seek(B_MEDIA_SEEK_TO_TIME, &frame, &time);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// compute frame count and duration from sample count
|
|
||||||
frames = last_granulepos - fFirstGranulepos;
|
|
||||||
|
|
||||||
*frameCount = frames;
|
|
||||||
*duration = (1000000LL * frames) / (long long)fFrameRate;
|
|
||||||
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
#ifndef _OGG_VORBIS_SEEKABLE_H
|
|
||||||
#define _OGG_VORBIS_SEEKABLE_H
|
|
||||||
|
|
||||||
#include "OggSeekable.h"
|
|
||||||
|
|
||||||
namespace BPrivate { namespace media {
|
|
||||||
|
|
||||||
class OggVorbisSeekable : public OggSeekable {
|
|
||||||
private:
|
|
||||||
typedef OggSeekable inherited;
|
|
||||||
public:
|
|
||||||
static bool IsValidHeader(const ogg_packet & packet);
|
|
||||||
public:
|
|
||||||
OggVorbisSeekable(long serialno);
|
|
||||||
virtual ~OggVorbisSeekable();
|
|
||||||
|
|
||||||
virtual status_t GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
|
||||||
media_format *format);
|
|
||||||
};
|
|
||||||
|
|
||||||
} } // namespace BPrivate::media
|
|
||||||
|
|
||||||
using namespace BPrivate::media;
|
|
||||||
|
|
||||||
#endif // _OGG_VORBIS_SEEKABLE_H
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
#include "OggVorbisFormats.h"
|
|
||||||
#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);
|
|
||||||
long blocksizes0 = oggpack_read(opb, 4);
|
|
||||||
long blocksizes1 = oggpack_read(opb, 4);
|
|
||||||
if (vi->rate < 1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (vi->channels < 1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (blocksizes0 < 8) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (blocksizes1 < blocksizes0) {
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
TRACE("OggVorbisStream::OggVorbisStream\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
OggVorbisStream::~OggVorbisStream()
|
|
||||||
{
|
|
||||||
TRACE("OggVorbisStream::~OggVorbisStream\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (GetHeaderPackets().size() < 1) {
|
|
||||||
result = GetPacket(&packet);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
SaveHeaderPacket(packet);
|
|
||||||
}
|
|
||||||
packet = GetHeaderPackets()[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 = vorbis_description();
|
|
||||||
BMediaFormats formats;
|
|
||||||
result = formats.InitCheck();
|
|
||||||
if (result == B_OK) {
|
|
||||||
result = formats.GetFormatFor(description, format);
|
|
||||||
}
|
|
||||||
if (result != B_OK) {
|
|
||||||
*format = vorbis_encoded_media_format();
|
|
||||||
// ignore error, allow user to use ReadChunk interface
|
|
||||||
}
|
|
||||||
|
|
||||||
// fill out format from header packet
|
|
||||||
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.output.frame_rate = (float)info.rate;
|
|
||||||
format->u.encoded_audio.output.channel_count = info.channels;
|
|
||||||
format->u.encoded_audio.output.buffer_size
|
|
||||||
= AudioBufferSize(&format->u.encoded_audio.output);
|
|
||||||
|
|
||||||
// get comment packet
|
|
||||||
if (GetHeaderPackets().size() < 2) {
|
|
||||||
result = GetPacket(&packet);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
SaveHeaderPacket(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get codebook packet
|
|
||||||
if (GetHeaderPackets().size() < 3) {
|
|
||||||
result = GetPacket(&packet);
|
|
||||||
if (result != B_OK) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
SaveHeaderPacket(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
format->SetMetaData((void*)&GetHeaderPackets(),sizeof(GetHeaderPackets()));
|
|
||||||
|
|
||||||
// compute frame count and duration from sample count
|
|
||||||
*duration = 5 * 60 * 1000000;
|
|
||||||
*frameCount = *duration * (long long)format->u.encoded_audio.output.frame_rate;
|
|
||||||
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
#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