new separation of streams from seekables - first view of ogm video!
git-svn-id: file:///srv/svn/repos/haiku/trunk/current@6523 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -5,13 +5,15 @@ UsePrivateHeaders media ;
|
||||
SubDirHdrs [ FDirName $(SUBDIR) libogg ] ;
|
||||
|
||||
Addon ogg : media plugins :
|
||||
OggFrameInfo.cpp
|
||||
OggReaderPlugin.cpp
|
||||
OggTrack.cpp
|
||||
OggStream.cpp
|
||||
OggSeekable.cpp
|
||||
OggSpeexStream.cpp
|
||||
OggTheoraStream.cpp
|
||||
OggTobiasStream.cpp
|
||||
OggVorbisStream.cpp
|
||||
OggVorbisSeekable.cpp
|
||||
: false : libogg.a
|
||||
;
|
||||
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
|
||||
#include "OggFrameInfo.h"
|
||||
|
||||
OggFrameInfo::OggFrameInfo(uint page, uint packetonpage, uint packet)
|
||||
{
|
||||
this->page = page;
|
||||
this->packetonpage = packetonpage;
|
||||
this->packet = packet;
|
||||
}
|
||||
|
||||
void
|
||||
OggFrameInfo::SetNextIsNewPage(bool newpage)
|
||||
{
|
||||
this->newpage = newpage;
|
||||
}
|
||||
|
||||
/* virtual */
|
||||
OggFrameInfo::~OggFrameInfo()
|
||||
{
|
||||
}
|
||||
|
||||
uint
|
||||
OggFrameInfo::GetPage() const
|
||||
{
|
||||
return page;
|
||||
}
|
||||
|
||||
uint
|
||||
OggFrameInfo::GetPacketOnPage() const
|
||||
{
|
||||
return packetonpage;
|
||||
}
|
||||
|
||||
uint
|
||||
OggFrameInfo::GetPacket() const
|
||||
{
|
||||
return packet;
|
||||
}
|
||||
|
||||
uint
|
||||
OggFrameInfo::GetNextPage() const
|
||||
{
|
||||
return (newpage ? page + 1 : page);
|
||||
}
|
||||
|
||||
uint
|
||||
OggFrameInfo::GetNextPacketOnPage() const
|
||||
{
|
||||
return (newpage ? 0 : packetonpage + 1);
|
||||
}
|
||||
|
||||
uint
|
||||
OggFrameInfo::GetNextPacket() const
|
||||
{
|
||||
return packet + 1;
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
#ifndef _OGG_FRAME_INFO_H
|
||||
#define _OGG_FRAME_INFO_H
|
||||
|
||||
#include <SupportDefs.h>
|
||||
|
||||
class OggFrameInfo {
|
||||
public:
|
||||
OggFrameInfo(uint page, uint packetonpage, uint packet);
|
||||
virtual ~OggFrameInfo();
|
||||
void SetNextIsNewPage(bool newpage = true);
|
||||
|
||||
uint GetPage() const;
|
||||
uint GetPacketOnPage() const;
|
||||
uint GetPacket() const;
|
||||
|
||||
uint GetNextPage() const;
|
||||
uint GetNextPacketOnPage() const;
|
||||
uint GetNextPacket() const;
|
||||
private:
|
||||
uint page; // the page the frame started on
|
||||
uint16 packetonpage; // of all the packets on that page which packet is this
|
||||
uint packet; // the packet the frame started on
|
||||
bool newpage;
|
||||
};
|
||||
|
||||
#endif _OGG_FRAME_INFO_H
|
||||
@@ -2,12 +2,15 @@
|
||||
#include <string.h>
|
||||
#include <malloc.h>
|
||||
#include <stdint.h>
|
||||
#include <DataIO.h>
|
||||
#include <Autolock.h>
|
||||
#include <ByteOrder.h>
|
||||
#include <DataIO.h>
|
||||
#include <File.h>
|
||||
#include <InterfaceDefs.h>
|
||||
#include <MediaFormats.h>
|
||||
#include "OggReaderPlugin.h"
|
||||
#include "OggStream.h"
|
||||
#include "OggSeekable.h"
|
||||
|
||||
#define TRACE_THIS 1
|
||||
#if TRACE_THIS
|
||||
@@ -20,20 +23,22 @@ OggReader::OggReader()
|
||||
{
|
||||
TRACE("OggReader::OggReader\n");
|
||||
ogg_sync_init(&fSync);
|
||||
fSeekable = NULL;
|
||||
fFile = NULL;
|
||||
fPosition = -1;
|
||||
}
|
||||
|
||||
|
||||
OggReader::~OggReader()
|
||||
{
|
||||
TRACE("OggReader::~OggReader\n");
|
||||
serialno_OggStream_map::iterator i = fStreams.begin();
|
||||
while (i != fStreams.end()) {
|
||||
serialno_OggStream_map::iterator j = i;
|
||||
serialno_OggTrack_map::iterator i = fTracks.begin();
|
||||
while (i != fTracks.end()) {
|
||||
serialno_OggTrack_map::iterator j = i;
|
||||
i++;
|
||||
delete j->second;
|
||||
}
|
||||
ogg_sync_clear(&fSync);
|
||||
fNextPosition = -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,13 +49,16 @@ OggReader::Copyright()
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
OggReader::GetPage(ogg_page * page, int read_size, bool short_page)
|
||||
// the main page reading hook (stream friendly)
|
||||
ssize_t
|
||||
OggReader::ReadPage(bool first_page)
|
||||
{
|
||||
// TRACE("OggReader::GetPage\n");
|
||||
// TRACE("OggReader::ReadPage\n");
|
||||
int read_size = (first_page ? 4096 : 4*B_PAGE_SIZE);
|
||||
BAutolock autolock(fSyncLock);
|
||||
ogg_page page;
|
||||
retry:
|
||||
off_t position = fNextPosition;
|
||||
int result = ogg_sync_pageout(&fSync,page); // first read leftovers
|
||||
int result = ogg_sync_pageout(&fSync, &page); // first read leftovers
|
||||
while (result == 0) {
|
||||
char * buffer = ogg_sync_buffer(&fSync, read_size);
|
||||
ssize_t bytes = Source()->Read(buffer, read_size);
|
||||
@@ -66,42 +74,46 @@ retry:
|
||||
TRACE("OggReader::GetPage: ogg_sync_wrote failed?: error\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
result = ogg_sync_pageout(&fSync,page);
|
||||
if (short_page && (result != 1)) {
|
||||
TRACE("OggReader::GetPage: short page not found: error\n");
|
||||
result = ogg_sync_pageout(&fSync, &page);
|
||||
if (first_page && (result != 1)) {
|
||||
TRACE("OggReader::GetPage: short first page not found: error\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
fPosition += bytes;
|
||||
}
|
||||
if (result == -1) {
|
||||
TRACE("OggReader::GetPage: ogg_sync_pageout: not synced: error\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
#ifdef STRICT_OGG
|
||||
if (ogg_page_version(page) != 0) {
|
||||
if (ogg_page_version(&page) != 0) {
|
||||
TRACE("OggReader::GetPage: ogg_page_version: error in page encoding: error\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
#endif
|
||||
fNextPosition += (fSeekable ? page->header_len + page->body_len : 0);
|
||||
long serialno = ogg_page_serialno(page);
|
||||
if (fStreams.find(serialno) == fStreams.end()) {
|
||||
long serialno = ogg_page_serialno(&page);
|
||||
if (fTracks.find(serialno) == fTracks.end()) {
|
||||
// this is an unknown serialno
|
||||
if (ogg_page_bos(page) == 0) {
|
||||
if (ogg_page_bos(&page) == 0) {
|
||||
TRACE("OggReader::GetPage: non-bos page with unknown serialno\n");
|
||||
#ifdef STRICT_OGG
|
||||
return B_ERROR;
|
||||
#else
|
||||
// silently discard packets with unknown serialno
|
||||
// silently discard non-bos packets with unknown serialno
|
||||
goto retry;
|
||||
#endif
|
||||
}
|
||||
#ifdef STRICT_OGG
|
||||
if (ogg_page_continued(&page) != 0) {
|
||||
TRACE("oggReader::GetPage: ogg_page_continued: continued page: not ogg\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
#endif STRICT_OGG
|
||||
// this is a beginning of stream page
|
||||
ogg_stream_state stream;
|
||||
if (ogg_stream_init(&stream, serialno) != 0) {
|
||||
TRACE("oggReader::GetPage: ogg_stream_init failed?: error\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
if (ogg_stream_pagein(&stream, page) != 0) {
|
||||
if (ogg_stream_pagein(&stream, &page) != 0) {
|
||||
TRACE("oggReader::GetPage: ogg_stream_pagein: failed: error\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
@@ -111,44 +123,52 @@ retry:
|
||||
return B_ERROR;
|
||||
#endif STRICT_OGG
|
||||
}
|
||||
class Interface : public GetPageInterface {
|
||||
if (fSeekable) {
|
||||
class Interface : public SeekableInterface {
|
||||
private:
|
||||
OggReader * reader;
|
||||
long serialno;
|
||||
public:
|
||||
Interface(OggReader * reader, long serialno) {
|
||||
Interface(OggReader * reader) {
|
||||
this->reader = reader;
|
||||
this->serialno = serialno;
|
||||
}
|
||||
virtual status_t GetNextPage() {
|
||||
ogg_page next_page;
|
||||
do {
|
||||
status_t result = reader->GetPage(&next_page);
|
||||
if (result != B_OK) {
|
||||
return result;
|
||||
}
|
||||
} while (ogg_page_serialno(&next_page) != serialno);
|
||||
return B_OK;
|
||||
}
|
||||
virtual status_t GetPageAt(off_t position, ogg_stream_state * stream,
|
||||
int read_size = 4*B_PAGE_SIZE) {
|
||||
return reader->GetPageAt(position,stream,read_size);
|
||||
virtual ssize_t ReadPageAt(off_t position, int read_size = 4*B_PAGE_SIZE) {
|
||||
return reader->ReadPageAt(position, read_size);
|
||||
}
|
||||
};
|
||||
fStreams[serialno] = OggStream::makeOggStream(new Interface(this, serialno), serialno, packet);
|
||||
fTracks[serialno] = OggSeekable::makeOggSeekable(new Interface(this), serialno, packet);
|
||||
} else {
|
||||
class Interface : public StreamInterface {
|
||||
private:
|
||||
OggReader * reader;
|
||||
public:
|
||||
Interface(OggReader * reader) {
|
||||
this->reader = reader;
|
||||
}
|
||||
virtual ssize_t ReadPage() {
|
||||
return reader->ReadPage();
|
||||
}
|
||||
};
|
||||
fTracks[serialno] = OggStream::makeOggStream(new Interface(this), serialno, packet);
|
||||
}
|
||||
fCookies.push_back(serialno);
|
||||
}
|
||||
return fStreams[serialno]->AddPage(position,page);
|
||||
off_t pageStart = fPosition - page.header_len - page.body_len;
|
||||
status_t status = fTracks[serialno]->AddPage(pageStart, page);
|
||||
if (status != B_OK) {
|
||||
return status;
|
||||
}
|
||||
return page.header_len + page.body_len;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
OggReader::GetPageAt(off_t position, ogg_stream_state * stream, int read_size)
|
||||
ssize_t
|
||||
OggReader::ReadPageAt(off_t position, int read_size)
|
||||
{
|
||||
TRACE("OggReader::GetPageAt %llu\n", position);
|
||||
if (!fSeekable) {
|
||||
return B_ERROR;
|
||||
}
|
||||
/*
|
||||
ogg_sync_state sync;
|
||||
ogg_sync_init(&sync);
|
||||
ogg_page page;
|
||||
@@ -185,6 +205,7 @@ OggReader::GetPageAt(off_t position, ogg_stream_state * stream, int read_size)
|
||||
return B_ERROR;
|
||||
}
|
||||
ogg_sync_clear(&sync);
|
||||
*/
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
@@ -222,43 +243,42 @@ get_seekable(BDataIO * data)
|
||||
return seekable;
|
||||
}
|
||||
|
||||
static BFile *
|
||||
get_file(BDataIO * data)
|
||||
{
|
||||
// try to cast to BFile then perform a series of
|
||||
// sanity checks to ensure that the file is okay
|
||||
BFile * file = dynamic_cast<BFile *>(data);
|
||||
if (file == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (file->InitCheck() != B_OK) {
|
||||
return 0;
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
status_t
|
||||
OggReader::Sniff(int32 *streamCount)
|
||||
{
|
||||
TRACE("OggReader::Sniff\n");
|
||||
#ifdef STRICT_OGG
|
||||
bool short_page = true;
|
||||
bool first_page = true;
|
||||
#else
|
||||
bool short_page = false;
|
||||
bool first_page = false;
|
||||
#endif
|
||||
fSeekable = get_seekable(Source());
|
||||
// fSeekable = get_seekable(Source());
|
||||
fFile = get_file(Source());
|
||||
|
||||
fNextPosition = (fSeekable ? fSeekable->Position() : -1);
|
||||
|
||||
ogg_page page;
|
||||
if (GetPage(&page,4096,short_page) != B_OK) {
|
||||
return B_ERROR;
|
||||
ssize_t bytes = ReadPage(first_page);
|
||||
if (bytes < 0) {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// page sanity checks
|
||||
if (ogg_page_version(&page) != 0) {
|
||||
TRACE("OggReader::Sniff: ogg_page_version: error in page encoding: not ogg\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
#ifdef STRICT_OGG
|
||||
if (ogg_page_bos(&page) == 0) {
|
||||
TRACE("OggReader::Sniff: ogg_page_bos: not beginning of a bitstream: not ogg\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
if (ogg_page_continued(&page) != 0) {
|
||||
TRACE("OggReader::Sniff: ogg_page_continued: continued page: not ogg\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
#endif STRICT_OGG
|
||||
|
||||
while (ogg_page_bos(&page) > 0) {
|
||||
if (GetPage(&page) != B_OK) {
|
||||
return B_ERROR;
|
||||
uint count = 1;
|
||||
while (count++ == fCookies.size()) {
|
||||
ssize_t bytes = ReadPage();
|
||||
if (bytes < 0) {
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
*streamCount = fCookies.size();
|
||||
@@ -292,9 +312,9 @@ OggReader::AllocateCookie(int32 streamNumber, void **cookie)
|
||||
TRACE("OggReader::AllocateCookie: invalid streamNumber: bail\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
OggStream * stream = fStreams[fCookies[streamNumber]];
|
||||
OggTrack * track = fTracks[fCookies[streamNumber]];
|
||||
// store the cookie
|
||||
*cookie = stream;
|
||||
*cookie = track;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
@@ -314,8 +334,8 @@ OggReader::GetStreamInfo(void *cookie, int64 *frameCount, bigtime_t *duration,
|
||||
TRACE("OggReader::GetStreamInfo\n");
|
||||
*infoBuffer = 0;
|
||||
*infoSize = 0;
|
||||
OggStream * stream = static_cast<OggStream*>(cookie);
|
||||
return stream->GetStreamInfo(frameCount,duration,format);
|
||||
OggTrack * track = static_cast<OggTrack*>(cookie);
|
||||
return track->GetStreamInfo(frameCount, duration, format);
|
||||
}
|
||||
|
||||
|
||||
@@ -325,8 +345,8 @@ OggReader::Seek(void *cookie,
|
||||
int64 *frame, bigtime_t *time)
|
||||
{
|
||||
TRACE("OggReader::Seek to %lld : %lld\n", *frame, *time);
|
||||
OggStream * stream = static_cast<OggStream*>(cookie);
|
||||
return stream->Seek(seekTo,frame,time);
|
||||
OggTrack * track = static_cast<OggTrack*>(cookie);
|
||||
return track->Seek(seekTo, frame, time);
|
||||
}
|
||||
|
||||
|
||||
@@ -336,8 +356,8 @@ OggReader::GetNextChunk(void *cookie,
|
||||
media_header *mediaHeader)
|
||||
{
|
||||
// TRACE("OggReader::GetNextChunk\n");
|
||||
OggStream * stream = static_cast<OggStream*>(cookie);
|
||||
return stream->GetNextChunk(chunkBuffer,chunkSize,mediaHeader);
|
||||
OggTrack * track = static_cast<OggTrack*>(cookie);
|
||||
return track->GetNextChunk(chunkBuffer, chunkSize, mediaHeader);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
namespace BPrivate { namespace media {
|
||||
|
||||
class OggStream;
|
||||
class OggTrack;
|
||||
|
||||
typedef std::map<long,OggStream*> serialno_OggStream_map;
|
||||
typedef std::map<long,OggTrack*> serialno_OggTrack_map;
|
||||
typedef std::vector<long> serialno_vector;
|
||||
|
||||
class OggReader : public Reader
|
||||
@@ -28,36 +28,35 @@ public:
|
||||
status_t AllocateCookie(int32 streamNumber, void **cookie);
|
||||
status_t FreeCookie(void *cookie);
|
||||
|
||||
// delegated to OggStream
|
||||
// delegated to OggTrack
|
||||
status_t GetStreamInfo(void *cookie, int64 *frameCount, bigtime_t *duration,
|
||||
media_format *format, void **infoBuffer, int32 *infoSize);
|
||||
status_t Seek(void *cookie,
|
||||
uint32 seekTo,
|
||||
int64 *frame, bigtime_t *time);
|
||||
status_t GetNextChunk(void *cookie,
|
||||
void **chunkBuffer, int32 *chunkSize,
|
||||
status_t Seek(void *cookie, uint32 seekTo, int64 *frame, bigtime_t *time);
|
||||
status_t GetNextChunk(void *cookie, void **chunkBuffer, int32 *chunkSize,
|
||||
media_header *mediaHeader);
|
||||
|
||||
protected:
|
||||
// called by OggStream through GetPageInterface
|
||||
status_t GetPage(ogg_page * page, int read_size = 4*B_PAGE_SIZE,
|
||||
bool short_page = false);
|
||||
status_t GetPageAt(off_t position, ogg_stream_state * stream,
|
||||
int read_size = 4*B_PAGE_SIZE);
|
||||
|
||||
ogg_sync_state fSync;
|
||||
serialno_OggStream_map fStreams;
|
||||
serialno_vector fCookies;
|
||||
BPositionIO * fSeekable;
|
||||
|
||||
private:
|
||||
off_t fNextPosition;
|
||||
ogg_sync_state fSync;
|
||||
BLocker fSyncLock;
|
||||
serialno_OggTrack_map fTracks;
|
||||
serialno_vector fCookies;
|
||||
|
||||
class GetPageInterface {
|
||||
BPositionIO * fSeekable;
|
||||
BFile * fFile;
|
||||
|
||||
off_t fPosition;
|
||||
|
||||
// interfaces for OggTracks
|
||||
ssize_t ReadPage(bool first_page = false);
|
||||
ssize_t ReadPageAt(off_t position, int read_size = 4*B_PAGE_SIZE);
|
||||
|
||||
class StreamInterface {
|
||||
public:
|
||||
virtual status_t GetNextPage() = 0;
|
||||
virtual status_t GetPageAt(off_t position, ogg_stream_state * stream,
|
||||
int read_size = 4*B_PAGE_SIZE) = 0;
|
||||
virtual ssize_t ReadPage() = 0;
|
||||
};
|
||||
class SeekableInterface {
|
||||
public:
|
||||
virtual ssize_t ReadPageAt(off_t position, int read_size = 4*B_PAGE_SIZE) = 0;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
#include "OggSeekable.h"
|
||||
#include "OggVorbisSeekable.h"
|
||||
#include <Autolock.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define TRACE_THIS 1
|
||||
#if TRACE_THIS
|
||||
#define TRACE printf
|
||||
#else
|
||||
#define TRACE(a...) ((void)0)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* OggSeekable codec identification and instantiation
|
||||
*/
|
||||
|
||||
/* static */ OggSeekable *
|
||||
OggSeekable::makeOggSeekable(OggReader::SeekableInterface * interface,
|
||||
long serialno, const ogg_packet & packet)
|
||||
{
|
||||
TRACE("OggSeekable::makeOggSeekable\n");
|
||||
OggSeekable * stream;
|
||||
if (OggVorbisSeekable::IsValidHeader(packet)) {
|
||||
stream = new OggVorbisSeekable(serialno);
|
||||
// } else if (OggTobiasSeekable::IsValidHeader(packet)) {
|
||||
// stream = new OggTobiasSeekable(serialno);
|
||||
// } else if (OggSpeexSeekable::IsValidHeader(packet)) {
|
||||
// stream = new OggSpeexSeekable(serialno);
|
||||
// } else if (OggTheoraSeekable::IsValidHeader(packet)) {
|
||||
// stream = new OggTheoraSeekable(serialno);
|
||||
} else {
|
||||
stream = new OggSeekable(serialno);
|
||||
}
|
||||
stream->fReaderInterface = interface;
|
||||
return stream;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* OggSeekable generic functions
|
||||
*/
|
||||
|
||||
OggSeekable::OggSeekable(long serialno)
|
||||
: OggTrack(serialno)
|
||||
{
|
||||
TRACE("OggSeekable::OggSeekable\n");
|
||||
ogg_sync_init(&fSync);
|
||||
ogg_stream_init(&fStreamState,serialno);
|
||||
}
|
||||
|
||||
|
||||
OggSeekable::~OggSeekable()
|
||||
{
|
||||
// free internal stream state storage
|
||||
ogg_sync_clear(&fSync);
|
||||
ogg_stream_clear(&fStreamState);
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
OggSeekable::AddPage(off_t position, const ogg_page & page)
|
||||
{
|
||||
TRACE("OggSeekable::AddPage\n");
|
||||
BAutolock autolock(fSyncLock);
|
||||
char * buffer;
|
||||
// copy the header to our local sync
|
||||
buffer = ogg_sync_buffer(&fSync, page.header_len);
|
||||
memcpy(buffer, page.header, page.header_len);
|
||||
ogg_sync_wrote(&fSync, page.header_len);
|
||||
// copy the body to our local sync
|
||||
buffer = ogg_sync_buffer(&fSync, page.body_len);
|
||||
memcpy(buffer, page.body, page.body_len);
|
||||
ogg_sync_wrote(&fSync, page.body_len);
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
OggSeekable::GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
||||
media_format *format)
|
||||
{
|
||||
TRACE("OggSeekable::GetStreamInfo\n");
|
||||
status_t result = B_OK;
|
||||
ogg_packet packet;
|
||||
if (GetHeaderPackets().size() < 1) {
|
||||
result = GetPacket(&packet);
|
||||
if (result != B_OK) {
|
||||
TRACE("OggSeekable::GetStreamInfo failed to get header packet\n");
|
||||
return result;
|
||||
}
|
||||
SaveHeaderPacket(packet);
|
||||
}
|
||||
packet = GetHeaderPackets()[0];
|
||||
if (!packet.b_o_s) {
|
||||
TRACE("OggSeekable::GetStreamInfo 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;
|
||||
result = formats.InitCheck();
|
||||
if (result == B_OK) {
|
||||
result = formats.GetFormatFor(description, format);
|
||||
}
|
||||
if (result != B_OK) {
|
||||
// ignore the error, allow the user to use ReadChunk interface
|
||||
}
|
||||
|
||||
// fill out format from header packet
|
||||
format->user_data_type = B_CODEC_TYPE_INFO;
|
||||
strncpy((char*)format->user_data, (char*)(&packet), 4);
|
||||
|
||||
format->SetMetaData((void*)&GetHeaderPackets(),sizeof(GetHeaderPackets()));
|
||||
*duration = 140000000;
|
||||
*frameCount = 60000;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
// the default chunk is an ogg packet
|
||||
status_t
|
||||
OggSeekable::GetNextChunk(void **chunkBuffer, int32 *chunkSize,
|
||||
media_header *mediaHeader)
|
||||
{
|
||||
status_t result = GetPacket(&fChunkPacket);
|
||||
if (result != B_OK) {
|
||||
TRACE("OggSeekable::GetNextChunk failed: GetPacket = %s\n", strerror(result));
|
||||
return result;
|
||||
}
|
||||
*chunkBuffer = &fChunkPacket;
|
||||
*chunkSize = sizeof(fChunkPacket);
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
// subclass pull input function
|
||||
status_t
|
||||
OggSeekable::GetPacket(ogg_packet * packet)
|
||||
{
|
||||
// at the end, pull the packet
|
||||
while (ogg_stream_packetpeek(&fStreamState, NULL) != 1) {
|
||||
BAutolock autolock(fSyncLock);
|
||||
int result;
|
||||
ogg_page page;
|
||||
while ((result = ogg_sync_pageout(&fSync,&page)) == 0) {
|
||||
status_t result = B_ERROR; // fReaderInterface->ReadPage();
|
||||
if (result != B_OK) {
|
||||
TRACE("OggSeekable::GetPacket: GetNextPage = %s\n", strerror(result));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (result == -1) {
|
||||
TRACE("OggSeekable::GetPacket: ogg_sync_pageout: not synced??\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
if (ogg_stream_pagein(&fStreamState,&page) != 0) {
|
||||
TRACE("OggSeekable::GetPacket: ogg_stream_pagein: failed??\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
}
|
||||
if (ogg_stream_packetout(&fStreamState, packet) != 1) {
|
||||
TRACE("OggSeekable::GetPacket: ogg_stream_packetout failed\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef _OGG_SEEKABLE_H
|
||||
#define _OGG_SEEKABLE_H
|
||||
|
||||
#include "OggTrack.h"
|
||||
#include "OggReaderPlugin.h"
|
||||
#include <map>
|
||||
|
||||
namespace BPrivate { namespace media {
|
||||
|
||||
class OggSeekable : public OggTrack {
|
||||
public:
|
||||
static OggSeekable * makeOggSeekable(OggReader::SeekableInterface * interface,
|
||||
long serialno, const ogg_packet & packet);
|
||||
|
||||
// interface for OggReader
|
||||
virtual status_t GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
||||
media_format *format);
|
||||
virtual status_t GetNextChunk(void **chunkBuffer, int32 *chunkSize,
|
||||
media_header *mediaHeader);
|
||||
|
||||
protected:
|
||||
OggSeekable(long serialno);
|
||||
public:
|
||||
virtual ~OggSeekable();
|
||||
|
||||
// reader push input function
|
||||
status_t AddPage(off_t position, const ogg_page & page);
|
||||
|
||||
protected:
|
||||
// subclass pull input function
|
||||
status_t GetPacket(ogg_packet * packet);
|
||||
|
||||
private:
|
||||
ogg_sync_state fSync;
|
||||
BLocker fSyncLock;
|
||||
ogg_stream_state fStreamState;
|
||||
OggReader::SeekableInterface * fReaderInterface;
|
||||
ogg_packet fChunkPacket;
|
||||
};
|
||||
|
||||
} } // namespace BPrivate::media
|
||||
|
||||
using namespace BPrivate::media;
|
||||
|
||||
#endif // _OGG_SEEKABLE_H
|
||||
@@ -70,14 +70,14 @@ OggSpeexStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
||||
ogg_packet packet;
|
||||
|
||||
// get header packet
|
||||
if (fHeaderPackets.size() < 1) {
|
||||
if (GetHeaderPackets().size() < 1) {
|
||||
result = GetPacket(&packet);
|
||||
if (result != B_OK) {
|
||||
return result;
|
||||
}
|
||||
SaveHeaderPacket(packet);
|
||||
}
|
||||
packet = fHeaderPackets[0];
|
||||
packet = GetHeaderPackets()[0];
|
||||
if (!packet.b_o_s) {
|
||||
return B_ERROR; // first packet was not beginning of stream
|
||||
}
|
||||
@@ -122,7 +122,7 @@ OggSpeexStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
||||
format->u.encoded_audio.output.buffer_size = buffer_size;
|
||||
|
||||
// get comment packet
|
||||
if (fHeaderPackets.size() < 2) {
|
||||
if (GetHeaderPackets().size() < 2) {
|
||||
result = GetPacket(&packet);
|
||||
if (result != B_OK) {
|
||||
return result;
|
||||
@@ -131,7 +131,7 @@ OggSpeexStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
||||
}
|
||||
|
||||
// get extra headers
|
||||
while ((signed)fHeaderPackets.size() < header->extra_headers) {
|
||||
while ((signed)GetHeaderPackets().size() < header->extra_headers) {
|
||||
result = GetPacket(&packet);
|
||||
if (result != B_OK) {
|
||||
return result;
|
||||
@@ -139,8 +139,23 @@ OggSpeexStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
||||
SaveHeaderPacket(packet);
|
||||
}
|
||||
|
||||
format->SetMetaData((void*)&fHeaderPackets,sizeof(fHeaderPackets));
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@ public:
|
||||
|
||||
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
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
|
||||
/* static */ OggStream *
|
||||
OggStream::makeOggStream(OggReader::GetPageInterface * interface,
|
||||
OggStream::makeOggStream(OggReader::StreamInterface * interface,
|
||||
long serialno, const ogg_packet & packet)
|
||||
{
|
||||
TRACE("OggStream::makeOggStream\n");
|
||||
@@ -39,78 +39,43 @@ OggStream::makeOggStream(OggReader::GetPageInterface * interface,
|
||||
}
|
||||
|
||||
|
||||
/* static */ bool
|
||||
OggStream::findIdentifier(const ogg_packet & packet, const char * id, uint pos)
|
||||
{
|
||||
uint length = strlen(id);
|
||||
if ((unsigned)packet.bytes < pos+length) {
|
||||
return false;
|
||||
}
|
||||
return !memcmp(&packet.packet[pos], id, length);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* OggStream generic functions
|
||||
*/
|
||||
|
||||
OggStream::OggStream(long serialno)
|
||||
: OggTrack(serialno)
|
||||
{
|
||||
TRACE("OggStream::OggStream\n");
|
||||
fCurrentFrame = 0;
|
||||
fCurrentTime = 0;
|
||||
this->fSerialno = serialno;
|
||||
ogg_sync_init(&fSync);
|
||||
ogg_stream_init(&fSeekStreamState,serialno);
|
||||
fCurrentPage = 0;
|
||||
fPacketOnCurrentPage = 0;
|
||||
fCurrentPacket = 0;
|
||||
ogg_stream_init(&fEndStreamState,serialno);
|
||||
fEndPage = 0;
|
||||
fPacketOnEndPage = 0;
|
||||
fEndPacket = 0;
|
||||
ogg_stream_init(&fStreamState,serialno);
|
||||
}
|
||||
|
||||
|
||||
OggStream::~OggStream()
|
||||
{
|
||||
// free internal header packet storage
|
||||
std::vector<ogg_packet>::iterator iter = fHeaderPackets.begin();
|
||||
while (iter != fHeaderPackets.end()) {
|
||||
delete iter->packet;
|
||||
iter++;
|
||||
}
|
||||
// free internal stream state storage
|
||||
ogg_sync_clear(&fSync);
|
||||
ogg_stream_clear(&fSeekStreamState);
|
||||
ogg_stream_clear(&fEndStreamState);
|
||||
}
|
||||
|
||||
|
||||
long
|
||||
OggStream::GetSerial() const
|
||||
{
|
||||
return fSerialno;
|
||||
ogg_stream_clear(&fStreamState);
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
OggStream::AddPage(off_t position, ogg_page * page)
|
||||
OggStream::AddPage(off_t position, const ogg_page & page)
|
||||
{
|
||||
TRACE("OggStream::AddPage");
|
||||
if (position >= 0) {
|
||||
TRACE(" %lld", position);
|
||||
fPagePositions.push_back(position);
|
||||
}
|
||||
TRACE("\n");
|
||||
TRACE("OggStream::AddPage\n");
|
||||
BAutolock autolock(fSyncLock);
|
||||
char * buffer;
|
||||
buffer = ogg_sync_buffer(&fSync, page->header_len);
|
||||
memcpy(buffer,page->header, page->header_len);
|
||||
ogg_sync_wrote(&fSync, page->header_len);
|
||||
buffer = ogg_sync_buffer(&fSync, page->body_len);
|
||||
memcpy(buffer,page->body, page->body_len);
|
||||
ogg_sync_wrote(&fSync, page->body_len);
|
||||
// copy the header to our local sync
|
||||
buffer = ogg_sync_buffer(&fSync, page.header_len);
|
||||
memcpy(buffer, page.header, page.header_len);
|
||||
ogg_sync_wrote(&fSync, page.header_len);
|
||||
// copy the body to our local sync
|
||||
buffer = ogg_sync_buffer(&fSync, page.body_len);
|
||||
memcpy(buffer, page.body, page.body_len);
|
||||
ogg_sync_wrote(&fSync, page.body_len);
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
@@ -122,7 +87,7 @@ OggStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
||||
TRACE("OggStream::GetStreamInfo\n");
|
||||
status_t result = B_OK;
|
||||
ogg_packet packet;
|
||||
if (fHeaderPackets.size() < 1) {
|
||||
if (GetHeaderPackets().size() < 1) {
|
||||
result = GetPacket(&packet);
|
||||
if (result != B_OK) {
|
||||
TRACE("OggStream::GetStreamInfo failed to get header packet\n");
|
||||
@@ -130,7 +95,7 @@ OggStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
||||
}
|
||||
SaveHeaderPacket(packet);
|
||||
}
|
||||
packet = fHeaderPackets[0];
|
||||
packet = GetHeaderPackets()[0];
|
||||
if (!packet.b_o_s) {
|
||||
TRACE("OggStream::GetStreamInfo failed : not beginning of stream\n");
|
||||
return B_ERROR; // first packet was not beginning of stream
|
||||
@@ -157,70 +122,18 @@ OggStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
||||
format->user_data_type = B_CODEC_TYPE_INFO;
|
||||
strncpy((char*)format->user_data, (char*)(&packet), 4);
|
||||
|
||||
format->SetMetaData((void*)&fHeaderPackets,sizeof(fHeaderPackets));
|
||||
format->SetMetaData((void*)&GetHeaderPackets(),sizeof(GetHeaderPackets()));
|
||||
*duration = 140000000;
|
||||
*frameCount = 60000;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
OggStream::Seek(uint32 seekTo, int64 *frame, bigtime_t *time)
|
||||
{
|
||||
TRACE("OggStream::Seek to %lld : %lld\n", *frame, *time);
|
||||
if (seekTo & B_MEDIA_SEEK_TO_FRAME) {
|
||||
if (*frame > fOggFrameInfos.size() - 1) {
|
||||
// seek to end
|
||||
fCurrentPage = fEndPage;
|
||||
fPacketOnCurrentPage = fPacketOnEndPage;
|
||||
fCurrentPacket = fEndPacket;
|
||||
return B_OK;
|
||||
}
|
||||
*frame = max_c(0, *frame); // clip to zero
|
||||
// input the page into a temporary seek stream
|
||||
ogg_stream_state seekStreamState;
|
||||
uint pageno = fOggFrameInfos[*frame].GetPage();
|
||||
off_t position = fPagePositions[pageno];
|
||||
ogg_stream_init(&seekStreamState, fSerialno);
|
||||
status_t result = fReaderInterface->GetPageAt(position, &seekStreamState);
|
||||
if (result != B_OK) {
|
||||
TRACE("OggStream::GetPageAt %lld failed\n", position);
|
||||
return result; // pageno/fPagePosition corrupted?
|
||||
}
|
||||
// discard earlier packets from this page
|
||||
uint packetno = fOggFrameInfos[*frame].GetPacketOnPage();
|
||||
while (packetno-- > 0) {
|
||||
ogg_packet packet;
|
||||
// don't check for errors, we may have a packet ending on page
|
||||
ogg_stream_packetout(&seekStreamState, &packet);
|
||||
}
|
||||
// clear out the former seek stream state
|
||||
// this will delete its internal storage
|
||||
ogg_stream_clear(&fSeekStreamState);
|
||||
// initialize it with the temporary seek stream
|
||||
// this will transfer the internal storage to it
|
||||
fSeekStreamState = seekStreamState;
|
||||
// we notably do not clear our temporary stream
|
||||
// instead we just let it go out of scope
|
||||
fCurrentPage = fOggFrameInfos[*frame].GetNextPage();
|
||||
fPacketOnCurrentPage = fOggFrameInfos[*frame].GetNextPacketOnPage();
|
||||
fCurrentPacket = fOggFrameInfos[*frame].GetPacket()+1;
|
||||
} else if (seekTo & B_MEDIA_SEEK_TO_TIME) {
|
||||
*frame = *time/50000;
|
||||
return Seek(B_MEDIA_SEEK_TO_FRAME,frame,time);
|
||||
}
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
// the default chunk is an ogg packet
|
||||
status_t
|
||||
OggStream::GetNextChunk(void **chunkBuffer, int32 *chunkSize,
|
||||
media_header *mediaHeader)
|
||||
{
|
||||
if (fCurrentPacket - fHeaderPackets.size() == fOggFrameInfos.size()) {
|
||||
OggFrameInfo info(fEndPage,fPacketOnEndPage,fEndPacket);
|
||||
fOggFrameInfos.push_back(info);
|
||||
}
|
||||
status_t result = GetPacket(&fChunkPacket);
|
||||
if (result != B_OK) {
|
||||
TRACE("OggStream::GetNextChunk failed: GetPacket = %s\n", strerror(result));
|
||||
@@ -236,16 +149,14 @@ OggStream::GetNextChunk(void **chunkBuffer, int32 *chunkSize,
|
||||
status_t
|
||||
OggStream::GetPacket(ogg_packet * packet)
|
||||
{
|
||||
if (fCurrentPacket >= fEndPacket) {
|
||||
// at the end, pull the packet
|
||||
uint8 pageno = fEndPage;
|
||||
while (ogg_stream_packetpeek(&fEndStreamState, NULL) != 1) {
|
||||
while (ogg_stream_packetpeek(&fStreamState, NULL) != 1) {
|
||||
BAutolock autolock(fSyncLock);
|
||||
int result;
|
||||
ogg_page page;
|
||||
while ((result = ogg_sync_pageout(&fSync,&page)) == 0) {
|
||||
status_t result = fReaderInterface->GetNextPage();
|
||||
if (result != B_OK) {
|
||||
result = fReaderInterface->ReadPage();
|
||||
if (result < 0) {
|
||||
TRACE("OggStream::GetPacket: GetNextPage = %s\n", strerror(result));
|
||||
return result;
|
||||
}
|
||||
@@ -254,64 +165,15 @@ OggStream::GetPacket(ogg_packet * packet)
|
||||
TRACE("OggStream::GetPacket: ogg_sync_pageout: not synced??\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
if (ogg_stream_pagein(&fEndStreamState,&page) != 0) {
|
||||
if (ogg_stream_pagein(&fStreamState,&page) != 0) {
|
||||
TRACE("OggStream::GetPacket: ogg_stream_pagein: failed??\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
fEndPage++;
|
||||
}
|
||||
if (ogg_stream_packetout(&fEndStreamState, packet) != 1) {
|
||||
TRACE("OggStream::GetPacket: ogg_stream_packetout failed at the end\n");
|
||||
if (ogg_stream_packetout(&fStreamState, packet) != 1) {
|
||||
TRACE("OggStream::GetPacket: ogg_stream_packetout failed\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
fEndPacket++;
|
||||
if (pageno != fEndPage) {
|
||||
size_t last_info = fOggFrameInfos.size();
|
||||
if (last_info > 0) {
|
||||
if (fOggFrameInfos[last_info-1].GetNextPacket() == fEndPacket) {
|
||||
fOggFrameInfos[last_info-1].SetNextIsNewPage();
|
||||
}
|
||||
}
|
||||
fPacketOnEndPage = 0;
|
||||
} else {
|
||||
fPacketOnEndPage++;
|
||||
}
|
||||
fCurrentPacket = fEndPacket;
|
||||
fPacketOnCurrentPage = fPacketOnEndPage;
|
||||
fCurrentPage = fEndPage;
|
||||
} else {
|
||||
// in the middle, get packet at position
|
||||
uint8 page = fCurrentPage;
|
||||
while (ogg_stream_packetpeek(&fSeekStreamState, NULL) != 1) {
|
||||
off_t position = fPagePositions[fCurrentPage++];
|
||||
status_t result = fReaderInterface->GetPageAt(position, &fSeekStreamState);
|
||||
if (result != B_OK) {
|
||||
TRACE("OggStream::GetPacket: GetPageAt = %s\n", strerror(result));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (ogg_stream_packetout(&fSeekStreamState, packet) != 1) {
|
||||
TRACE("OggStream::GetPacket: ogg_stream_packetout failed in the middle\n");
|
||||
return B_ERROR;
|
||||
}
|
||||
fCurrentPacket++;
|
||||
if (page != fCurrentPage) {
|
||||
fPacketOnCurrentPage = 0;
|
||||
} else {
|
||||
fPacketOnCurrentPage++;
|
||||
}
|
||||
}
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
// GetStreamInfo helpers
|
||||
void
|
||||
OggStream::SaveHeaderPacket(ogg_packet packet)
|
||||
{
|
||||
uint8 * buffer = new uint8[packet.bytes];
|
||||
memcpy(buffer, packet.packet, packet.bytes);
|
||||
packet.packet = buffer;
|
||||
fHeaderPackets.push_back(packet);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,67 +1,45 @@
|
||||
#ifndef _OGG_STREAM_H
|
||||
#define _OGG_STREAM_H
|
||||
|
||||
#include <SupportDefs.h>
|
||||
#include <MediaDefs.h>
|
||||
#include "ogg/ogg.h"
|
||||
#include "OggTrack.h"
|
||||
#include "OggReaderPlugin.h"
|
||||
#include "OggFrameInfo.h"
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace BPrivate { namespace media {
|
||||
|
||||
class OggStream {
|
||||
class OggStream : public OggTrack {
|
||||
public:
|
||||
static OggStream * makeOggStream(OggReader::GetPageInterface * interface,
|
||||
static OggStream * makeOggStream(OggReader::StreamInterface * interface,
|
||||
long serialno, const ogg_packet & packet);
|
||||
|
||||
protected:
|
||||
static bool findIdentifier(const ogg_packet & packet,
|
||||
const char * id, uint pos);
|
||||
OggStream(long serialno);
|
||||
public:
|
||||
virtual ~OggStream();
|
||||
long GetSerial() const;
|
||||
|
||||
// reader push input function
|
||||
status_t AddPage(off_t position, ogg_page * page);
|
||||
|
||||
// reader pull output functions
|
||||
// interface for OggReader
|
||||
virtual status_t GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
||||
media_format *format);
|
||||
virtual status_t Seek(uint32 seekTo, int64 *frame, bigtime_t *time);
|
||||
virtual status_t GetNextChunk(void **chunkBuffer, int32 *chunkSize,
|
||||
media_header *mediaHeader);
|
||||
|
||||
protected:
|
||||
OggStream(long serialno);
|
||||
public:
|
||||
virtual ~OggStream();
|
||||
|
||||
// reader push input function
|
||||
status_t AddPage(off_t position, const ogg_page & page);
|
||||
|
||||
protected:
|
||||
// subclass pull input function
|
||||
status_t GetPacket(ogg_packet * packet);
|
||||
|
||||
// GetStreamInfo helpers
|
||||
void SaveHeaderPacket(ogg_packet packet);
|
||||
std::vector<ogg_packet> fHeaderPackets;
|
||||
ogg_packet fChunkPacket;
|
||||
|
||||
protected:
|
||||
int64 fCurrentFrame;
|
||||
bigtime_t fCurrentTime;
|
||||
|
||||
private:
|
||||
long fSerialno;
|
||||
std::vector<off_t> fPagePositions;
|
||||
std::vector<OggFrameInfo> fOggFrameInfos;
|
||||
ogg_sync_state fSync;
|
||||
BLocker fSyncLock;
|
||||
ogg_stream_state fSeekStreamState;
|
||||
uint fCurrentPage;
|
||||
uint fPacketOnCurrentPage;
|
||||
uint fCurrentPacket;
|
||||
ogg_stream_state fEndStreamState;
|
||||
uint fEndPage;
|
||||
uint fPacketOnEndPage;
|
||||
uint fEndPacket;
|
||||
OggReader::GetPageInterface * fReaderInterface;
|
||||
ogg_packet fChunkPacket;
|
||||
ogg_stream_state fStreamState;
|
||||
OggReader::StreamInterface * fReaderInterface;
|
||||
};
|
||||
|
||||
} } // namespace BPrivate::media
|
||||
|
||||
@@ -152,14 +152,14 @@ OggTheoraStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
||||
ogg_packet packet;
|
||||
|
||||
// get header packet
|
||||
if (fHeaderPackets.size() < 1) {
|
||||
if (GetHeaderPackets().size() < 1) {
|
||||
result = GetPacket(&packet);
|
||||
if (result != B_OK) {
|
||||
return result;
|
||||
}
|
||||
SaveHeaderPacket(packet);
|
||||
}
|
||||
packet = fHeaderPackets[0];
|
||||
packet = GetHeaderPackets()[0];
|
||||
if (!packet.b_o_s) {
|
||||
return B_ERROR; // first packet was not beginning of stream
|
||||
}
|
||||
@@ -199,7 +199,7 @@ OggTheoraStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
||||
format->u.encoded_video.output.display.line_count = info.frame_height;
|
||||
// TODO: wring more info out of the headers
|
||||
|
||||
format->SetMetaData((void*)&fHeaderPackets,sizeof(fHeaderPackets));
|
||||
format->SetMetaData((void*)&GetHeaderPackets(),sizeof(GetHeaderPackets()));
|
||||
*duration = 80000000;
|
||||
*frameCount = 60000;
|
||||
return B_OK;
|
||||
|
||||
@@ -73,7 +73,7 @@ OggTobiasStream::OggTobiasStream(long serialno)
|
||||
|
||||
OggTobiasStream::~OggTobiasStream()
|
||||
{
|
||||
|
||||
TRACE("OggTobiasStream::~OggTobiasStream\n");
|
||||
}
|
||||
|
||||
status_t
|
||||
@@ -85,14 +85,14 @@ OggTobiasStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
||||
ogg_packet packet;
|
||||
|
||||
// get header packet
|
||||
if (fHeaderPackets.size() < 1) {
|
||||
if (GetHeaderPackets().size() < 1) {
|
||||
result = GetPacket(&packet);
|
||||
if (result != B_OK) {
|
||||
return result;
|
||||
}
|
||||
SaveHeaderPacket(packet);
|
||||
}
|
||||
packet = fHeaderPackets[0];
|
||||
packet = GetHeaderPackets()[0];
|
||||
if (!packet.b_o_s) {
|
||||
return B_ERROR; // first packet was not beginning of stream
|
||||
}
|
||||
@@ -128,7 +128,7 @@ OggTobiasStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
||||
// TODO: wring more info out of the headers
|
||||
|
||||
// get comment packet
|
||||
if (fHeaderPackets.size() < 2) {
|
||||
if (GetHeaderPackets().size() < 2) {
|
||||
result = GetPacket(&packet);
|
||||
if (result != B_OK) {
|
||||
return result;
|
||||
@@ -136,7 +136,7 @@ OggTobiasStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
||||
SaveHeaderPacket(packet);
|
||||
}
|
||||
|
||||
format->SetMetaData((void*)&fHeaderPackets,sizeof(fHeaderPackets));
|
||||
format->SetMetaData((void*)&GetHeaderPackets(),sizeof(GetHeaderPackets()));
|
||||
*duration = 80000000;
|
||||
*frameCount = 60000;
|
||||
return B_OK;
|
||||
@@ -146,8 +146,12 @@ status_t
|
||||
OggTobiasStream::GetNextChunk(void **chunkBuffer, int32 *chunkSize,
|
||||
media_header *mediaHeader)
|
||||
{
|
||||
OggStream::GetNextChunk(chunkBuffer, chunkSize, mediaHeader);
|
||||
*chunkSize = ((ogg_packet*)chunkBuffer)->bytes;
|
||||
*chunkBuffer = ((ogg_packet*)chunkBuffer)->packet;
|
||||
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;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@ public:
|
||||
media_format *format);
|
||||
virtual status_t GetNextChunk(void **chunkBuffer, int32 *chunkSize,
|
||||
media_header *mediaHeader);
|
||||
private:
|
||||
ogg_packet fChunkPacket;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
#include "OggTrack.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#define TRACE_THIS 1
|
||||
#if TRACE_THIS
|
||||
#define TRACE printf
|
||||
#else
|
||||
#define TRACE(a...) ((void)0)
|
||||
#endif
|
||||
|
||||
/* static */ bool
|
||||
OggTrack::findIdentifier(const ogg_packet & packet, const char * id, uint pos)
|
||||
{
|
||||
uint length = strlen(id);
|
||||
if ((unsigned)packet.bytes < pos+length) {
|
||||
return false;
|
||||
}
|
||||
return !memcmp(&packet.packet[pos], id, length);
|
||||
}
|
||||
|
||||
|
||||
OggTrack::OggTrack(long serialno)
|
||||
{
|
||||
TRACE("OggTrack::OggTrack\n");
|
||||
fSerialno = serialno;
|
||||
}
|
||||
|
||||
|
||||
OggTrack::~OggTrack()
|
||||
{
|
||||
// free internal header packet storage
|
||||
std::vector<ogg_packet>::iterator iter = fHeaderPackets.begin();
|
||||
while (iter != fHeaderPackets.end()) {
|
||||
delete iter->packet;
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
long
|
||||
OggTrack::GetSerial() const
|
||||
{
|
||||
return fSerialno;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
OggTrack::GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
||||
media_format *format)
|
||||
{
|
||||
*frameCount = 0;
|
||||
*duration = 0;
|
||||
media_format f;
|
||||
*format = f;
|
||||
return B_UNSUPPORTED;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
OggTrack::Seek(uint32 seekTo, int64 *frame, bigtime_t *time)
|
||||
{
|
||||
*frame = 0;
|
||||
*time = 0;
|
||||
return B_UNSUPPORTED;
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
OggTrack::GetNextChunk(void **chunkBuffer, int32 *chunkSize,
|
||||
media_header *mediaHeader)
|
||||
{
|
||||
*chunkBuffer = 0;
|
||||
*chunkSize = 0;
|
||||
media_header mh;
|
||||
*mediaHeader = mh;
|
||||
return B_UNSUPPORTED;
|
||||
}
|
||||
|
||||
|
||||
// GetStreamInfo helpers
|
||||
void
|
||||
OggTrack::SaveHeaderPacket(ogg_packet packet)
|
||||
{
|
||||
uint8 * buffer = new uint8[packet.bytes];
|
||||
memcpy(buffer, packet.packet, packet.bytes);
|
||||
packet.packet = buffer;
|
||||
fHeaderPackets.push_back(packet);
|
||||
}
|
||||
|
||||
|
||||
const std::vector<ogg_packet> &
|
||||
OggTrack::GetHeaderPackets()
|
||||
{
|
||||
return fHeaderPackets;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#ifndef _OGG_TRACK_H
|
||||
#define _OGG_TRACK_H
|
||||
|
||||
#include <SupportDefs.h>
|
||||
#include <MediaDefs.h>
|
||||
#include "ogg/ogg.h"
|
||||
#include <vector>
|
||||
|
||||
namespace BPrivate { namespace media {
|
||||
|
||||
class OggTrack {
|
||||
protected:
|
||||
static bool findIdentifier(const ogg_packet & packet,
|
||||
const char * id, uint pos);
|
||||
OggTrack(long serialno);
|
||||
public:
|
||||
virtual ~OggTrack();
|
||||
long GetSerial() const;
|
||||
|
||||
// push interface
|
||||
virtual status_t AddPage(off_t position, const ogg_page & page) = 0;
|
||||
|
||||
// interface for OggReader
|
||||
virtual status_t GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
||||
media_format *format);
|
||||
virtual status_t Seek(uint32 seekTo, int64 *frame, bigtime_t *time);
|
||||
virtual status_t GetNextChunk(void **chunkBuffer, int32 *chunkSize,
|
||||
media_header *mediaHeader);
|
||||
|
||||
protected:
|
||||
// GetStreamInfo helpers
|
||||
void SaveHeaderPacket(ogg_packet packet);
|
||||
const std::vector<ogg_packet> & GetHeaderPackets();
|
||||
|
||||
private:
|
||||
std::vector<ogg_packet> fHeaderPackets;
|
||||
long fSerialno;
|
||||
};
|
||||
|
||||
} } // namespace BPrivate::media
|
||||
|
||||
using namespace BPrivate::media;
|
||||
|
||||
#endif // _OGG_TRACK_H
|
||||
@@ -0,0 +1,197 @@
|
||||
#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;
|
||||
}
|
||||
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
|
||||
int64 samples = 1000000;
|
||||
*frameCount = samples;
|
||||
*duration = (1000000LL * samples) / (long long)format->u.encoded_audio.output.frame_rate;
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef _OGG_VORBIS_SEEKABLE_H
|
||||
#define _OGG_VORBIS_SEEKABLE_H
|
||||
|
||||
#include "OggSeekable.h"
|
||||
|
||||
namespace BPrivate { namespace media {
|
||||
|
||||
class OggVorbisSeekable : public OggSeekable {
|
||||
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
|
||||
@@ -93,13 +93,11 @@ OggVorbisStream::OggVorbisStream(long serialno)
|
||||
: OggStream(serialno)
|
||||
{
|
||||
TRACE("OggVorbisStream::OggVorbisStream\n");
|
||||
fFrameCount = -1;
|
||||
fDuration = -1;
|
||||
}
|
||||
|
||||
OggVorbisStream::~OggVorbisStream()
|
||||
{
|
||||
|
||||
TRACE("OggVorbisStream::~OggVorbisStream\n");
|
||||
}
|
||||
|
||||
status_t
|
||||
@@ -111,14 +109,14 @@ OggVorbisStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
||||
ogg_packet packet;
|
||||
|
||||
// get header packet
|
||||
if (fHeaderPackets.size() < 1) {
|
||||
if (GetHeaderPackets().size() < 1) {
|
||||
result = GetPacket(&packet);
|
||||
if (result != B_OK) {
|
||||
return result;
|
||||
}
|
||||
SaveHeaderPacket(packet);
|
||||
}
|
||||
packet = fHeaderPackets[0];
|
||||
packet = GetHeaderPackets()[0];
|
||||
if (!packet.b_o_s) {
|
||||
return B_ERROR; // first packet was not beginning of stream
|
||||
}
|
||||
@@ -171,7 +169,7 @@ OggVorbisStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
||||
= AudioBufferSize(&format->u.encoded_audio.output);
|
||||
|
||||
// get comment packet
|
||||
if (fHeaderPackets.size() < 2) {
|
||||
if (GetHeaderPackets().size() < 2) {
|
||||
result = GetPacket(&packet);
|
||||
if (result != B_OK) {
|
||||
return result;
|
||||
@@ -180,7 +178,7 @@ OggVorbisStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
||||
}
|
||||
|
||||
// get codebook packet
|
||||
if (fHeaderPackets.size() < 3) {
|
||||
if (GetHeaderPackets().size() < 3) {
|
||||
result = GetPacket(&packet);
|
||||
if (result != B_OK) {
|
||||
return result;
|
||||
@@ -188,49 +186,12 @@ OggVorbisStream::GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
||||
SaveHeaderPacket(packet);
|
||||
}
|
||||
|
||||
format->SetMetaData((void*)&fHeaderPackets,sizeof(fHeaderPackets));
|
||||
|
||||
// use cached frame count and duration for now
|
||||
if (fDuration >= 0) {
|
||||
*frameCount = fFrameCount;
|
||||
*duration = fDuration;
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
void *chunkBuffer = 0;
|
||||
int32 chunkSize = 0;
|
||||
media_header mediaHeader;
|
||||
if (GetNextChunk(&chunkBuffer, &chunkSize, &mediaHeader) != B_OK) {
|
||||
*frameCount = 0;
|
||||
*duration = 0;
|
||||
return B_OK;
|
||||
}
|
||||
ogg_packet * chunk_packet = static_cast<ogg_packet *>(chunkBuffer);
|
||||
ogg_int64_t first_packet_granulepos = chunk_packet->granulepos;
|
||||
ogg_int64_t last_packet_granulepos = chunk_packet->granulepos;
|
||||
ogg_int64_t next_packet = chunk_packet->packetno + 1;
|
||||
int64 samples = 0;
|
||||
while (GetNextChunk(&chunkBuffer, &chunkSize, &mediaHeader) == B_OK) {
|
||||
chunk_packet = static_cast<ogg_packet *>(chunkBuffer);
|
||||
if (chunk_packet->packetno != next_packet) {
|
||||
// there's a hole in the data, add samples so far, and start counting again
|
||||
TRACE("OggVorbisStream::GetStreamInfo: substream end\n");
|
||||
samples += last_packet_granulepos - first_packet_granulepos;
|
||||
first_packet_granulepos = chunk_packet->granulepos;
|
||||
}
|
||||
last_packet_granulepos = chunk_packet->granulepos;
|
||||
next_packet = chunk_packet->packetno + 1;
|
||||
}
|
||||
samples += last_packet_granulepos - first_packet_granulepos;
|
||||
format->SetMetaData((void*)&GetHeaderPackets(),sizeof(GetHeaderPackets()));
|
||||
|
||||
// compute frame count and duration from sample count
|
||||
*frameCount = fFrameCount = samples;
|
||||
*duration = fDuration = (1000000LL * samples) / (long long)format->u.encoded_audio.output.frame_rate;
|
||||
|
||||
// restore our position to the start
|
||||
int64 start_frame = 0;
|
||||
bigtime_t start_time = 0;
|
||||
Seek(B_MEDIA_SEEK_TO_FRAME, &start_frame, &start_time);
|
||||
int64 samples = 1000000;
|
||||
*frameCount = samples;
|
||||
*duration = (1000000LL * samples) / (long long)format->u.encoded_audio.output.frame_rate;
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
@@ -14,9 +14,6 @@ public:
|
||||
|
||||
virtual status_t GetStreamInfo(int64 *frameCount, bigtime_t *duration,
|
||||
media_format *format);
|
||||
private:
|
||||
int64 fFrameCount;
|
||||
bigtime_t fDuration;
|
||||
};
|
||||
|
||||
} } // namespace BPrivate::media
|
||||
|
||||
Reference in New Issue
Block a user