917 lines
24 KiB
C
Executable File
917 lines
24 KiB
C
Executable File
/*****************************************************************************
|
|
*
|
|
* This program is free software ; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
* $Id: ogg.c 543 2006-01-07 22:06:24Z picard $
|
|
*
|
|
* The Core Pocket Media Player
|
|
* Copyright (c) 2004-2005 Gabor Kovacs
|
|
*
|
|
****************************************************************************/
|
|
|
|
#include "../common/common.h"
|
|
#include "vorbis.h"
|
|
#include "tremor/ogg.h"
|
|
#include "tremor/ivorbiscodec.h"
|
|
|
|
// OGG and OGM file format
|
|
|
|
#define PACKET_TYPE_HEADER 0x01
|
|
#define PACKET_TYPE_COMMENT 0x03
|
|
#define PACKET_TYPE_CODEBOOK 0x05
|
|
#define PACKET_TYPE_BITS 0x07
|
|
|
|
#define PACKET_IS_SYNCPOINT 0x08
|
|
#define PACKET_LEN_BITS01 0xc0
|
|
#define PACKET_LEN_BITS2 0x02
|
|
|
|
static const char MT_Video[] = "video";
|
|
static const char MT_Audio[] = "audio";
|
|
static const char MT_Text[] = "text";
|
|
|
|
typedef struct oggstream
|
|
{
|
|
format_stream Stream;
|
|
|
|
bool_t Invalid; // not vorbis and not ogm
|
|
bool_t Vorbis; // sending direct oggpackets (not the merged binary data)
|
|
bool_t Native; // speex
|
|
int PacketNo;
|
|
|
|
int64_t MediaTime; // in units
|
|
int64_t MediaRateNum; // in units/tick
|
|
int64_t MediaRateDen;
|
|
int DefaultLen;
|
|
|
|
bool_t NeedMorePage;
|
|
|
|
ogg_stream_state* OggStream;
|
|
ogg_packet OggPacket;
|
|
|
|
vorbis_info Info;
|
|
vorbis_comment Comment;
|
|
|
|
} oggstream;
|
|
|
|
typedef struct ogg
|
|
{
|
|
format_base Format;
|
|
ogg_sync_state* OggSync;
|
|
ogg_page OggPage;
|
|
|
|
} ogg;
|
|
|
|
typedef struct ogm_header_video
|
|
{
|
|
int32_t width;
|
|
int32_t height;
|
|
|
|
} ogm_header_video;
|
|
|
|
typedef struct ogm_header_audio
|
|
{
|
|
int16_t channels;
|
|
int16_t blockalign;
|
|
int32_t avgbytespersec;
|
|
} ogm_header_audio;
|
|
|
|
typedef struct ogm_header
|
|
{
|
|
char streamtype[8];
|
|
char subtype[4];
|
|
|
|
int32_t size;
|
|
|
|
int64_t time_unit;
|
|
int64_t samples_per_unit;
|
|
int32_t default_len;
|
|
|
|
int32_t buffersize;
|
|
int16_t bits_per_sample;
|
|
|
|
union
|
|
{
|
|
ogm_header_video video;
|
|
ogm_header_audio audio;
|
|
|
|
} format;
|
|
|
|
} ogm_header;
|
|
|
|
extern int _vorbis_unpack_comment(vorbis_comment *vc,oggpack_buffer *opb,int eop);
|
|
|
|
static void FreeStream(ogg* p,oggstream* Stream)
|
|
{
|
|
vorbis_comment_clear(&Stream->Comment);
|
|
vorbis_info_clear(&Stream->Info);
|
|
|
|
ogg_packet_release(&Stream->OggPacket);
|
|
if (Stream->OggStream)
|
|
{
|
|
ogg_stream_destroy(Stream->OggStream);
|
|
Stream->OggStream = NULL;
|
|
}
|
|
}
|
|
|
|
static void Done(ogg* p)
|
|
{
|
|
ogg_page_release(&p->OggPage);
|
|
if (p->OggSync)
|
|
{
|
|
ogg_sync_destroy(p->OggSync);
|
|
p->OggSync = NULL;
|
|
}
|
|
}
|
|
|
|
static int AddBuffer(ogg* p,format_buffer* Buffer)
|
|
{
|
|
if (!Buffer)
|
|
return ERR_NEED_MORE_DATA;
|
|
|
|
if (Buffer->Length)
|
|
{
|
|
ogg_buffer* Ptr = ogg_sync_bufferinext(p->OggSync);
|
|
if (!Ptr)
|
|
{
|
|
Format_BufferRelease(&p->Format,Buffer);
|
|
return ERR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
Ptr->ext = &p->Format;
|
|
Ptr->extdata = Buffer;
|
|
Ptr->data = Buffer->Block.Ptr;
|
|
Ptr->size = Buffer->Length;
|
|
ogg_sync_wrote(p->OggSync,Buffer->Length);
|
|
}
|
|
else
|
|
Format_BufferRelease(&p->Format,Buffer);
|
|
|
|
return ERR_NONE;
|
|
}
|
|
|
|
static int Init(ogg* p)
|
|
{
|
|
format_reader* Reader = p->Format.Reader;
|
|
format_buffer* Buffer;
|
|
int i;
|
|
|
|
memset(&p->OggPage,0,sizeof(ogg_page));
|
|
p->OggSync = ogg_sync_create();
|
|
p->Format.TimeStamps = 1;
|
|
if (!p->OggSync)
|
|
return ERR_OUT_OF_MEMORY;
|
|
|
|
Format_ReadBuffer(Reader,0);
|
|
Buffer = Format_BufferRemove(Reader);
|
|
if (!Buffer)
|
|
return ERR_INVALID_DATA;
|
|
|
|
// search for 'OggS' pattern
|
|
for (i=3;i<Buffer->Length;++i)
|
|
if (Buffer->Block.Ptr[i-3]=='O' &&
|
|
Buffer->Block.Ptr[i-2]=='g' &&
|
|
Buffer->Block.Ptr[i-1]=='g' &&
|
|
Buffer->Block.Ptr[i-0]=='S')
|
|
break;
|
|
|
|
if (i==Buffer->Length)
|
|
{
|
|
Format_BufferRelease(&p->Format,Buffer);
|
|
return ERR_INVALID_DATA;
|
|
}
|
|
|
|
return AddBuffer(p,Buffer);
|
|
}
|
|
|
|
static void AfterSeek(ogg* p)
|
|
{
|
|
ogg_sync_reset(p->OggSync);
|
|
}
|
|
|
|
static void ReleaseStream(ogg* p,oggstream* Stream)
|
|
{
|
|
ogg_stream_reset_serialno(Stream->OggStream,Stream->Stream.Id);
|
|
Stream->NeedMorePage = 1;
|
|
Stream->MediaTime = Stream->Stream.Reader->FilePos>0 && Stream->PacketNo>3 ? -1:0;
|
|
}
|
|
|
|
extern void oggext_release(void* ext,void* ext2)
|
|
{
|
|
if (ext && ext2)
|
|
Format_BufferRelease((format_base*)ext,(format_buffer*)ext2);
|
|
}
|
|
|
|
static int ReadPacketTime( ogg* p, format_reader* Reader, format_packet* Packet )
|
|
{
|
|
// only called by CalcDuration
|
|
|
|
format_buffer* Buffer;
|
|
int Bytes;
|
|
ogg_buffer* Ptr;
|
|
ogg_sync_state* Sync = ogg_sync_create();
|
|
ogg_page Page;
|
|
|
|
if (!Sync)
|
|
return ERR_OUT_OF_MEMORY;
|
|
|
|
while ((Buffer = Format_BufferRemove(Reader))!=NULL)
|
|
{
|
|
Ptr = ogg_sync_bufferinext(Sync);
|
|
if (Ptr)
|
|
{
|
|
Ptr->ext = &p->Format;
|
|
Ptr->extdata = Buffer;
|
|
Ptr->data = Buffer->Block.Ptr;
|
|
Ptr->size = Buffer->Length;
|
|
ogg_sync_wrote(Sync,Buffer->Length);
|
|
}
|
|
else
|
|
Format_BufferRelease(&p->Format,Buffer);
|
|
}
|
|
|
|
memset(&Page,0,sizeof(Page));
|
|
|
|
while ((Bytes = ogg_sync_pageseek(Sync,&Page)) != 0)
|
|
{
|
|
if (Bytes > 0)
|
|
{
|
|
int64_t MediaTime = ogg_page_granulepos(&Page);
|
|
int Id = ogg_page_serialno(&Page);
|
|
int i;
|
|
|
|
if (MediaTime != -1)
|
|
for (i=0;i<p->Format.StreamCount;++i)
|
|
if (p->Format.Streams[i]->Id == Id)
|
|
{
|
|
oggstream* s = (oggstream*) p->Format.Streams[i];
|
|
|
|
if (s->MediaRateNum)
|
|
{
|
|
tick_t RefTime = (tick_t)((MediaTime * s->MediaRateDen) / s->MediaRateNum);
|
|
|
|
if (RefTime > Packet->RefTime)
|
|
{
|
|
Packet->Stream = &s->Stream;
|
|
Packet->RefTime = RefTime;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ogg_page_release(&Page);
|
|
ogg_sync_destroy(Sync);
|
|
|
|
return ERR_NONE;
|
|
}
|
|
|
|
static void FrameRate( fraction* f, int64_t Num, int64_t Den )
|
|
{
|
|
while (Num > MAX_INT || Den > MAX_INT)
|
|
{
|
|
Num >>= 1;
|
|
Den >>= 1;
|
|
}
|
|
f->Num = (int)Num;
|
|
f->Den = (int)Den;
|
|
}
|
|
|
|
static bool_t SpeexHeader(ogg* p, oggstream* s, const char* Data, int Length )
|
|
{
|
|
if (Length<80 && strncmp(Data,"Speex ",8)!=0)
|
|
return 0;
|
|
|
|
PacketFormatClear(&s->Stream.Format);
|
|
s->Stream.Format.Type = PACKET_AUDIO;
|
|
s->Stream.Format.Format.Audio.Format = AUDIOFMT_SPEEX;
|
|
s->Stream.Format.Format.Audio.Channels = INT32LE(*(int32_t*)(Data+48));
|
|
s->Stream.Format.ByteRate = INT32LE(*(int32_t*)(Data+52))/8;
|
|
s->Stream.Format.Format.Audio.SampleRate = INT32LE(*(int32_t*)(Data+36));
|
|
|
|
s->MediaRateDen = TICKSPERSEC;
|
|
s->MediaRateNum = s->Stream.Format.Format.Audio.SampleRate;
|
|
s->Native = 1;
|
|
|
|
if (PacketFormatExtra(&s->Stream.Format,Length))
|
|
memcpy(s->Stream.Format.Extra,Data,s->Stream.Format.ExtraLength);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static bool_t OGMHeader(ogg* p, oggstream* s, const char* Data, int Length )
|
|
{
|
|
int i;
|
|
if (Length<40 || (*Data & PACKET_TYPE_BITS) != PACKET_TYPE_HEADER)
|
|
return 0;
|
|
|
|
if (strncmp(Data+1, "Direct Show Samples embedded in Ogg", 35) == 0)
|
|
{
|
|
// old header
|
|
|
|
if (INT32LE(*(int32_t*)(Data+96)) == 0x05589F80)
|
|
{
|
|
PacketFormatClear(&s->Stream.Format);
|
|
s->Stream.Format.Type = PACKET_VIDEO;
|
|
s->Stream.Format.Format.Video.Width = INT32LE(*(int32_t*)(Data+176));
|
|
s->Stream.Format.Format.Video.Height = INT32LE(*(int32_t*)(Data+180));
|
|
s->Stream.Format.Format.Video.Pixel.FourCC = INT32LE(*(int32_t*)(Data+68));
|
|
s->Stream.Format.Format.Video.Pixel.BitCount = INT16LE(*(int16_t*)(Data+182));
|
|
|
|
i = INT16LE(*(int16_t*)(Data+136)); // extrasize
|
|
if (i && PacketFormatExtra(&s->Stream.Format,i))
|
|
memcpy(s->Stream.Format.Extra,Data+142,s->Stream.Format.ExtraLength);
|
|
|
|
s->MediaRateDen = INT64LE(*(int32_t*)(Data+164))*TICKSPERSEC;
|
|
s->MediaRateNum = 10000000;
|
|
s->DefaultLen = 1;
|
|
|
|
FrameRate(&s->Stream.Format.PacketRate,s->MediaRateNum,s->MediaRateDen/TICKSPERSEC);
|
|
return 1;
|
|
}
|
|
|
|
if (INT32LE(*(int32_t*)(Data+96)) == 0x05589F81)
|
|
{
|
|
PacketFormatClear(&s->Stream.Format);
|
|
s->Stream.Format.Type = PACKET_AUDIO;
|
|
s->Stream.Format.Format.Audio.Format = INT16LE(*(int16_t*)(Data+124));
|
|
s->Stream.Format.Format.Audio.Channels = INT16LE(*(int16_t*)(Data+126));
|
|
s->Stream.Format.Format.Audio.BlockAlign = INT16LE(*(int16_t*)(Data+136));
|
|
s->Stream.Format.Format.Audio.Bits = INT16LE(*(int16_t*)(Data+138));
|
|
s->Stream.Format.Format.Audio.SampleRate = INT32LE(*(int32_t*)(Data+128));
|
|
s->Stream.Format.ByteRate = INT32LE(*(int32_t*)(p+132));
|
|
|
|
i = INT16LE(*(int16_t*)(Data+136)); // extrasize
|
|
if (i && PacketFormatExtra(&s->Stream.Format,i))
|
|
memcpy(s->Stream.Format.Extra,Data+142,s->Stream.Format.ExtraLength);
|
|
|
|
s->MediaRateDen = TICKSPERSEC;
|
|
s->MediaRateNum = INT32LE(*(int32_t*)(Data+128));
|
|
s->DefaultLen = 1;
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
if (Length >= sizeof(ogm_header)+1)
|
|
{
|
|
ogm_header Head;
|
|
memcpy(&Head,Data+1,sizeof(Head));
|
|
|
|
// new header
|
|
if (strncmp(Head.streamtype, MT_Video, strlen(MT_Video)) == 0)
|
|
{
|
|
PacketFormatClear(&s->Stream.Format);
|
|
s->Stream.Format.Type = PACKET_VIDEO;
|
|
s->Stream.Format.Format.Video.Width = INT32LE(Head.format.video.width);
|
|
s->Stream.Format.Format.Video.Height = INT32LE(Head.format.video.height);
|
|
s->Stream.Format.Format.Video.Pixel.FourCC = INT32LE(*(int32_t*)Head.subtype);
|
|
s->Stream.Format.Format.Video.Pixel.BitCount = INT16LE(Head.bits_per_sample);
|
|
|
|
s->MediaRateDen = INT64LE(Head.time_unit)*TICKSPERSEC;
|
|
s->MediaRateNum = INT64LE(Head.samples_per_unit) * 10000000;
|
|
s->DefaultLen = INT32LE(Head.default_len);
|
|
|
|
FrameRate(&s->Stream.Format.PacketRate,s->MediaRateNum,s->MediaRateDen/TICKSPERSEC);
|
|
i = Length - (sizeof(ogm_header)+1);
|
|
if (i && PacketFormatExtra(&s->Stream.Format,i))
|
|
memcpy(s->Stream.Format.Extra,Data+1+sizeof(ogm_header),s->Stream.Format.ExtraLength);
|
|
return 1;
|
|
}
|
|
|
|
if (strncmp(Head.streamtype, MT_Audio, strlen(MT_Audio)) == 0)
|
|
{
|
|
PacketFormatClear(&s->Stream.Format);
|
|
s->Stream.Format.Type = PACKET_AUDIO;
|
|
s->Stream.Format.Format.Audio.Format = 0;
|
|
for (i=0;i<4;++i)
|
|
if (Head.subtype[i])
|
|
s->Stream.Format.Format.Audio.Format = s->Stream.Format.Format.Audio.Format*16+Hex(Head.subtype[i]);
|
|
s->Stream.Format.Format.Audio.Channels = INT16LE(Head.format.audio.channels);
|
|
s->Stream.Format.Format.Audio.Bits = INT16LE(Head.bits_per_sample);
|
|
s->Stream.Format.Format.Audio.BlockAlign = INT16LE(Head.format.audio.blockalign);
|
|
s->Stream.Format.ByteRate = INT32LE(Head.format.audio.avgbytespersec);
|
|
s->Stream.Format.Format.Audio.SampleRate = (int)INT64LE(Head.samples_per_unit);
|
|
|
|
s->MediaRateDen = INT64LE(Head.time_unit)*TICKSPERSEC;
|
|
s->MediaRateNum = INT64LE(Head.samples_per_unit) * 10000000;
|
|
s->DefaultLen = INT32LE(Head.default_len);
|
|
|
|
i = Length - (sizeof(ogm_header)+1);
|
|
if (i && PacketFormatExtra(&s->Stream.Format,i))
|
|
memcpy(s->Stream.Format.Extra,Data+1+sizeof(ogm_header),s->Stream.Format.ExtraLength);
|
|
return 1;
|
|
}
|
|
|
|
if (strncmp(Data+1, MT_Text, strlen(MT_Text)) == 0)
|
|
{
|
|
PacketFormatClear(&s->Stream.Format);
|
|
s->Stream.Format.Type = PACKET_SUBTITLE;
|
|
s->Stream.Format.Format.Subtitle.FourCC = SUBTITLE_OEM; //???
|
|
|
|
s->MediaRateDen = INT64LE(Head.time_unit)*TICKSPERSEC;
|
|
s->MediaRateNum = INT64LE(Head.samples_per_unit) * 10000000;
|
|
s->DefaultLen = INT32LE(Head.default_len);
|
|
|
|
i = Length - (sizeof(ogm_header)+1);
|
|
if (i && PacketFormatExtra(&s->Stream.Format,i))
|
|
memcpy(s->Stream.Format.Extra,Data+1+sizeof(ogm_header),s->Stream.Format.ExtraLength);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void SendComments(oggstream* Stream)
|
|
{
|
|
tchar_t s[256];
|
|
if (Stream->Stream.Comment.Node)
|
|
{
|
|
int No;
|
|
for (No=0;No<Stream->Comment.comments;++No)
|
|
{
|
|
UTF8ToTcs(s,TSIZEOF(s),Stream->Comment.user_comments[No]);
|
|
Stream->Stream.Comment.Node->Set(Stream->Stream.Comment.Node,Stream->Stream.Comment.No,s,sizeof(s));
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool_t VorbisHeader(ogg* p,oggstream* s)
|
|
{
|
|
tchar_t URL[MAXPATH];
|
|
|
|
if (vorbis_synthesis_headerin(&s->Info,&s->Comment,&s->OggPacket)<0)
|
|
return 0;
|
|
|
|
PacketFormatClear(&s->Stream.Format);
|
|
s->Stream.Format.Type = PACKET_AUDIO;
|
|
s->Stream.Format.Format.Audio.Channels = s->Info.channels;
|
|
s->Stream.Format.Format.Audio.SampleRate = s->Info.rate;
|
|
s->Stream.Format.ByteRate = s->Info.bitrate_nominal >> 3;
|
|
|
|
if (p->Format.Reader->Input->Get(p->Format.Reader->Input,STREAM_URL,URL,sizeof(URL))==ERR_NONE &&
|
|
CheckExts(URL,T("ogg:A")) != 0)
|
|
s->Stream.Format.Format.Audio.Format = AUDIOFMT_VORBIS_INTERNAL_AUDIO;
|
|
else
|
|
s->Stream.Format.Format.Audio.Format = AUDIOFMT_VORBIS_INTERNAL_VIDEO;
|
|
|
|
s->Vorbis = 1;
|
|
s->MediaRateDen = TICKSPERSEC;
|
|
s->MediaRateNum = s->Info.rate;
|
|
return 1;
|
|
}
|
|
|
|
static int FillQueue(ogg* p,format_reader* Reader)
|
|
{
|
|
for (;;)
|
|
{
|
|
int Bytes = ogg_sync_pageseek(p->OggSync,&p->OggPage);
|
|
|
|
if (Bytes == 0) // need more data
|
|
{
|
|
int Result;
|
|
format_buffer* Buffer;
|
|
|
|
if (!Reader->BufferAvailable && (!p->Format.SyncMode || p->Format.SyncRead<=0))
|
|
return ERR_NEED_MORE_DATA;
|
|
|
|
Buffer = Format_BufferRemove(Reader);
|
|
if (!Buffer && p->Format.SyncMode && p->Format.SyncRead>0 && Format_ReadBuffer(Reader,0))
|
|
Buffer = Format_BufferRemove(Reader);
|
|
|
|
Result = AddBuffer(p,Buffer);
|
|
if (Result != ERR_NONE)
|
|
return Result;
|
|
}
|
|
else
|
|
if (Bytes < 0)
|
|
Reader->FilePos -= Bytes;
|
|
else
|
|
if (Bytes > 0)
|
|
{
|
|
int StreamNo;
|
|
oggstream* s;
|
|
int Id;
|
|
|
|
Reader->FilePos += Bytes;
|
|
|
|
Id = ogg_page_serialno(&p->OggPage);
|
|
|
|
DEBUG_MSG4(DEBUG_FORMAT,T("OGG Page id:%d size:%d gran:%d filepos:%d"),Id,p->OggPage.body_len,(int)ogg_page_granulepos(&p->OggPage),Reader->FilePos - Bytes);
|
|
|
|
for (StreamNo=0;StreamNo<p->Format.StreamCount;++StreamNo)
|
|
if (p->Format.Streams[StreamNo]->Id == Id)
|
|
break;
|
|
|
|
if (StreamNo==p->Format.StreamCount)
|
|
{
|
|
// check for restarted audio http streaming (comments changed)
|
|
if (p->Format.StreamCount==1 &&
|
|
p->Format.Streams[0]->Format.Type == PACKET_AUDIO &&
|
|
p->Format.Streams[0]->LastTime>0)
|
|
{
|
|
StreamNo = 0;
|
|
s = (oggstream*) p->Format.Streams[0];
|
|
if (s->Vorbis)
|
|
{
|
|
// vorbis decoder have to release s->Info
|
|
s->Stream.Format.Extra = NULL;
|
|
s->Stream.Format.ExtraLength = 0;
|
|
ConnectionUpdate((node*)&p->Format,FORMAT_STREAM+0,s->Stream.Pin.Node,s->Stream.Pin.No);
|
|
}
|
|
FreeStream(p,s);
|
|
}
|
|
else
|
|
{
|
|
s = (oggstream*) Format_AddStream(&p->Format,sizeof(oggstream));
|
|
if (!s) continue;
|
|
}
|
|
|
|
// init stream
|
|
s->Stream.Id = Id;
|
|
s->OggStream = ogg_stream_create(Id);
|
|
s->NeedMorePage = 1;
|
|
s->MediaTime = 0;
|
|
s->Invalid = 0;
|
|
s->Vorbis = 0;
|
|
s->Native = 0;
|
|
s->PacketNo = 0;
|
|
|
|
vorbis_info_init(&s->Info);
|
|
vorbis_comment_init(&s->Comment);
|
|
}
|
|
|
|
s = (oggstream*) p->Format.Streams[StreamNo];
|
|
|
|
if (s->Invalid) // drop invalid streams
|
|
continue;
|
|
|
|
if (s->PacketNo>=3)
|
|
{
|
|
if (!s->Stream.Pin.Node) // drop unused streams
|
|
continue;
|
|
|
|
if (p->Format.InSeek)
|
|
{
|
|
// reftime needed for SeekByPacket
|
|
if ((s->MediaTime = ogg_page_granulepos(&p->OggPage)) != -1)
|
|
{
|
|
// no need for GlobalOffset here
|
|
s->Stream.LastTime = (tick_t)(s->MediaTime * s->MediaRateDen / s->MediaRateNum);
|
|
if (s->Stream.Format.Type == PACKET_AUDIO)
|
|
{
|
|
s->Stream.LastTime += p->Format.AVOffset;
|
|
if (s->Stream.LastTime < 0)
|
|
s->Stream.LastTime = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// add page to stream
|
|
if (ogg_stream_pagein(s->OggStream,&p->OggPage) >= 0)
|
|
{
|
|
if (s->PacketNo<3) // header packet needed?
|
|
{
|
|
int i = ogg_stream_packetout(s->OggStream,&s->OggPacket);
|
|
if (i == 0) // stream needs more pages
|
|
continue;
|
|
|
|
if (++s->PacketNo==1) // first packet?
|
|
{
|
|
ogg_reference* Ref;
|
|
const void* Data;
|
|
int Length;
|
|
|
|
if (i < 0)
|
|
{
|
|
// first header packet is a must have
|
|
s->Invalid = 1;
|
|
continue;
|
|
}
|
|
|
|
if (p->Format.UseBufferBlock)
|
|
{
|
|
for (Length=0,Ref=s->OggPacket.packet;Ref;Ref=Ref->next)
|
|
Length += Ref->length;
|
|
|
|
if (s->Stream.BufferBlockLength<Length && !Format_AllocBufferBlock(&p->Format,&s->Stream,Length))
|
|
{
|
|
Length = 0;
|
|
Data = NULL;
|
|
}
|
|
else
|
|
{
|
|
for (Length=0,Ref=s->OggPacket.packet;Ref;Ref=Ref->next)
|
|
{
|
|
WriteBlock(&s->Stream.BufferBlock,Length,Ref->buffer->data + Ref->begin,Ref->length);
|
|
Length += Ref->length;
|
|
}
|
|
Data = s->Stream.BufferBlock.Ptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BufferDrop(&s->Stream.BufferMem);
|
|
for (Ref=s->OggPacket.packet;Ref;Ref=Ref->next)
|
|
BufferWrite(&s->Stream.BufferMem,Ref->buffer->data + Ref->begin, Ref->length, 16384);
|
|
Data = s->Stream.BufferMem.Data;
|
|
Length = s->Stream.BufferMem.WritePos;
|
|
}
|
|
|
|
if (OGMHeader(p,s,(char*)Data,Length) || SpeexHeader(p,s,(char*)Data,Length))
|
|
{
|
|
PacketFormatDefault(&s->Stream.Format);
|
|
s->PacketNo = 3; // no more headers
|
|
}
|
|
else
|
|
if (!VorbisHeader(p,s))
|
|
{
|
|
s->Invalid = 1;
|
|
continue;
|
|
}
|
|
|
|
while (s->MediaRateNum > (1<<30))
|
|
{
|
|
s->MediaRateDen >>= 1;
|
|
s->MediaRateNum >>= 1;
|
|
}
|
|
|
|
Format_PrepairStream(&p->Format,&s->Stream);
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
assert(s->Vorbis);
|
|
|
|
// error in second or third header packet will not cause fatal error
|
|
vorbis_synthesis_headerin(&s->Info,&s->Comment,&s->OggPacket);
|
|
|
|
if (s->PacketNo == 3)
|
|
{
|
|
// got the three header packets: reinit codec with vorbis_info
|
|
s->Stream.Format.Extra = &s->Info;
|
|
s->Stream.Format.ExtraLength = -1;
|
|
ConnectionUpdate((node*)&p->Format,FORMAT_STREAM+StreamNo,s->Stream.Pin.Node,s->Stream.Pin.No);
|
|
SendComments(s);
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
s->NeedMorePage = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return ERR_NONE;
|
|
}
|
|
|
|
static int Process(ogg* p,oggstream* Stream)
|
|
{
|
|
int Result;
|
|
int No;
|
|
int Burst = 1;
|
|
|
|
if (Stream->Invalid)
|
|
return ERR_NONE;
|
|
|
|
if (Stream->Stream.Pending)
|
|
{
|
|
Result = Format_Send(&p->Format,&Stream->Stream);
|
|
|
|
if (Result == ERR_BUFFER_FULL || Result == ERR_SYNCED)
|
|
return Result;
|
|
}
|
|
|
|
// process a limited number of packets at once (other streams need cpu time too)
|
|
|
|
Result = ERR_NONE;
|
|
Burst = Stream->Stream.PacketBurst;
|
|
|
|
for (No=0;No<Burst;++No)
|
|
{
|
|
while (Stream->NeedMorePage)
|
|
{
|
|
Result = FillQueue(p,Stream->Stream.Reader);
|
|
|
|
if (Result == ERR_NEED_MORE_DATA && Stream->Stream.Reader->NoMoreInput)
|
|
Result = Format_CheckEof(&p->Format,&Stream->Stream);
|
|
|
|
if (Result != ERR_NONE || (p->Format.Bench && Stream->NeedMorePage))
|
|
return Result;
|
|
}
|
|
|
|
// sync: only SyncStream is processed
|
|
if (p->Format.SyncMode && Stream != (oggstream*)p->Format.SyncStream)
|
|
return ERR_NEED_MORE_DATA;
|
|
|
|
Result = ogg_stream_packetout(Stream->OggStream,&Stream->OggPacket);
|
|
if (Result == 0)
|
|
{
|
|
// stream needs more pages
|
|
Stream->NeedMorePage = 1;
|
|
if (p->Format.Bench) break; // benchmark mode: no additional pages are added in this loop
|
|
--No;
|
|
continue;
|
|
}
|
|
if (Result < 0) // packet hole or span
|
|
continue;
|
|
|
|
DEBUG_MSG3(DEBUG_FORMAT,T("OGG Packet id:%d size:%d gran:%d"),Stream->Stream.Id,Stream->OggPacket.bytes,(int)Stream->OggPacket.granulepos);
|
|
|
|
if (!Stream->Vorbis && Stream->PacketNo==3)
|
|
{
|
|
// non vorbis: second packet can be a comment (after first header)
|
|
++Stream->PacketNo;
|
|
|
|
if (Stream->Native)
|
|
{
|
|
oggpack_buffer opb;
|
|
oggpack_readinit(&opb,Stream->OggPacket.packet);
|
|
if (_vorbis_unpack_comment(&Stream->Comment,&opb,0)>=0)
|
|
SendComments(Stream);
|
|
continue; // always assume comment packet
|
|
}
|
|
else
|
|
{
|
|
Stream->Info.rate=1;
|
|
if (vorbis_synthesis_headerin(&Stream->Info,&Stream->Comment,&Stream->OggPacket) >= 0)
|
|
{
|
|
SendComments(Stream);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// build output packet
|
|
|
|
if (Stream->OggPacket.granulepos >= 0)
|
|
Stream->MediaTime = Stream->OggPacket.granulepos;
|
|
|
|
if (Stream->MediaTime >= 0)
|
|
{
|
|
tick_t t = (tick_t)((Stream->MediaTime * Stream->MediaRateDen) / Stream->MediaRateNum);
|
|
t += p->Format.GlobalOffset;
|
|
if (Stream->Stream.Format.Type == PACKET_AUDIO)
|
|
{
|
|
t += p->Format.AVOffset;
|
|
if (t<0) t=0;
|
|
}
|
|
if (Stream->Stream.LastTime>t && t-p->Format.GlobalOffset<TICKSPERSEC)
|
|
Format_TimeStampRestarted(&p->Format,&Stream->Stream,&t);
|
|
Stream->Stream.LastTime = Stream->Stream.Packet.RefTime = t;
|
|
}
|
|
else
|
|
{
|
|
if (Stream->Stream.LastTime < 0)
|
|
continue;
|
|
Stream->Stream.Packet.RefTime = TIME_UNKNOWN;
|
|
}
|
|
|
|
if (!Stream->Vorbis)
|
|
{
|
|
ogg_reference* Ref;
|
|
const uint8_t* Data;
|
|
int Length;
|
|
int Len;
|
|
int PacketTime;
|
|
|
|
// merge buffers
|
|
|
|
if (p->Format.UseBufferBlock)
|
|
{
|
|
for (Length=0,Ref=Stream->OggPacket.packet;Ref;Ref=Ref->next)
|
|
Length += Ref->length;
|
|
|
|
if (Stream->Stream.BufferBlockLength<Length && !Format_AllocBufferBlock(&p->Format,&Stream->Stream,Length))
|
|
{
|
|
Length = 0;
|
|
Data = NULL;
|
|
}
|
|
else
|
|
{
|
|
for (Length=0,Ref=Stream->OggPacket.packet;Ref;Ref=Ref->next)
|
|
{
|
|
WriteBlock(&Stream->Stream.BufferBlock,Length,Ref->buffer->data + Ref->begin,Ref->length);
|
|
Length += Ref->length;
|
|
}
|
|
Data = Stream->Stream.BufferBlock.Ptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BufferDrop(&Stream->Stream.BufferMem);
|
|
for (Ref=Stream->OggPacket.packet;Ref;Ref=Ref->next)
|
|
BufferWrite(&Stream->Stream.BufferMem,Ref->buffer->data + Ref->begin, Ref->length, 16384);
|
|
Data = (const uint8_t*)Stream->Stream.BufferMem.Data;
|
|
Length = Stream->Stream.BufferMem.WritePos;
|
|
}
|
|
|
|
if (Stream->Native)
|
|
{
|
|
Stream->Stream.Packet.Data[0] = Data;
|
|
Stream->Stream.Packet.Length = Length;
|
|
Stream->MediaTime = -1;
|
|
}
|
|
else
|
|
{
|
|
// process OGM information
|
|
if (*Data & PACKET_TYPE_HEADER)
|
|
continue; // not data packet
|
|
|
|
Len = (*Data & PACKET_LEN_BITS01) >> 6;
|
|
Len |= (*Data & PACKET_LEN_BITS2) << 1;
|
|
|
|
Stream->Stream.Packet.Key = (*Data & PACKET_IS_SYNCPOINT) != 0;
|
|
Stream->Stream.Packet.Data[0] = Data + 1 + Len;
|
|
Stream->Stream.Packet.Length = Length - 1 - Len;
|
|
|
|
if (!Len)
|
|
PacketTime = Stream->DefaultLen;
|
|
else
|
|
for (PacketTime=0;Len;--Len)
|
|
{
|
|
PacketTime <<= 8;
|
|
PacketTime |= Data[Len];
|
|
}
|
|
|
|
Stream->MediaTime += PacketTime;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// send oggpacket directly
|
|
if (!Stream->OggPacket.packet)
|
|
continue;
|
|
|
|
Stream->Stream.Packet.Data[0] = &Stream->OggPacket;
|
|
Stream->Stream.Packet.Length = sizeof(Stream->OggPacket);
|
|
Stream->MediaTime = -1;
|
|
}
|
|
Stream->Stream.Pending = 1;
|
|
|
|
Result = Format_Send(&p->Format,&Stream->Stream);
|
|
|
|
if (Result == ERR_BUFFER_FULL || Result == ERR_SYNCED)
|
|
break;
|
|
|
|
if (Stream->Stream.State.DropLevel==2)
|
|
Burst = Stream->Stream.PacketBurst*2; // try to catch up
|
|
}
|
|
|
|
// buffer full: there was possibly some processing so don't allow sleeping
|
|
// need data: burst count exceeded, but it doesn't mean there is no data left in buffers
|
|
if (Result == ERR_BUFFER_FULL || Result == ERR_NEED_MORE_DATA)
|
|
Result = ERR_NONE;
|
|
|
|
return Result;
|
|
}
|
|
|
|
static int Create(ogg* p)
|
|
{
|
|
p->Format.Init = (fmtfunc)Init;
|
|
p->Format.Done = (fmtvoid)Done;
|
|
p->Format.Seek = (fmtseek)Format_SeekByPacket;
|
|
p->Format.FillQueue = (fmtfill)FillQueue;
|
|
p->Format.ReadPacket = (fmtreadpacket)ReadPacketTime;
|
|
p->Format.ReleaseStream = (fmtstream)ReleaseStream;
|
|
p->Format.AfterSeek = (fmtvoid)AfterSeek;
|
|
p->Format.FreeStream = (fmtstream)FreeStream;
|
|
p->Format.Process = (fmtstreamprocess)Process;
|
|
p->Format.DisableReader = 1;
|
|
p->Format.Sended = NULL;
|
|
return ERR_NONE;
|
|
}
|
|
|
|
static const nodedef OGG =
|
|
{
|
|
sizeof(ogg),
|
|
OGG_ID,
|
|
FORMATBASE_CLASS,
|
|
PRI_DEFAULT,
|
|
(nodecreate)Create,
|
|
};
|
|
|
|
void OGG_Init()
|
|
{
|
|
NodeRegisterClass(&OGG);
|
|
}
|
|
|
|
void OGG_Done()
|
|
{
|
|
NodeUnRegisterClass(OGG_ID);
|
|
}
|