2826 lines
66 KiB
C
Executable File
2826 lines
66 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: format_base.c 611 2006-01-25 22:01:51Z picard $
|
|
*
|
|
* The Core Pocket Media Player
|
|
* Copyright (c) 2004-2005 Gabor Kovacs
|
|
*
|
|
****************************************************************************/
|
|
|
|
#include "common.h"
|
|
|
|
#define DROP_SHOW 8
|
|
|
|
#define ENDCHUNKS (128*1024/BLOCKSIZE)
|
|
#define SKIPDISTANCE 64*1024
|
|
|
|
#define SYNCREAD 6
|
|
|
|
#define SEEKTRIES 4
|
|
#define SEEKMAXREAD 256*1024
|
|
|
|
#define PROCESSMINAUDIO BLOCKSIZE
|
|
#define PROCESSMINVIDEO 128*1024
|
|
|
|
#define READER_BONUS 256*1024
|
|
|
|
int FormatBaseEnum(format_base* p,int* No,datadef* Param)
|
|
{
|
|
if (FormatEnum(p,No,Param) == ERR_NONE)
|
|
return ERR_NONE;
|
|
|
|
if (*No < p->TotalCount)
|
|
{
|
|
memset(Param,0,sizeof(datadef));
|
|
Param->Class = FORMAT_CLASS;
|
|
Param->Size = sizeof(pin);
|
|
Param->No = FORMAT_STREAM + *No;
|
|
Param->Type = TYPE_PACKET;
|
|
Param->Flags = DF_OUTPUT;
|
|
*No = 0;
|
|
return ERR_NONE;
|
|
}
|
|
*No -= p->TotalCount;
|
|
|
|
if (*No < p->TotalCount)
|
|
{
|
|
memset(Param,0,sizeof(datadef));
|
|
Param->Class = FORMAT_CLASS;
|
|
Param->Size = sizeof(pin);
|
|
Param->No = FORMAT_COMMENT + *No;
|
|
Param->Type = TYPE_COMMENT;
|
|
Param->Flags = DF_OUTPUT;
|
|
*No = 0;
|
|
return ERR_NONE;
|
|
}
|
|
*No -= p->TotalCount;
|
|
|
|
return ERR_INVALID_PARAM;
|
|
}
|
|
|
|
static INLINE void Format_BufferInsert(format_reader* p,format_buffer* Buffer)
|
|
{
|
|
Buffer->Next = NULL;
|
|
if (p->BufferLast)
|
|
p->BufferLast->Next = Buffer;
|
|
else
|
|
p->BufferFirst = Buffer;
|
|
p->BufferLast = Buffer;
|
|
p->BufferAvailable += Buffer->Length;
|
|
}
|
|
|
|
format_buffer* Format_BufferAlloc(format_base*,int Mode); //0:optional 1:drop 2:new alloc
|
|
format_packet* Format_PacketAlloc(format_base*);
|
|
void Format_PacketRelease(format_base*, format_packet*);
|
|
bool_t Format_ReadBuffer(format_reader*, bool_t ToRead);
|
|
|
|
int Format_Seek(format_base* p,filepos_t Pos,int Origin)
|
|
{
|
|
int Result = p->Reader->Seek(p->Reader,Pos,Origin);
|
|
if (Result == ERR_NONE)
|
|
Format_AfterSeek(p);
|
|
return Result;
|
|
}
|
|
|
|
int Format_SeekForce(format_base* p,filepos_t Pos,int Origin)
|
|
{
|
|
int Result;
|
|
filepos_t Save = p->Reader[0].FilePos;
|
|
p->Reader[0].FilePos = -1;
|
|
Result = Format_Seek(p,Pos,Origin);
|
|
if (Result != ERR_NONE)
|
|
p->Reader[0].FilePos = Save;
|
|
return Result;
|
|
}
|
|
|
|
int Format_SeekByPacket(format_base* p,tick_t Time,filepos_t FilePos,bool_t PrevKey)
|
|
{
|
|
int Try;
|
|
int LastTime;
|
|
int LastPos;
|
|
format_stream* Stream = NULL;
|
|
|
|
if (FilePos < 0)
|
|
{
|
|
Time -= SHOWAHEAD; // we should go a little earlier
|
|
if (Time > 0)
|
|
{
|
|
if (p->FileSize<0 || p->Duration<0)
|
|
return ERR_NOT_SUPPORTED;
|
|
|
|
FilePos = Scale(p->FileSize,Time,p->Duration);
|
|
}
|
|
else
|
|
FilePos = 0;
|
|
}
|
|
|
|
if (Time>0)
|
|
Stream = Format_DefSyncStream(p,p->Reader);
|
|
|
|
p->GlobalOffset = 0;
|
|
p->InSeek = 1;
|
|
|
|
LastPos = -1;
|
|
LastTime = -1;
|
|
|
|
for (Try=0;Try<SEEKTRIES;++Try) // maximum iterations
|
|
{
|
|
if (FilePos < p->SeekByPacket_DataStart)
|
|
FilePos = p->SeekByPacket_DataStart;
|
|
if (p->SeekByPacket_BlockAlign > 1)
|
|
FilePos -= (FilePos - p->SeekByPacket_DataStart) % p->SeekByPacket_BlockAlign;
|
|
|
|
if (Format_Seek(p,FilePos,SEEK_SET) != ERR_NONE)
|
|
{
|
|
p->InSeek = 0;
|
|
return ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
if (Stream)
|
|
{
|
|
// check if really seeked to the right time (and adjust if needed)
|
|
|
|
format_reader* Reader = p->Reader;
|
|
int SeekRead = FilePos + SEEKMAXREAD;
|
|
|
|
while (Reader->FilePos<SeekRead)
|
|
{
|
|
Format_ReadBuffer(Reader,0); // simulate input thread
|
|
|
|
if (p->FillQueue(p,Reader) == ERR_END_OF_FILE)
|
|
break;
|
|
|
|
if (Stream->LastTime >= 0)
|
|
{
|
|
DEBUG_MSG3(DEBUG_FORMAT,T("SeekByPacket to:%d current:%d filepos:%d"),Time,Stream->LastTime,FilePos);
|
|
|
|
if (!p->TimeStamps)
|
|
{
|
|
// adjust GlobalOffset
|
|
p->GlobalOffset = Time - Stream->LastTime;
|
|
if (p->GlobalOffset>0)
|
|
{
|
|
// need to adjust all stream packets
|
|
int No;
|
|
for (No=0;No<p->StreamCount;++No)
|
|
{
|
|
format_packet* i;
|
|
format_stream* s = p->Streams[No];
|
|
if (s->LastTime >= 0)
|
|
s->LastTime += p->GlobalOffset;
|
|
for (i=s->PacketFirst;i;i=i->Next)
|
|
if (i->RefTime >= 0)
|
|
i->RefTime += p->GlobalOffset;
|
|
}
|
|
}
|
|
else
|
|
p->GlobalOffset=0; // can't be negative
|
|
SeekRead = -1; // accept
|
|
break;
|
|
}
|
|
|
|
if (Time>=Stream->LastTime && (Time-Stream->LastTime) <= (TICKSPERSEC/2))
|
|
FilePos = -1; // accept
|
|
else
|
|
{
|
|
int Move;
|
|
|
|
if (LastPos >= 0)
|
|
Move = Scale(FilePos-LastPos,Time-Stream->LastTime,Stream->LastTime-LastTime);
|
|
else
|
|
Move = Scale(p->FileSize,Time-Stream->LastTime,p->Duration);
|
|
|
|
if (Move < 0)
|
|
Move = (Move*9)/8;
|
|
else
|
|
Move = (Move*15)/16;
|
|
|
|
if (Move > 64*1024 || Move < -64*1024)
|
|
{
|
|
LastTime = Stream->LastTime;
|
|
LastPos = FilePos;
|
|
}
|
|
else
|
|
LastPos = -1;
|
|
|
|
FilePos += Move;
|
|
}
|
|
|
|
if (FilePos < 0 || FilePos >= p->FileSize)
|
|
SeekRead = -1; // accept
|
|
break;
|
|
}
|
|
|
|
if (!Reader->BufferAvailable && !Reader->ReadBuffer && Reader->NoMoreInput)
|
|
break; // end of file
|
|
}
|
|
|
|
if (Reader->FilePos<SeekRead)
|
|
continue; // try again
|
|
}
|
|
break;
|
|
}
|
|
|
|
p->InSeek = 0;
|
|
|
|
return ERR_NONE;
|
|
}
|
|
|
|
format_buffer* Format_BufferRemoveLast(format_reader* Reader)
|
|
{
|
|
format_buffer *Buffer,*Tail;
|
|
LockEnter(Reader->Format->BufferLock);
|
|
Buffer = Reader->BufferLast;
|
|
if (Buffer)
|
|
{
|
|
Tail = Reader->BufferFirst;
|
|
if (Tail == Buffer)
|
|
Reader->BufferFirst = Tail = NULL;
|
|
else
|
|
{
|
|
while (Tail && Tail->Next != Buffer)
|
|
Tail = Tail->Next;
|
|
if (Tail)
|
|
Tail->Next = NULL;
|
|
}
|
|
Reader->BufferLast = Tail;
|
|
Reader->BufferAvailable -= Buffer->Length;
|
|
assert(Reader->BufferAvailable >= 0);
|
|
}
|
|
LockLeave(Reader->Format->BufferLock);
|
|
return Buffer;
|
|
}
|
|
|
|
int Format_Hibernate(format_base* p, int Mode)
|
|
{
|
|
bool_t Changed = 0;
|
|
|
|
if (p->InputLock)
|
|
{
|
|
int No;
|
|
format_buffer *Buffer;
|
|
|
|
LockEnter(p->InputLock);
|
|
LockEnter(p->BufferLock);
|
|
|
|
#ifndef TARGET_PALMOS
|
|
|
|
if (Mode == HIBERNATE_HARD)
|
|
{
|
|
format_reader *Reader = p->Reader;
|
|
filepos_t Seek = 0;
|
|
|
|
if (Reader->InputBuffer)
|
|
Seek += Reader->InputBuffer->Length;
|
|
|
|
for (Buffer = Reader->BufferFirst;Buffer;Buffer = Buffer->Next)
|
|
Seek += Buffer->Length;
|
|
|
|
if (Seek && Reader->Input && Reader->Input->Seek(Reader->Input,-Seek,SEEK_CUR)>=0)
|
|
{
|
|
Reader->NoMoreInput = 0;
|
|
|
|
if (Reader->InputBuffer)
|
|
{
|
|
Format_BufferRelease(p,Reader->InputBuffer);
|
|
Reader->InputBuffer = NULL;
|
|
}
|
|
|
|
while ((Buffer = Format_BufferRemoveLast(Reader)) != NULL)
|
|
Format_BufferRelease(p,Buffer);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
while (p->FreeBuffers)
|
|
{
|
|
Buffer = p->FreeBuffers;
|
|
p->FreeBuffers = Buffer->Next;
|
|
FreeBlock(&Buffer->Block);
|
|
free(Buffer);
|
|
Changed = 1;
|
|
}
|
|
|
|
if (!Changed)
|
|
for (No=0;No<MAXREADER;++No)
|
|
for (Buffer = p->Reader[No].BufferFirst;Buffer;Buffer = Buffer->Next)
|
|
if (SetHeapBlock(BLOCKSIZE,&Buffer->Block,HEAP_STORAGE))
|
|
Changed = 1;
|
|
|
|
LockLeave(p->BufferLock);
|
|
LockLeave(p->InputLock);
|
|
}
|
|
|
|
return Changed ? ERR_NONE : ERR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
static bool_t DropBuffer(format_base* p)
|
|
{
|
|
bool_t Dropped = 0;
|
|
int No=0;
|
|
|
|
for (No=0;No<p->StreamCount;++No)
|
|
{
|
|
format_stream* Stream = p->Streams[No];
|
|
if (Stream->PacketFirst)
|
|
{
|
|
format_packet* Packet = Stream->PacketFirst;
|
|
Stream->PacketFirst = Packet->Next;
|
|
if (Stream->PacketLast == Packet)
|
|
Stream->PacketLast = NULL;
|
|
|
|
Format_PacketRelease(p,Packet);
|
|
|
|
Stream->Pending = 0;
|
|
Stream->State.DropLevel = 2;
|
|
Stream->DropCount = DROP_SHOW;
|
|
Dropped = 1;
|
|
}
|
|
}
|
|
|
|
return Dropped;
|
|
}
|
|
|
|
format_buffer* Format_BufferAlloc(format_base* p,int Mode)
|
|
{
|
|
format_buffer* Buffer;
|
|
|
|
retry:
|
|
Buffer = p->FreeBuffers;
|
|
if (!Buffer)
|
|
{
|
|
if (p->BufferUsed < p->BufferSize || Mode==2)
|
|
{
|
|
block Block;
|
|
if (AllocBlock(BLOCKSIZE,&Block,Mode!=2,HEAP_ANY))
|
|
{
|
|
Buffer = (format_buffer*) malloc(sizeof(format_buffer));
|
|
if (Buffer)
|
|
Buffer->Block = Block;
|
|
else
|
|
FreeBlock(&Block);
|
|
}
|
|
}
|
|
else
|
|
if (Mode==0)
|
|
return NULL;
|
|
|
|
if (!Buffer)
|
|
{
|
|
if (Mode==1 && DropBuffer(p))
|
|
goto retry;
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
assert(Buffer->RefCount==0);
|
|
p->FreeBuffers = Buffer->Next;
|
|
}
|
|
|
|
Buffer->Length = 0;
|
|
Buffer->Id = p->BufferId++;
|
|
Buffer->RefCount = 1;
|
|
Buffer->Next = NULL;
|
|
++p->BufferUsed;
|
|
|
|
return Buffer;
|
|
}
|
|
|
|
static int SumByteRate(format_base* p)
|
|
{
|
|
// try to sum stream byterates
|
|
int No;
|
|
int ByteRate = 0;
|
|
|
|
for (No=0;No<p->StreamCount;++No)
|
|
{
|
|
packetformat Format;
|
|
p->Format.Get(p,(FORMAT_STREAM|PIN_FORMAT)+No,&Format,sizeof(Format));
|
|
if (Format.ByteRate<=0)
|
|
{
|
|
if (p->Streams[No]->Pin.Node && p->ProcessTime == TIME_SYNC)
|
|
{
|
|
p->ProcessTime = 0;
|
|
p->Process(p,p->Streams[No]); // try processing
|
|
p->ProcessTime = TIME_SYNC;
|
|
}
|
|
p->Format.Get(p,(FORMAT_STREAM|PIN_FORMAT)+No,&Format,sizeof(Format));
|
|
if (Format.ByteRate<=0)
|
|
return p->SumByteRate; // failed
|
|
}
|
|
ByteRate += Format.ByteRate;
|
|
}
|
|
|
|
return ByteRate;
|
|
}
|
|
|
|
static bool_t IsVideo(format_base* p)
|
|
{
|
|
int No;
|
|
for (No=0;No<p->StreamCount;++No)
|
|
if (p->Streams[No]->Format.Type == PACKET_VIDEO)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static void FindCoverArt(format_base* p);
|
|
|
|
static void CalcDuration(format_base* p)
|
|
{
|
|
if (p->Duration<0 && p->FileSize>=0)
|
|
{
|
|
if (p->TimeStamps)
|
|
{
|
|
// try to read the last packets and figure out Duration
|
|
filepos_t SavePos;
|
|
format_reader* Reader = p->Reader;
|
|
|
|
LockEnter(p->InputLock);
|
|
LockEnter(p->BufferLock);
|
|
|
|
SavePos = Reader->Input->Seek(Reader->Input,0,SEEK_CUR);
|
|
if (SavePos >= 0)
|
|
{
|
|
format_buffer* Buffer[ENDCHUNKS];
|
|
int No,i;
|
|
int Total = 0;
|
|
tick_t Max=-1;
|
|
format_reader Backup = *Reader;
|
|
|
|
memset(Buffer,0,sizeof(Buffer));
|
|
|
|
Reader->NoMoreInput = 1; //disable reading during ReadPacket
|
|
|
|
for (No=0;No<ENDCHUNKS;++No)
|
|
{
|
|
filepos_t Pos = p->FileSize - (No+1)*BLOCKSIZE;
|
|
if (Pos<0)
|
|
{
|
|
if (No)
|
|
break;
|
|
Pos = 0;
|
|
}
|
|
|
|
Buffer[No] = Format_BufferAlloc(p,2);
|
|
if (!Buffer[No])
|
|
break;
|
|
|
|
if (Reader->Input->Seek(Reader->Input,Pos,SEEK_SET)<0)
|
|
break;
|
|
|
|
Buffer[No]->Length = Reader->Input->ReadBlock(Reader->Input,&Buffer[No]->Block,0,BLOCKSIZE);
|
|
if (Buffer[No]->Length < 0)
|
|
break;
|
|
|
|
Total += Buffer[No]->Length;
|
|
if (No)
|
|
Buffer[No]->Next = Buffer[No-1];
|
|
|
|
// setup temporary buffer
|
|
Reader->InputBuffer = NULL;
|
|
Reader->BufferAvailable = Total;
|
|
Reader->BufferLast = Buffer[0];
|
|
Reader->BufferFirst = Buffer[No];
|
|
Reader->ReadPos = 0;
|
|
Reader->ReadBuffer = NULL;
|
|
Reader->FilePos = Pos;
|
|
for (i=0;i<=No;++i)
|
|
Buffer[i]->RefCount++;
|
|
|
|
if (p->SeekByPacket_BlockAlign > 1)
|
|
{
|
|
int Rem = (Pos - p->SeekByPacket_DataStart) % p->SeekByPacket_BlockAlign;
|
|
if (Rem)
|
|
Reader->Skip(Reader,p->SeekByPacket_BlockAlign - Rem);
|
|
}
|
|
if (p->BackupPacketState)
|
|
p->BackupPacketState(p,1);
|
|
|
|
while (Reader->BufferLast || Reader->ReadBuffer)
|
|
{
|
|
int Result;
|
|
format_packet* Packet = Format_PacketAlloc(p);
|
|
if (!Packet)
|
|
break;
|
|
|
|
Result = p->ReadPacket(p,Reader,Packet);
|
|
|
|
if (Result != ERR_NONE && Result != ERR_DATA_NOT_FOUND)
|
|
{
|
|
Reader->BufferLast = NULL;
|
|
Reader->ReadBuffer = NULL;
|
|
}
|
|
else
|
|
if (Result == ERR_NONE && Packet->Stream && Packet->RefTime>Max)
|
|
Max = Packet->RefTime;
|
|
|
|
Format_PacketRelease(p,Packet);
|
|
}
|
|
|
|
if (p->BackupPacketState)
|
|
p->BackupPacketState(p,0);
|
|
|
|
for (i=0;i<=No;++i)
|
|
Buffer[i]->RefCount=1; // ugly
|
|
|
|
if (Max >= 0)
|
|
{
|
|
p->Duration = Max;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (No=0;No<ENDCHUNKS;++No)
|
|
if (Buffer[No])
|
|
Format_BufferRelease(p,Buffer[No]);
|
|
|
|
Reader->Input->Seek(Reader->Input,SavePos,SEEK_SET);
|
|
*Reader = Backup;
|
|
}
|
|
|
|
LockLeave(p->BufferLock);
|
|
LockLeave(p->InputLock);
|
|
}
|
|
|
|
if (p->StreamCount)
|
|
{
|
|
int ByteRate = SumByteRate(p);
|
|
if (ByteRate>0)
|
|
{
|
|
tick_t Duration = Scale(p->FileSize,TICKSPERSEC,ByteRate);
|
|
if (p->Duration<0 || p->Duration+3*TICKSPERSEC<Duration)
|
|
{
|
|
p->Duration = Duration;
|
|
p->TimeStamps = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if !defined(MULTITHREAD)
|
|
if (IsVideo(p))
|
|
p->AutoReadSize = 1; // force auto readsize
|
|
#endif
|
|
|
|
if (p->StreamCount && p->ProcessMinBuffer)
|
|
{
|
|
int ByteRate = SumByteRate(p);
|
|
if (ByteRate<=0 && p->Duration>0)
|
|
{
|
|
int FileSize = p->FileSize;
|
|
if (FileSize < 0 && p->Reader->Input)
|
|
{
|
|
LockEnter(p->InputLock);
|
|
p->Reader->Input->Get(p->Reader->Input,STREAM_LENGTH,&FileSize,sizeof(FileSize));
|
|
LockLeave(p->InputLock);
|
|
}
|
|
ByteRate = Scale(FileSize,TICKSPERSEC,p->Duration);
|
|
}
|
|
|
|
if (ByteRate>0)
|
|
{
|
|
while (ByteRate>p->ProcessMinBuffer*2 && p->ProcessMinBuffer < p->BufferSize*BLOCKSIZE/4)
|
|
p->ProcessMinBuffer += BLOCKSIZE;
|
|
|
|
if (p->AutoReadSize)
|
|
{
|
|
int No;
|
|
int Rate = ByteRate / 16;
|
|
|
|
// find video framerate
|
|
for (No=0;No<p->StreamCount;++No)
|
|
{
|
|
packetformat Format;
|
|
p->Format.Get(p,(FORMAT_STREAM|PIN_FORMAT)+No,&Format,sizeof(Format));
|
|
if (Format.Type == PACKET_VIDEO && Format.PacketRate.Num>0)
|
|
{
|
|
Rate = Scale(ByteRate,Format.PacketRate.Den,Format.PacketRate.Num);
|
|
break;
|
|
}
|
|
}
|
|
|
|
p->ReadSize = 16384;
|
|
while (p->ReadSize>1024 && Rate<5000)
|
|
{
|
|
Rate <<= 1;
|
|
p->ReadSize >>= 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(TARGET_SYMBIAN)
|
|
if (p->ReadSize > 4096) // large reads interfere with audio (until multithreaded is not added...)
|
|
p->ReadSize = 4096;
|
|
#endif
|
|
|
|
FindCoverArt(p);
|
|
}
|
|
|
|
void Format_BufferRelease(format_base* p, format_buffer* Buffer)
|
|
{
|
|
//DEBUG_MSG4(DEBUG_FORMAT,"Buffer:%d release %d->%d (%d)",Buffer->Id,Buffer->RefCount,Buffer->RefCount-1,p->BufferUsed);
|
|
|
|
LockEnter(p->BufferLock);
|
|
|
|
assert(Buffer->RefCount>0 && Buffer->RefCount<1000);
|
|
|
|
if (--Buffer->RefCount <= 0)
|
|
{
|
|
assert(Buffer->RefCount==0);
|
|
--p->BufferUsed;
|
|
if (p->BufferUsed <= p->BufferSize)
|
|
{
|
|
Buffer->Next = p->FreeBuffers;
|
|
p->FreeBuffers = Buffer;
|
|
}
|
|
else
|
|
{
|
|
FreeBlock(&Buffer->Block);
|
|
free(Buffer);
|
|
}
|
|
}
|
|
|
|
LockLeave(p->BufferLock);
|
|
}
|
|
|
|
format_buffer* Format_BufferRemove(format_reader* Reader)
|
|
{
|
|
format_buffer* Buffer;
|
|
LockEnter(Reader->Format->BufferLock);
|
|
Buffer = Reader->BufferFirst;
|
|
if (Buffer)
|
|
{
|
|
Reader->BufferFirst = Buffer->Next;
|
|
if (Reader->BufferLast == Buffer)
|
|
Reader->BufferLast = NULL;
|
|
Reader->BufferAvailable -= Buffer->Length;
|
|
assert(Reader->BufferAvailable >= 0);
|
|
}
|
|
LockLeave(Reader->Format->BufferLock);
|
|
return Buffer;
|
|
}
|
|
|
|
void Format_SingleRefRelease(format_base* p, format_ref* Ref)
|
|
{
|
|
if (Ref->Buffer)
|
|
Format_BufferRelease(p,Ref->Buffer);
|
|
Ref->Next = p->FreeRefs;
|
|
p->FreeRefs = Ref;
|
|
}
|
|
|
|
void Format_ReleaseRef(format_base* p, format_ref* Ref)
|
|
{
|
|
while (Ref)
|
|
{
|
|
format_ref* Next = Ref->Next;
|
|
Format_SingleRefRelease(p,Ref);
|
|
Ref = Next;
|
|
}
|
|
}
|
|
|
|
void Format_PacketRelease(format_base* p, format_packet* Packet)
|
|
{
|
|
Format_ReleaseRef(p,Packet->Data);
|
|
Packet->Data = NULL;
|
|
Packet->Next = p->FreePackets;
|
|
p->FreePackets = Packet;
|
|
}
|
|
|
|
static NOINLINE void ReleaseStream(format_base* p,format_stream* Stream)
|
|
{
|
|
format_packet* Packet;
|
|
|
|
Stream->State.DropLevel = 0;
|
|
Stream->DropCount = 0;
|
|
Stream->Pending = 0;
|
|
Stream->LastTime = TIME_SYNC;
|
|
|
|
while (Stream->PacketFirst)
|
|
{
|
|
Packet = Stream->PacketFirst;
|
|
Stream->PacketFirst = Packet->Next;
|
|
if (Stream->PacketLast == Packet)
|
|
Stream->PacketLast = NULL;
|
|
|
|
Format_PacketRelease(p,Packet);
|
|
}
|
|
|
|
if (p->ReleaseStream && Stream != p->SubTitle && Stream != p->CoverArt)
|
|
p->ReleaseStream(p,Stream);
|
|
}
|
|
|
|
static int UpdateStreamPin(format_base* p,format_stream* Stream)
|
|
{
|
|
if (!Stream->Pin.Node)
|
|
ReleaseStream(p,Stream);
|
|
return ERR_NONE;
|
|
}
|
|
|
|
void Format_AfterSeek(format_base* p)
|
|
{
|
|
int No;
|
|
|
|
// release stream packets
|
|
for (No=0;No<p->StreamCount;++No)
|
|
ReleaseStream(p,p->Streams[No]);
|
|
|
|
if (p->SubTitle)
|
|
ReleaseStream(p,p->SubTitle);
|
|
|
|
p->LastRead = NULL;
|
|
if (p->AfterSeek)
|
|
p->AfterSeek(p);
|
|
}
|
|
|
|
void Format_ReleaseBuffers(format_reader* Reader)
|
|
{
|
|
format_buffer* Buffer;
|
|
|
|
// release read buffers
|
|
Reader->NoMoreInput = 0;
|
|
|
|
Reader->ReadPos = 0;
|
|
if (Reader->ReadBuffer)
|
|
{
|
|
Format_BufferRelease(Reader->Format,Reader->ReadBuffer);
|
|
Reader->ReadBuffer = NULL;
|
|
}
|
|
|
|
if (Reader->InputBuffer)
|
|
{
|
|
Format_BufferRelease(Reader->Format,Reader->InputBuffer);
|
|
Reader->InputBuffer = NULL;
|
|
}
|
|
|
|
while ((Buffer = Format_BufferRemove(Reader))!=NULL)
|
|
Format_BufferRelease(Reader->Format,Buffer);
|
|
}
|
|
|
|
format_reader* Format_FindReader(format_base* p,filepos_t Min,filepos_t Max)
|
|
{
|
|
int No;
|
|
filepos_t LimitMax;
|
|
|
|
if (Max < Min+16)
|
|
Max = Min+16;
|
|
|
|
LimitMax = p->BufferSize*BLOCKSIZE/2;
|
|
if (p->Duration>0)
|
|
{
|
|
// example a 55Mb/sec mjpeg with pcm audio can have large differences at end of file
|
|
LimitMax = Scale(Max-Min,3*TICKSPERSEC,p->Duration);
|
|
if (LimitMax < 512*1024)
|
|
LimitMax = 512*1024;
|
|
}
|
|
|
|
for (No=0;No<MAXREADER;++No)
|
|
{
|
|
format_reader* Reader = p->Reader+No;
|
|
filepos_t Limit = (Reader->OffsetMax - Reader->OffsetMin)/4;
|
|
if (Limit<128*1024)
|
|
Limit=128*1024;
|
|
if (Limit>LimitMax)
|
|
Limit=LimitMax;
|
|
|
|
if (!Reader->OffsetMax ||
|
|
(Reader->OffsetMin < Max && Reader->OffsetMax > Min &&
|
|
abs(Reader->OffsetMin-Min)<Limit && abs(Reader->OffsetMax-Max)<Limit))
|
|
{
|
|
stream* Orig = p->Reader[0].Input;
|
|
|
|
if (Reader->OffsetMin > Min)
|
|
Reader->OffsetMin = Min;
|
|
if (Reader->OffsetMax < Max)
|
|
Reader->OffsetMax = Max;
|
|
|
|
Reader->Ratio = Scale(MAX_INT,1,Reader->OffsetMax - Reader->OffsetMin);
|
|
|
|
if (!Reader->Input && Orig)
|
|
{
|
|
bool_t Silent = 1;
|
|
tchar_t URL[MAXPATH];
|
|
stream* Input;
|
|
|
|
if (Orig->Get(Orig,STREAM_URL,URL,sizeof(URL)) != ERR_NONE)
|
|
break;
|
|
|
|
Input = (stream*)NodeCreate(Orig->Class);
|
|
if (!Input)
|
|
break;
|
|
|
|
Input->Set(Input,STREAM_SILENT,&Silent,sizeof(bool_t));
|
|
|
|
if (Input->Set(Input,STREAM_URL,URL,sizeof(URL))!=ERR_NONE)
|
|
{
|
|
NodeDelete((node*)Input);
|
|
break;
|
|
}
|
|
|
|
Reader->ReadPos = 0;
|
|
Reader->FilePos = 0;
|
|
Reader->Input = Input;
|
|
}
|
|
|
|
return Reader;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static NOINLINE void Format_FreeStream(format_base* p,format_stream* Stream)
|
|
{
|
|
PacketFormatClear(&Stream->Format);
|
|
BufferClear(&Stream->BufferMem);
|
|
FreeBlock(&Stream->BufferBlock);
|
|
free(Stream);
|
|
}
|
|
|
|
int Format_Reset(format_base* p)
|
|
{
|
|
int Result = ERR_NONE;
|
|
format_packet* Packet;
|
|
format_buffer* Buffer;
|
|
format_reader* Reader;
|
|
format_ref* Ref;
|
|
int No;
|
|
|
|
if (p->BufferLock) // only if it was inited
|
|
{
|
|
Format_AfterSeek(p);
|
|
for (No=0;No<MAXREADER;++No)
|
|
Format_ReleaseBuffers(p->Reader+No);
|
|
}
|
|
|
|
if (p->ReleaseStreams.Func)
|
|
p->ReleaseStreams.Func(p->ReleaseStreams.This,0,0);
|
|
|
|
if (p->Done)
|
|
p->Done(p);
|
|
|
|
while (p->StreamCount)
|
|
Format_RemoveStream(p);
|
|
|
|
while (p->FreePackets)
|
|
{
|
|
Packet = p->FreePackets;
|
|
p->FreePackets = Packet->Next;
|
|
free(Packet);
|
|
}
|
|
|
|
while (p->FreeBuffers)
|
|
{
|
|
Buffer = p->FreeBuffers;
|
|
p->FreeBuffers = Buffer->Next;
|
|
FreeBlock(&Buffer->Block);
|
|
free(Buffer);
|
|
}
|
|
|
|
while (p->FreeRefs)
|
|
{
|
|
Ref = p->FreeRefs;
|
|
p->FreeRefs = Ref->Next;
|
|
free(Ref);
|
|
}
|
|
|
|
if (p->SubTitle)
|
|
{
|
|
Format_FreeSubTitle(p,p->SubTitle);
|
|
p->SubTitle = NULL;
|
|
}
|
|
|
|
if (p->CoverArt)
|
|
{
|
|
Format_FreeStream(p,p->CoverArt);
|
|
p->CoverArt = NULL;
|
|
}
|
|
|
|
p->TotalCount = 0;
|
|
p->StreamCount = 0;
|
|
p->ActiveStreams = 0;
|
|
|
|
LockDelete(p->BufferLock);
|
|
LockDelete(p->InputLock);
|
|
p->BufferLock = NULL;
|
|
p->InputLock = NULL;
|
|
p->LastRead = NULL;
|
|
|
|
for (No=0;No<MAXREADER;++No)
|
|
{
|
|
Reader = p->Reader+No;
|
|
if (No>0 && Reader->Input)
|
|
{
|
|
Reader->Input->Set(Reader->Input,STREAM_URL,NULL,0);
|
|
NodeDelete((node*)Reader->Input);
|
|
Reader->Input = NULL;
|
|
}
|
|
Reader->Ratio = -1;
|
|
Reader->OffsetMin = MAX_INT;
|
|
Reader->OffsetMax = 0;
|
|
Reader->Current = NULL;
|
|
Reader->OutOfSync = 0;
|
|
}
|
|
|
|
Reader = p->Reader;
|
|
if (Reader->Input)
|
|
{
|
|
if (!NodeIsClass(Reader->Input->Class,STREAM_CLASS) &&
|
|
!NodeIsClass(Reader->Input->Class,STREAMPROCESS_CLASS))
|
|
return ERR_INVALID_PARAM;
|
|
|
|
if (Reader->Input->Get(Reader->Input,STREAM_LENGTH,&p->FileSize,sizeof(p->FileSize)) != ERR_NONE)
|
|
p->FileSize = -1;
|
|
|
|
p->InputLock = LockCreate();
|
|
p->BufferLock = LockCreate();
|
|
p->Duration = TIME_UNKNOWN;
|
|
p->FirstSync = 1;
|
|
p->SyncMode = 1; // start with syncmode
|
|
p->SyncTime = TIME_SYNC;
|
|
p->SyncStream = NULL;
|
|
p->HeaderLoaded = 0;
|
|
p->ProcessMinBuffer = PROCESSMINVIDEO;
|
|
p->ReadSize = 16384;
|
|
p->GlobalOffset = 0;
|
|
p->SumByteRate = 0;
|
|
|
|
Reader->FilePos = 0;
|
|
Reader->NoMoreInput = 0;
|
|
|
|
p->SeekByPacket_DataStart = 0;
|
|
p->SeekByPacket_BlockAlign = 1;
|
|
|
|
if (p->Init)
|
|
{
|
|
Result = p->Init(p);
|
|
if (Result != ERR_NONE)
|
|
{
|
|
Reader->Input = NULL;
|
|
Format_Reset(p);
|
|
}
|
|
else
|
|
p->SyncRead = SYNCREAD;
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
int FormatBaseGet(format_base* p,int No,void* Data,int Size)
|
|
{
|
|
int Result = ERR_INVALID_PARAM;
|
|
if (No >= FORMAT_COMMENT && No < FORMAT_COMMENT+p->TotalCount)
|
|
GETVALUE(p->Streams[No-FORMAT_COMMENT]->Comment,pin)
|
|
else
|
|
if (No >= FORMAT_STREAM && No < FORMAT_STREAM+p->TotalCount)
|
|
GETVALUE(p->Streams[No-FORMAT_STREAM]->Pin,pin)
|
|
else
|
|
if (No >= FORMAT_STREAM_PRIORITY && No < FORMAT_STREAM_PRIORITY+p->TotalCount)
|
|
GETVALUE(p->Streams[No-FORMAT_STREAM_PRIORITY]->Priority,int)
|
|
else
|
|
if (No >= (FORMAT_STREAM|PIN_PROCESS) && No < (FORMAT_STREAM|PIN_PROCESS)+p->TotalCount)
|
|
GETVALUE(p->Streams[No-(FORMAT_STREAM|PIN_PROCESS)]->Process,packetprocess)
|
|
else
|
|
if (No >= (FORMAT_STREAM|PIN_FORMAT) && No < (FORMAT_STREAM|PIN_FORMAT)+p->TotalCount)
|
|
{
|
|
packetformat CodecFormat;
|
|
format_stream* s = p->Streams[No-(FORMAT_STREAM|PIN_FORMAT)];
|
|
assert(Size == sizeof(packetformat));
|
|
if (s->Pin.Node && s->Pin.Node->Get(s->Pin.Node,s->Pin.No|PIN_FORMAT,&CodecFormat,sizeof(CodecFormat))==ERR_NONE)
|
|
PacketFormatCombine(&s->Format,&CodecFormat);
|
|
*(packetformat*)Data = s->Format;
|
|
Result = ERR_NONE;
|
|
}
|
|
else
|
|
switch (No)
|
|
{
|
|
case FORMAT_BUFFERSIZE: GETVALUE(p->BufferSize,int); break;
|
|
case FORMAT_INPUT: GETVALUE(p->Reader[0].Input,stream*); break;
|
|
case FORMAT_DURATION: GETVALUECOND(p->Duration,tick_t,p->Duration>=0); break;
|
|
case FORMAT_FILEPOS: GETVALUECOND(p->Reader[0].FilePos,int,p->Timing && p->Reader[0].FilePos>=0); break;
|
|
case FORMAT_FILEALIGN: GETVALUE(p->FileAlign,int); break;
|
|
case FORMAT_STREAM_COUNT: GETVALUE(p->TotalCount,int); break;
|
|
case FORMAT_FILESIZE: GETVALUECOND(p->FileSize/1024,int,p->FileSize>=0); break;
|
|
case FORMAT_FIND_SUBTITLES: GETVALUE(p->NeedSubtitles,bool_t); break;
|
|
case FORMAT_GLOBAL_COMMENT: GETVALUE(p->Comment,pin); break;
|
|
case FORMAT_AUTO_READSIZE: GETVALUE(p->AutoReadSize,bool_t); break;
|
|
case FORMAT_TIMING: GETVALUE(p->Timing,bool_t); break;
|
|
case FORMAT_COVERART: GETVALUECOND(p->StreamCount+(p->SubTitle!=NULL),int,p->CoverArt!=NULL); break;
|
|
case FORMAT_MIN_BUFFER: GETVALUE(p->ProcessMinBuffer/BLOCKSIZE,int); break;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
int FormatBaseSet(format_base* p,int No,const void* Data,int Size)
|
|
{
|
|
node* Advanced;
|
|
uint8_t* Ptr;
|
|
int Result = ERR_INVALID_PARAM;
|
|
|
|
if (No >= FORMAT_COMMENT && No < FORMAT_COMMENT+p->TotalCount)
|
|
SETVALUE(p->Streams[No-FORMAT_COMMENT]->Comment,pin,ERR_NONE)
|
|
else
|
|
if (No >= FORMAT_STREAM && No < FORMAT_STREAM+p->TotalCount)
|
|
{
|
|
format_stream* s = p->Streams[No-FORMAT_STREAM];
|
|
if (s->Pin.Node && No-FORMAT_STREAM<p->StreamCount) p->ActiveStreams--;
|
|
SETVALUE(s->Pin,pin,UpdateStreamPin(p,s));
|
|
if (s->Pin.Node && No-FORMAT_STREAM<p->StreamCount) p->ActiveStreams++;
|
|
}
|
|
else
|
|
if (No >= (FORMAT_STREAM|PIN_PROCESS) && No < (FORMAT_STREAM|PIN_PROCESS)+p->TotalCount)
|
|
SETVALUE(p->Streams[No-(FORMAT_STREAM|PIN_PROCESS)]->Process,packetprocess,ERR_NONE)
|
|
else
|
|
switch (No)
|
|
{
|
|
case FORMAT_INPUT: SETVALUENULL(p->Reader[0].Input,stream*,Format_Reset(p),p->Reader[0].Input=NULL); break;
|
|
case FORMAT_UPDATESTREAMS: SETVALUE(p->UpdateStreams,notify,ERR_NONE); break;
|
|
case FORMAT_RELEASESTREAMS: SETVALUE(p->ReleaseStreams,notify,ERR_NONE); break;
|
|
case FORMAT_FIND_SUBTITLES: SETVALUE(p->NeedSubtitles,bool_t,ERR_NONE); break;
|
|
case FORMAT_GLOBAL_COMMENT: SETVALUE(p->Comment,pin,ERR_NONE); break;
|
|
case FORMAT_FILEALIGN: SETVALUE(p->FileAlign,int,ERR_NONE); break;
|
|
case FORMAT_AUTO_READSIZE: SETVALUE(p->AutoReadSize,bool_t,ERR_NONE); break;
|
|
|
|
case NODE_HIBERNATE:
|
|
assert(Size == sizeof(int));
|
|
Result = Format_Hibernate(p,*(int*)Data);
|
|
break;
|
|
|
|
case NODE_SETTINGSCHANGED:
|
|
Advanced = Context()->Advanced;
|
|
if (Advanced)
|
|
{
|
|
Advanced->Get(Advanced,ADVANCED_DROPTOL,&p->DropTolerance,sizeof(tick_t));
|
|
Advanced->Get(Advanced,ADVANCED_SKIPTOL,&p->SkipTolerance,sizeof(tick_t));
|
|
Advanced->Get(Advanced,ADVANCED_AVOFFSET,&p->AVOffset,sizeof(tick_t));
|
|
}
|
|
break;
|
|
|
|
case FORMAT_DATAFEED:
|
|
|
|
Ptr = (uint8_t*) Data;
|
|
while (Size)
|
|
{
|
|
format_buffer* Buffer;
|
|
|
|
Buffer = Format_BufferAlloc(p,2);
|
|
if (Buffer)
|
|
{
|
|
Buffer->Length = Size;
|
|
if (Buffer->Length > BLOCKSIZE)
|
|
Buffer->Length = BLOCKSIZE;
|
|
|
|
WriteBlock(&Buffer->Block,0,Ptr,Buffer->Length);
|
|
|
|
Ptr += Buffer->Length;
|
|
Size -= Buffer->Length;
|
|
|
|
Format_BufferInsert(p->Reader,Buffer);
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case FORMAT_BUFFERSIZE: SETVALUE(p->BufferSize,int,ERR_NONE); break;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
format_packet* Format_PacketAlloc(format_base* p)
|
|
{
|
|
format_packet* Packet = p->FreePackets;
|
|
|
|
if (!Packet)
|
|
{
|
|
Packet = (format_packet*) malloc(sizeof(format_packet));
|
|
if (!Packet)
|
|
return NULL;
|
|
}
|
|
else
|
|
p->FreePackets = Packet->Next;
|
|
|
|
Packet->Data = NULL;
|
|
Packet->RefTime = TIME_UNKNOWN;
|
|
Packet->EndRefTime = TIME_UNKNOWN;
|
|
Packet->Stream = NULL;
|
|
Packet->Next = NULL;
|
|
Packet->Key = 1;
|
|
|
|
return Packet;
|
|
}
|
|
|
|
static int Format_Sync(format_base* p,tick_t Time,int FilePos,bool_t PrevKey)
|
|
{
|
|
if (!p->Seek || !p->Reader[0].Input)
|
|
return ERR_NOT_SUPPORTED;
|
|
|
|
if (!p->HeaderLoaded)
|
|
return ERR_LOADING_HEADER;
|
|
|
|
p->SyncMode = 1;
|
|
p->SyncRead = SYNCREAD;
|
|
p->SyncTime = TIME_SYNC; // set before p->Seek
|
|
p->SyncStream = NULL;
|
|
|
|
if (Time>=0 || FilePos>=0)
|
|
{
|
|
int Result = p->Seek(p,Time,FilePos,PrevKey);
|
|
if (Result != ERR_NONE)
|
|
{
|
|
p->SyncMode = 0;
|
|
return Result;
|
|
}
|
|
}
|
|
|
|
return ERR_NONE;
|
|
}
|
|
|
|
int Format_CheckEof(format_base* p,format_stream* Stream)
|
|
{
|
|
int Result;
|
|
|
|
if (!p->Timing && p->ProcessTime>=0 && p->ProcessTime<5*TICKSPERSEC)
|
|
return ERR_BUFFER_FULL;
|
|
|
|
Stream->State.CurrTime = p->ProcessTime;
|
|
Stream->State.DropLevel = 0;
|
|
|
|
Result = Stream->Process(Stream->Pin.Node,NULL,&Stream->State);
|
|
if (Result != ERR_BUFFER_FULL)
|
|
Result = ERR_END_OF_FILE;
|
|
|
|
return Result;
|
|
}
|
|
|
|
static NOINLINE int Synced(format_base* p,format_stream* Stream)
|
|
{
|
|
p->SyncMode = 0;
|
|
|
|
if (p->SyncTime == TIME_SYNC)
|
|
{
|
|
p->SyncTime = Stream->Packet.RefTime;
|
|
if (p->SyncTime < 0)
|
|
p->SyncTime = Stream->LastTime;
|
|
}
|
|
|
|
if (p->FirstSync)
|
|
{
|
|
// some codecs may need the first few packets as headers
|
|
int No;
|
|
p->ProcessTime = 0;
|
|
for (No=0;No<p->StreamCount;++No)
|
|
if (p->Streams[No]->Pin.Node && p->Streams[No]->PacketFirst)
|
|
p->Process(p,p->Streams[No]); // try processing
|
|
p->ProcessTime = TIME_SYNC;
|
|
}
|
|
|
|
return ERR_SYNCED;
|
|
}
|
|
|
|
int Format_Send(format_base* p,format_stream* Stream)
|
|
{
|
|
int Result;
|
|
tick_t CurrTime = p->ProcessTime;
|
|
|
|
Stream->State.CurrTime = CurrTime;
|
|
|
|
if (Stream->Packet.RefTime >= 0 && !Stream->DisableDrop)
|
|
{
|
|
Stream->State.DropLevel = 0;
|
|
|
|
if (CurrTime >= 0)
|
|
{
|
|
tick_t Diff = CurrTime - Stream->Packet.RefTime;
|
|
|
|
if (Diff < -TICKSPERSEC/2 && Stream->Format.Type == PACKET_VIDEO)
|
|
{
|
|
// make sure buffers and pending packets are processed
|
|
Stream->Process(Stream->Pin.Node,NULL,&Stream->State);
|
|
return ERR_BUFFER_FULL;
|
|
}
|
|
|
|
if (Diff > p->DropTolerance)
|
|
{
|
|
if (Diff > p->SkipTolerance + p->DropTolerance)
|
|
{
|
|
Stream->State.DropLevel = 2;
|
|
Stream->DropCount = DROP_SHOW;
|
|
}
|
|
else
|
|
{
|
|
if (!Stream->State.DropLevel)
|
|
Stream->DropCount = 0;
|
|
|
|
if (Stream->DropCount >= DROP_SHOW)
|
|
{
|
|
Stream->State.DropLevel = 0; // show one packet
|
|
Stream->DropCount = 0;
|
|
}
|
|
else
|
|
{
|
|
Stream->State.DropLevel = 1;
|
|
++Stream->DropCount;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DEBUG_MSG5(DEBUG_FORMAT,T("Send stream:%d size:%d time:%d curr:%d drop:%d"),Stream->No,Stream->Packet.Length,Stream->Packet.RefTime,CurrTime,Stream->State.DropLevel);
|
|
|
|
Result = Stream->Process(Stream->Pin.Node,&Stream->Packet,&Stream->State);
|
|
|
|
// packet not processed?
|
|
if (Result == ERR_BUFFER_FULL)
|
|
{
|
|
DEBUG_MSG1(DEBUG_FORMAT,T("Send stream:%d BufferFull"),Stream->No);
|
|
return Result;
|
|
}
|
|
|
|
if (p->Sended)
|
|
p->Sended(p,Stream);
|
|
|
|
Stream->Pending = 0;
|
|
|
|
// sync: found a valid frame?
|
|
if (p->SyncMode && (Result == ERR_NONE || Result == ERR_NOT_SUPPORTED))
|
|
Result = Synced(p,Stream);
|
|
|
|
return Result;
|
|
}
|
|
|
|
void Format_TimeStampRestarted(format_base* p,format_stream* Stream,tick_t* RefTime)
|
|
{
|
|
if (!p->InSeek)
|
|
{
|
|
format_packet* i;
|
|
*RefTime -= p->GlobalOffset;
|
|
|
|
p->GlobalOffset = Stream->LastTime; // last processed time
|
|
for (i=Stream->PacketFirst;i;i=i->Next)
|
|
if (i->RefTime>=0)
|
|
p->GlobalOffset = i->RefTime; // last packet time
|
|
|
|
*RefTime += p->GlobalOffset;
|
|
p->TimeStamps = 0;
|
|
}
|
|
}
|
|
|
|
int Format_FillQueue(format_base* p,format_reader* Reader)
|
|
{
|
|
int Result;
|
|
format_packet* Packet;
|
|
|
|
if (!Reader->BufferAvailable && !Reader->ReadBuffer && p->ProcessMinBuffer!=0 &&
|
|
(!p->SyncMode || p->SyncRead<=0 || Reader->NoMoreInput))
|
|
return ERR_NEED_MORE_DATA;
|
|
|
|
Packet = Format_PacketAlloc(p);
|
|
if (!Packet)
|
|
return ERR_OUT_OF_MEMORY;
|
|
|
|
Result = p->ReadPacket(p,Reader,Packet);
|
|
|
|
if (Result == ERR_NONE && Packet->Stream && Packet->Stream->Pin.Node)
|
|
{
|
|
format_stream* s = Packet->Stream;
|
|
|
|
if (Packet->RefTime>=0)
|
|
{
|
|
Packet->RefTime += p->GlobalOffset;
|
|
|
|
if (s->Format.Type == PACKET_AUDIO)
|
|
{
|
|
Packet->RefTime += p->AVOffset;
|
|
if (Packet->RefTime < 0)
|
|
Packet->RefTime = 0;
|
|
}
|
|
|
|
if (s->LastTime<0)
|
|
s->LastTime = Packet->RefTime; // found reftime
|
|
else
|
|
if (s->LastTime > Packet->RefTime+TICKSPERSEC && Packet->RefTime-p->GlobalOffset<TICKSPERSEC)
|
|
Format_TimeStampRestarted(p,s,&Packet->RefTime);
|
|
}
|
|
|
|
if (s->LastTime >= TIME_UNKNOWN)
|
|
{
|
|
// add packet to stream
|
|
if (!s->PacketFirst)
|
|
s->PacketFirst = Packet;
|
|
else
|
|
s->PacketLast->Next = Packet;
|
|
s->PacketLast = Packet;
|
|
|
|
if (Packet->EndRefTime>=0 && s->Format.Type == PACKET_SUBTITLE)
|
|
{
|
|
// add empty packet
|
|
format_packet* Tail = Format_PacketAlloc(p);
|
|
if (Tail)
|
|
{
|
|
Tail->Stream = Packet->Stream;
|
|
Tail->RefTime = Packet->EndRefTime;
|
|
|
|
if (!s->PacketFirst)
|
|
s->PacketFirst = Tail;
|
|
else
|
|
s->PacketLast->Next = Tail;
|
|
s->PacketLast = Tail;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
Format_PacketRelease(p,Packet);
|
|
}
|
|
else
|
|
Format_PacketRelease(p,Packet);
|
|
|
|
return Result;
|
|
}
|
|
|
|
NOINLINE bool_t Format_AllocBufferBlock(format_base* p,format_stream* Stream,int Size)
|
|
{
|
|
FreeBlock(&Stream->BufferBlock);
|
|
if (Size>0)
|
|
{
|
|
Size = (Size + SAFETAIL + 0x7FFF) & ~0x7FFF;
|
|
if (AllocBlock(Size,&Stream->BufferBlock,0,HEAP_STORAGE))
|
|
Size -= SAFETAIL;
|
|
else
|
|
Size = 0;
|
|
}
|
|
Stream->BufferBlockLength = Size;
|
|
return Size>0;
|
|
}
|
|
|
|
/*
|
|
static bool_t ReleasePacket(format_base* p)
|
|
{
|
|
int No;
|
|
format_stream* Min = NULL;
|
|
tick_t MinTime = MAX_TICK;
|
|
|
|
for (No=0;No<p->StreamCount;++No)
|
|
{
|
|
format_stream* Stream = p->Streams[No];
|
|
if (Stream->PacketFirst && Stream->PacketFirst->RefTime < MinTime)
|
|
{
|
|
Min = Stream;
|
|
MinTime = Stream->PacketFirst->RefTime;
|
|
}
|
|
}
|
|
|
|
if (Min)
|
|
{
|
|
format_packet* Packet = Min->PacketFirst;
|
|
|
|
Min->PacketFirst = Packet->Next;
|
|
if (Min->PacketLast == Packet)
|
|
Min->PacketLast = NULL;
|
|
|
|
Format_PacketRelease(p,Packet);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
*/
|
|
|
|
static int Format_ProcessStream(format_base* p,format_stream* Stream)
|
|
{
|
|
format_packet* Packet;
|
|
format_ref* Ref;
|
|
int Result = ERR_NONE;
|
|
int No;
|
|
int Burst;
|
|
|
|
if (Stream->Pending)
|
|
{
|
|
Result = Format_Send(p,Stream);
|
|
|
|
if (Result == ERR_BUFFER_FULL || Result == ERR_SYNCED)
|
|
{
|
|
if (p->SyncMode && Stream == p->SyncStream) // can happen when sync without seek
|
|
Result = Synced(p,Stream);
|
|
return Result;
|
|
}
|
|
}
|
|
|
|
// process a limited number of packets at once (other streams need cpu time too)
|
|
|
|
Burst = Stream->PacketBurst;
|
|
|
|
for (No=0;No<Burst;++No)
|
|
{
|
|
while (!Stream->PacketFirst)
|
|
{
|
|
format_reader* Reader = Stream->Reader;
|
|
|
|
/*
|
|
if (p->BufferUsed >= p->BufferSize && p->SyncMode && Stream == p->SyncStream)
|
|
{
|
|
// try dropping other stream packets to make buffer for sync stream
|
|
while (p->BufferUsed >= p->BufferSize)
|
|
if (!ReleasePacket(p))
|
|
break;
|
|
}
|
|
*/
|
|
|
|
// prevent audio from going too much ahead
|
|
if (Reader->BufferAvailable < p->ProcessMinBuffer && !Reader->NoMoreInput &&
|
|
(!p->SyncMode || p->SyncRead<=0 || Stream != p->SyncStream))
|
|
return ERR_NEED_MORE_DATA;
|
|
|
|
Result = Format_FillQueue(p,Reader);
|
|
|
|
if (Reader->NoMoreInput && (Result == ERR_END_OF_FILE || Result == ERR_NEED_MORE_DATA))
|
|
Result = Format_CheckEof(p,Stream);
|
|
|
|
if (Result != ERR_NONE || (p->Bench && (No || !Stream->PacketFirst)))
|
|
return Result;
|
|
}
|
|
|
|
Packet = Stream->PacketFirst;
|
|
|
|
// sync: all non SyncStream packets are stored (or dropped)
|
|
if (p->SyncMode && Stream != p->SyncStream && p->SyncStream)
|
|
{
|
|
// catch up to syncstream (drop packets)
|
|
tick_t LastTime = p->SyncStream->LastTime;
|
|
if (p->SyncStream->Fragmented)
|
|
LastTime -= TICKSPERSEC/2; // LastTime may not be the correct sync time
|
|
|
|
while (Packet && Packet->RefTime < LastTime)
|
|
{
|
|
Stream->PacketFirst = Packet->Next;
|
|
if (Stream->PacketLast == Packet)
|
|
Stream->PacketLast = NULL;
|
|
|
|
Format_PacketRelease(p,Packet);
|
|
|
|
Packet = Stream->PacketFirst;
|
|
}
|
|
return ERR_NEED_MORE_DATA; // allow SyncStream to need more data
|
|
}
|
|
|
|
//DEBUG_MSG3(DEBUG_FORMAT,T("Packet stream:%d time:%d pos:%d"),Stream->No,Packet->RefTime,Packet->FilePos);
|
|
|
|
// build output packet
|
|
|
|
if (Packet->RefTime >= 0)
|
|
Stream->LastTime = Packet->RefTime;
|
|
|
|
Stream->Packet.RefTime = Packet->RefTime;
|
|
Stream->Packet.Key = Packet->Key;
|
|
Ref = Packet->Data;
|
|
|
|
if (!Ref)
|
|
{
|
|
Stream->Packet.Data[0] = NULL;
|
|
Stream->Packet.Length = 0;
|
|
Stream->Pending = 1;
|
|
}
|
|
else
|
|
if (!Stream->Fragmented)
|
|
{
|
|
if (Ref->Next || (Ref->Length + Ref->Begin + SAFETAIL > BLOCKSIZE) || Stream->ForceMerge)
|
|
{
|
|
// merge references
|
|
if (p->UseBufferBlock)
|
|
{
|
|
int Total = 0;
|
|
format_ref* i;
|
|
for (Total=0,i=Ref;i;i=i->Next)
|
|
Total += i->Length;
|
|
|
|
if (Stream->BufferBlockLength < Total && !Format_AllocBufferBlock(p,Stream,Total))
|
|
Ref = NULL;
|
|
|
|
for (Total=0,i=Ref;i;i=i->Next)
|
|
{
|
|
WriteBlock(&Stream->BufferBlock,Total,i->Buffer->Block.Ptr + i->Begin,i->Length);
|
|
Total += i->Length;
|
|
}
|
|
|
|
Stream->Packet.Data[0] = Stream->BufferBlock.Ptr;
|
|
Stream->Packet.Length = Total;
|
|
}
|
|
else
|
|
{
|
|
BufferDrop(&Stream->BufferMem);
|
|
for (;Ref;Ref=Ref->Next)
|
|
BufferWrite(&Stream->BufferMem,Ref->Buffer->Block.Ptr + Ref->Begin, Ref->Length,16384);
|
|
|
|
Stream->Packet.Data[0] = Stream->BufferMem.Data;
|
|
Stream->Packet.Length = Stream->BufferMem.WritePos;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// single reference
|
|
Stream->Packet.Data[0] = Ref->Buffer->Block.Ptr + Ref->Begin;
|
|
Stream->Packet.Length = Ref->Length;
|
|
}
|
|
Stream->Pending = 1;
|
|
}
|
|
else
|
|
{
|
|
// send first reference (doesn't matter how long)
|
|
Stream->Packet.Data[0] = Ref->Buffer->Block.Ptr + Ref->Begin;
|
|
Stream->Packet.Length = Ref->Length;
|
|
Stream->Pending = 2; // indicate parital sending
|
|
}
|
|
|
|
Result = Format_Send(p,Stream);
|
|
|
|
if (Result == ERR_BUFFER_FULL || Result == ERR_SYNCED)
|
|
break;
|
|
|
|
if (Stream->State.DropLevel==2 && Stream->PacketFirst && Burst<10 && No==Burst-1)
|
|
++Burst; // 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 void Format_Sended(format_base* p, format_stream* Stream)
|
|
{
|
|
format_packet* Packet = Stream->PacketFirst;
|
|
if (Packet)
|
|
{
|
|
if (Stream->Pending == 2 && Packet->Data)
|
|
{
|
|
// release sended references
|
|
format_ref* Ref = Packet->Data;
|
|
Packet->Data = Ref->Next;
|
|
Format_SingleRefRelease(p,Ref);
|
|
|
|
if (Packet->Data)
|
|
{
|
|
// packet still not finished
|
|
Packet->RefTime = TIME_UNKNOWN;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// full packet is released
|
|
Stream->PacketFirst = Packet->Next;
|
|
if (Stream->PacketLast == Packet)
|
|
Stream->PacketLast = NULL;
|
|
|
|
Format_PacketRelease(p,Packet);
|
|
}
|
|
}
|
|
|
|
NOINLINE format_stream* Format_DefSyncStream(format_base* p, format_reader* Reader)
|
|
{
|
|
format_stream* Found = NULL;
|
|
format_stream* Stream;
|
|
int No;
|
|
|
|
// prefer video streams
|
|
for (No=0;No<p->StreamCount;++No)
|
|
{
|
|
Stream = p->Streams[No];
|
|
if (Stream->Pin.Node && (!Reader || Stream->Reader==Reader))
|
|
{
|
|
if (Stream->Format.Type == PACKET_VIDEO)
|
|
return Stream;
|
|
|
|
if (!Found)
|
|
Found = Stream;
|
|
}
|
|
}
|
|
|
|
return Found;
|
|
}
|
|
|
|
static bool_t TryReSend(node* p)
|
|
{
|
|
if (p)
|
|
{
|
|
bool_t Buffered;
|
|
if (p->Get(p,FLOW_BUFFERED,&Buffered,sizeof(Buffered))==ERR_NONE && Buffered)
|
|
return p->Set(p,FLOW_RESEND,NULL,0) == ERR_NONE;
|
|
|
|
if (!NodeIsClass(p->Class,OUT_CLASS))
|
|
{
|
|
pin Pin;
|
|
node* Node;
|
|
datadef DataDef;
|
|
int No;
|
|
|
|
for (No=0;NodeEnum(p,No,&DataDef)==ERR_NONE;++No)
|
|
if (DataDef.Flags & DF_OUTPUT)
|
|
{
|
|
switch (DataDef.Type)
|
|
{
|
|
case TYPE_NODE:
|
|
if (p->Get(p,DataDef.No,&Node,sizeof(Node))==ERR_NONE && TryReSend(Node))
|
|
return 1;
|
|
break;
|
|
|
|
case TYPE_PACKET:
|
|
if (p->Get(p,DataDef.No,&Pin,sizeof(Pin))==ERR_NONE && TryReSend(Pin.Node))
|
|
return 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int DummyError(void* This,int Param,int Param2)
|
|
{
|
|
return ERR_NONE;
|
|
}
|
|
|
|
static NOINLINE int ProcessCoverArt(format_base* p,format_stream* Stream)
|
|
{
|
|
int Result;
|
|
notify Error;
|
|
context* c = Context();
|
|
node* Node = Stream->Pin.Node;
|
|
if (!Node)
|
|
return ERR_NONE;
|
|
|
|
if (TryReSend(Node))
|
|
return ERR_NONE;
|
|
|
|
// suppress covert art decoding error messages
|
|
Error = c->Error;
|
|
c->Error.Func = DummyError;
|
|
|
|
Stream->State.CurrTime = TIME_SYNC;
|
|
Stream->State.DropLevel = 0;
|
|
Result = Stream->Process(Node,&Stream->Packet,&Stream->State);
|
|
|
|
c->Error = Error;
|
|
return Result;
|
|
}
|
|
|
|
static int Format_Process(format_base* p,processstate* State)
|
|
{
|
|
int No;
|
|
int Result;
|
|
int AllNeedMoreData = 1;
|
|
int AllEndOfFile = 1;
|
|
|
|
assert(State->Time>=0 || State->Time == TIME_BENCH);
|
|
|
|
State->BufferUsedBefore = p->BufferUsed;
|
|
p->ProcessTime = State->Time;
|
|
p->Bench = p->ProcessTime < 0;
|
|
|
|
if (!p->HeaderLoaded)
|
|
{
|
|
// load format begining to find streams
|
|
format_reader* Reader = p->Reader;
|
|
|
|
Result = p->FillQueue(p,Reader);
|
|
|
|
if (Reader->FilePos > HEADERLOAD || (p->StreamCount && Reader->FilePos > p->MinHeaderLoad) || Reader->Eof(Reader))
|
|
p->HeaderLoaded = 1;
|
|
|
|
State->BufferUsedAfter = p->BufferUsed;
|
|
return Result;
|
|
}
|
|
|
|
if (p->SyncMode)
|
|
{
|
|
p->ProcessTime = TIME_SYNC;
|
|
// we need to choose one stream (to find a non dropped frame)
|
|
if (!p->SyncStream)
|
|
{
|
|
p->SyncStream = Format_DefSyncStream(p,NULL);
|
|
if (!p->SyncStream)
|
|
{
|
|
// no sync needed
|
|
p->SyncMode = 0;
|
|
|
|
// try to calc duration once (after first sync)
|
|
if (p->FirstSync)
|
|
{
|
|
p->FirstSync = 0;
|
|
CalcDuration(p);
|
|
}
|
|
|
|
if (p->CoverArt)
|
|
ProcessCoverArt(p,p->CoverArt);
|
|
|
|
// leave time as it is
|
|
State->BufferUsedAfter = p->BufferUsed;
|
|
return ERR_SYNCED;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if all streams are full: caller can go to sleep
|
|
Result = ERR_BUFFER_FULL;
|
|
|
|
if (p->SubTitle)
|
|
Format_ProcessSubTitle(p,p->SubTitle);
|
|
|
|
for (No=0;No<p->StreamCount;++No)
|
|
{
|
|
format_stream* Stream = p->Streams[No];
|
|
if (Stream->Pin.Node)
|
|
{
|
|
int i = p->Process(p,Stream);
|
|
|
|
if (!p->SyncMode || p->SyncStream == Stream)
|
|
{
|
|
if (i == ERR_NEED_MORE_DATA)
|
|
{
|
|
if (!Stream->Reader->NoMoreInput && Stream->Format.Type != PACKET_SUBTITLE)
|
|
{
|
|
// if less then 1/8 sec is left and stream needs data -> force need more data mode
|
|
tick_t LastTime = Stream->LastTime;
|
|
if (State->Fill || (LastTime >= 0 && p->ProcessTime >= 0 && LastTime < p->ProcessTime + TICKSPERSEC/8))
|
|
Result = ERR_NEED_MORE_DATA;
|
|
}
|
|
}
|
|
else
|
|
AllNeedMoreData = 0;
|
|
|
|
if (i != ERR_END_OF_FILE)
|
|
AllEndOfFile = 0;
|
|
}
|
|
|
|
if (i == ERR_SYNCED)
|
|
{
|
|
// try to calc duration once (after first sync)
|
|
if (p->FirstSync)
|
|
{
|
|
p->FirstSync = 0;
|
|
CalcDuration(p);
|
|
}
|
|
|
|
if (p->CoverArt)
|
|
ProcessCoverArt(p,p->CoverArt);
|
|
|
|
State->Time = p->SyncTime;
|
|
State->BufferUsedAfter = p->BufferUsed;
|
|
return i;
|
|
}
|
|
|
|
if (Result == ERR_BUFFER_FULL &&
|
|
i != ERR_BUFFER_FULL &&
|
|
i != ERR_END_OF_FILE &&
|
|
i != ERR_NEED_MORE_DATA)
|
|
Result = ERR_NONE; // no sleep
|
|
|
|
if (Result == ERR_NONE && Stream->State.DropLevel)
|
|
Result = ERR_DROPPING;
|
|
}
|
|
}
|
|
|
|
if (!p->ActiveStreams)
|
|
{
|
|
// release buffers
|
|
Result = ERR_END_OF_FILE;
|
|
|
|
for (No=0;No<MAXREADER;++No)
|
|
{
|
|
format_buffer* Buffer;
|
|
format_reader* Reader = p->Reader+No;
|
|
if (!Reader->Input)
|
|
break;
|
|
while ((Buffer = Format_BufferRemove(Reader))!=NULL)
|
|
{
|
|
Reader->FilePos += Buffer->Length;
|
|
Format_BufferRelease(p,Buffer);
|
|
}
|
|
if (!Reader->NoMoreInput)
|
|
Result = ERR_BUFFER_FULL; // return buffer full so fill mode doesn't go though file
|
|
}
|
|
|
|
State->Time = TIME_UNKNOWN; // player should use file position
|
|
}
|
|
else
|
|
{
|
|
if (AllEndOfFile) // all end of file?
|
|
Result = ERR_END_OF_FILE;
|
|
else
|
|
if (AllNeedMoreData) // all need data?
|
|
{
|
|
Result = ERR_END_OF_FILE;
|
|
for (No=0;No<MAXREADER;++No)
|
|
{
|
|
if (!p->Reader[No].Input)
|
|
break;
|
|
if (!p->Reader[No].NoMoreInput)
|
|
{
|
|
Result = ERR_NEED_MORE_DATA;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
if (Result == ERR_BUFFER_FULL && State->Fill)
|
|
{
|
|
// don't allow exiting fill mode if buffer is less than ProcessMinBuffer+BLOCKSIZE
|
|
// we might fallback to loadmode right after starting playback
|
|
if (p->Reader->BufferAvailable < p->ProcessMinBuffer+BLOCKSIZE && !p->Reader->NoMoreInput)
|
|
Result = ERR_NEED_MORE_DATA;
|
|
}
|
|
}
|
|
|
|
if (p->SyncMode && Result == ERR_END_OF_FILE)
|
|
{
|
|
p->SyncMode = 0;
|
|
if (p->Duration<0 && p->SyncStream)
|
|
State->Time = p->SyncStream->LastTime;
|
|
else
|
|
State->Time = p->Duration;
|
|
Result = ERR_SYNCED;
|
|
}
|
|
|
|
State->BufferUsedAfter = p->BufferUsed;
|
|
return Result;
|
|
}
|
|
|
|
format_ref* Format_RefAlloc(format_base* p, format_buffer* To, int Begin, int Length)
|
|
{
|
|
format_ref* Ref = p->FreeRefs;
|
|
|
|
if (!Ref)
|
|
{
|
|
Ref = (format_ref*) malloc(sizeof(format_ref));
|
|
if (!Ref)
|
|
return NULL;
|
|
}
|
|
else
|
|
p->FreeRefs = Ref->Next;
|
|
|
|
LockEnter(p->BufferLock);
|
|
|
|
//DEBUG_MSG3(DEBUG_FORMAT,"Buffer:%d addref %d->%d",To->Id,To->RefCount,To->RefCount+1);
|
|
++To->RefCount;
|
|
|
|
LockLeave(p->BufferLock);
|
|
|
|
Ref->Length = Length;
|
|
Ref->Begin = Begin;
|
|
Ref->Buffer = To;
|
|
Ref->Next = NULL;
|
|
return Ref;
|
|
}
|
|
|
|
bool_t Format_ReadBuffer(format_reader* Reader, bool_t ToRead)
|
|
{
|
|
bool_t Result;
|
|
|
|
LockEnter(Reader->Format->InputLock);
|
|
LockEnter(Reader->Format->BufferLock);
|
|
|
|
if (!Reader->BufferFirst && !Reader->NoMoreInput)
|
|
{
|
|
format_buffer* Buffer = Reader->InputBuffer;
|
|
|
|
if (!Buffer)
|
|
Buffer = Format_BufferAlloc(Reader->Format,1);
|
|
|
|
if (Buffer)
|
|
{
|
|
// use partial buffer loading after sync for a few times (SYNCREAD)
|
|
// but later have to use full buffer (matroska seeking example can run out of buffer...)
|
|
int TargetLength = (ToRead && Reader->Format->SyncRead>0)? Reader->Format->ReadSize:BLOCKSIZE;
|
|
if (Buffer->Length >= TargetLength)
|
|
{
|
|
Format_BufferInsert(Reader,Buffer);
|
|
Reader->InputBuffer = NULL;
|
|
}
|
|
else
|
|
{
|
|
int Length = Reader->Input->ReadBlock(Reader->Input,&Buffer->Block,Buffer->Length,TargetLength - Buffer->Length);
|
|
if (Length < 0)
|
|
{
|
|
// failed
|
|
if (!Reader->InputBuffer)
|
|
Format_BufferRelease(Reader->Format,Buffer);
|
|
}
|
|
else
|
|
{
|
|
Buffer->Length += Length;
|
|
Reader->NoMoreInput = Length == 0;
|
|
|
|
if (!Length && !Reader->InputBuffer)
|
|
Format_BufferRelease(Reader->Format,Buffer);
|
|
else
|
|
{
|
|
Format_BufferInsert(Reader,Buffer);
|
|
Reader->InputBuffer = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Reader->Format->SyncRead>0)
|
|
Reader->Format->SyncRead--;
|
|
}
|
|
|
|
Result = Reader->BufferFirst != NULL;
|
|
|
|
if (Result && ToRead)
|
|
{
|
|
Reader->ReadBuffer = Format_BufferRemove(Reader);
|
|
if (Reader->ReadBuffer)
|
|
Reader->ReadLen = Reader->ReadBuffer->Length;
|
|
}
|
|
|
|
LockLeave(Reader->Format->BufferLock);
|
|
LockLeave(Reader->Format->InputLock);
|
|
|
|
return Result;
|
|
}
|
|
|
|
format_ref* Format_DupRef(format_base* p, format_ref* Chain, int Offset, int Length)
|
|
{
|
|
format_ref* Head = NULL;
|
|
format_ref** Ptr = &Head;
|
|
|
|
while (Chain && Offset >= Chain->Length)
|
|
{
|
|
Chain = Chain->Next;
|
|
Offset -= Chain->Length;
|
|
}
|
|
|
|
while (Chain && Length > 0)
|
|
{
|
|
int Len = Chain->Length - Offset;
|
|
if (Len > Length)
|
|
Len = Length;
|
|
|
|
*Ptr = Format_RefAlloc(p,Chain->Buffer,Chain->Begin+Offset,Len);
|
|
if (!*Ptr)
|
|
break;
|
|
|
|
Chain = Chain->Next;
|
|
Length -= Len;
|
|
Offset = 0;
|
|
|
|
Ptr = &(*Ptr)->Next;
|
|
}
|
|
|
|
return Head;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------------------
|
|
|
|
static bool_t Reader_GetReadBuffer(format_reader* Reader)
|
|
{
|
|
if (!Reader->Input) // external reader
|
|
{
|
|
Reader->ReadBuffer = NULL;
|
|
return 0;
|
|
}
|
|
|
|
// release old buffer
|
|
if (Reader->ReadBuffer)
|
|
{
|
|
Reader->ReadPos -= Reader->ReadBuffer->Length;
|
|
Format_BufferRelease(Reader->Format,Reader->ReadBuffer);
|
|
}
|
|
|
|
// get new buffer from BufferFirst..BufferLast chain
|
|
Reader->ReadBuffer = Format_BufferRemove(Reader);
|
|
if (Reader->ReadBuffer)
|
|
{
|
|
Reader->ReadLen = Reader->ReadBuffer->Length;
|
|
return 1;
|
|
}
|
|
|
|
// load input now (can't wait for input thread)
|
|
return Format_ReadBuffer(Reader,1);
|
|
}
|
|
|
|
format_ref* Reader_ReadAsRef(format_reader* p, int Length)
|
|
{
|
|
int n;
|
|
format_ref* Ref;
|
|
format_ref** RefPtr;
|
|
|
|
Ref = NULL;
|
|
RefPtr = &Ref;
|
|
|
|
if (Length < 0) // read as much as possible from one buffer
|
|
{
|
|
while (!p->ReadBuffer || p->ReadPos >= p->ReadLen)
|
|
if (!p->GetReadBuffer(p))
|
|
return NULL;
|
|
|
|
n = p->ReadLen - p->ReadPos;
|
|
if (n > -Length)
|
|
n = -Length;
|
|
Length = n;
|
|
}
|
|
|
|
while (Length > 0)
|
|
{
|
|
while (!p->ReadBuffer || p->ReadPos >= p->ReadLen)
|
|
if (!p->GetReadBuffer(p))
|
|
return Ref;
|
|
|
|
n = p->ReadLen - p->ReadPos;
|
|
if (n > Length)
|
|
n = Length;
|
|
|
|
*RefPtr = Format_RefAlloc(p->Format,p->ReadBuffer,p->ReadPos,n);
|
|
if (!*RefPtr)
|
|
break;
|
|
|
|
RefPtr = &(*RefPtr)->Next;
|
|
|
|
Length -= n;
|
|
p->FilePos += n;
|
|
p->ReadPos += n;
|
|
}
|
|
return Ref;
|
|
}
|
|
|
|
static void Reader_Skip(format_reader* p,int n)
|
|
{
|
|
if (n > 0)
|
|
{
|
|
assert(n < 0x10000000);
|
|
p->FilePos += n;
|
|
p->ReadPos += n;
|
|
}
|
|
}
|
|
|
|
static int Reader_Seek(format_reader* Reader,filepos_t Pos,int Origin)
|
|
{
|
|
int Adjust = 0;
|
|
|
|
if (Reader->FilePos >= 0 && !Reader->Format->DisableReader)
|
|
{
|
|
filepos_t RelPos = Pos;
|
|
if (Origin == SEEK_SET)
|
|
RelPos -= Reader->FilePos;
|
|
if (Origin != SEEK_END)
|
|
{
|
|
// check if we can use Format_Skip
|
|
if (RelPos >= 0 && RelPos < Reader->BufferAvailable + SKIPDISTANCE)
|
|
{
|
|
Reader_Skip(Reader,RelPos);
|
|
return ERR_NONE;
|
|
}
|
|
|
|
// check if we are in the same ReadBuffer
|
|
if (RelPos < 0 && Reader->ReadBuffer && Reader->ReadPos + RelPos >= 0)
|
|
{
|
|
Reader->FilePos += RelPos;
|
|
Reader->ReadPos += RelPos;
|
|
return ERR_NONE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Reader->Input)
|
|
return ERR_NOT_SUPPORTED;
|
|
|
|
if (Origin == SEEK_CUR)
|
|
{
|
|
// input is at a different position already, using SEEK_SET
|
|
Origin = SEEK_SET;
|
|
Pos += Reader->FilePos;
|
|
}
|
|
|
|
if (Origin == SEEK_SET)
|
|
{
|
|
// align reading for local files
|
|
Adjust = Pos & (Reader->Format->FileAlign-1);
|
|
Pos &= ~(Reader->Format->FileAlign-1);
|
|
}
|
|
|
|
LockEnter(Reader->Format->InputLock);
|
|
Pos = Reader->Input->Seek(Reader->Input,Pos,Origin);
|
|
if (Pos < 0)
|
|
{
|
|
LockLeave(Reader->Format->InputLock);
|
|
return ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
// query again file size, because http could have reopened connection
|
|
if (Reader->Input->Get(Reader->Input,STREAM_LENGTH,&Reader->Format->FileSize,sizeof(Reader->Format->FileSize)) != ERR_NONE)
|
|
Reader->Format->FileSize = -1;
|
|
|
|
Format_ReleaseBuffers(Reader);
|
|
|
|
Reader->FilePos = Pos + Adjust;
|
|
Reader->ReadPos += Adjust;
|
|
|
|
LockLeave(Reader->Format->InputLock);
|
|
|
|
return ERR_NONE;
|
|
}
|
|
|
|
static bool_t Reader_Eof(format_reader* p)
|
|
{
|
|
return !p->BufferAvailable && !p->ReadBuffer && p->NoMoreInput;
|
|
}
|
|
|
|
static int Reader_Read(format_reader* p, void* Data, int Length0)
|
|
{
|
|
uint8_t* Ptr = (uint8_t*)Data;
|
|
int Length = Length0;
|
|
int n;
|
|
while (Length > 0)
|
|
{
|
|
while (!p->ReadBuffer || p->ReadPos >= p->ReadLen)
|
|
if (!p->GetReadBuffer(p))
|
|
return Length0 - Length;
|
|
|
|
n = p->ReadLen - p->ReadPos;
|
|
if (n > Length)
|
|
n = Length;
|
|
|
|
memcpy(Ptr,p->ReadBuffer->Block.Ptr+p->ReadPos,n);
|
|
Ptr += n;
|
|
Length -= n;
|
|
p->FilePos += n;
|
|
p->ReadPos += n;
|
|
}
|
|
|
|
return Length0;
|
|
}
|
|
|
|
static int Reader_Read8(format_reader* p)
|
|
{
|
|
while (!p->ReadBuffer || p->ReadPos >= p->ReadLen)
|
|
if (!p->GetReadBuffer(p))
|
|
return -1;
|
|
|
|
p->FilePos++;
|
|
return p->ReadBuffer->Block.Ptr[p->ReadPos++];
|
|
}
|
|
|
|
static int Reader_ReadBE16(format_reader* p)
|
|
{
|
|
int v = Reader_Read8(p) << 8;
|
|
v |= Reader_Read8(p);
|
|
return v;
|
|
}
|
|
|
|
static int Reader_ReadLE16(format_reader* p)
|
|
{
|
|
int v = Reader_Read8(p);
|
|
v |= Reader_Read8(p) << 8;
|
|
return v;
|
|
}
|
|
|
|
static int Reader_ReadBE32(format_reader* p)
|
|
{
|
|
int v = Reader_Read8(p) << 24;
|
|
v |= Reader_Read8(p) << 16;
|
|
v |= Reader_Read8(p) << 8;
|
|
v |= Reader_Read8(p);
|
|
return v;
|
|
}
|
|
|
|
static int Reader_ReadLE32(format_reader* p)
|
|
{
|
|
int v = Reader_Read8(p);
|
|
v |= Reader_Read8(p) << 8;
|
|
v |= Reader_Read8(p) << 16;
|
|
v |= Reader_Read8(p) << 24;
|
|
return v;
|
|
}
|
|
|
|
static int64_t Reader_ReadBE64(format_reader* p)
|
|
{
|
|
int64_t v = (int64_t)Reader_ReadBE32(p) << 32;
|
|
v |= (uint32_t)Reader_ReadBE32(p);
|
|
return v;
|
|
}
|
|
|
|
static int64_t Reader_ReadLE64(format_reader* p)
|
|
{
|
|
int64_t v = (uint32_t)Reader_ReadLE32(p);
|
|
v |= (int64_t)Reader_ReadLE32(p) << 32;
|
|
return v;
|
|
}
|
|
|
|
static NOINLINE int GetRate(format_reader* p,format_reader* Last)
|
|
{
|
|
int64_t Rate;
|
|
|
|
if (p->NoMoreInput)
|
|
return MAX_INT;
|
|
|
|
if (p->BufferAvailable <= PROCESSMINVIDEO)
|
|
return -MAX_INT;
|
|
|
|
Rate = (int64_t)p->Ratio * (p->BufferAvailable - (p==Last?READER_BONUS:0));
|
|
if (Rate > MAX_INT)
|
|
return MAX_INT;
|
|
return (int)Rate;
|
|
}
|
|
|
|
static int Format_ReadInput(format_base* p,int BufferMax,int* BufferUsed)
|
|
{
|
|
int No;
|
|
int Left,Length;
|
|
int Result = ERR_NONE;
|
|
format_buffer* Buffer;
|
|
format_reader* Reader;
|
|
int Used = p->BufferUsed;
|
|
|
|
// simple estimate test without locking BufferUsed
|
|
if (Used >= BufferMax)
|
|
{
|
|
*BufferUsed = Used;
|
|
return ERR_BUFFER_FULL;
|
|
}
|
|
|
|
LockEnter(p->InputLock);
|
|
LockEnter(p->BufferLock);
|
|
|
|
// select which reader to use
|
|
Reader = p->Reader;
|
|
if (p->Reader[1].Input)
|
|
{
|
|
int Lowest = GetRate(Reader,p->LastRead);
|
|
for (No=1;No<MAXREADER;++No)
|
|
{
|
|
format_reader* r = p->Reader+No;
|
|
if (r->Ratio > 0)
|
|
{
|
|
int Rate = GetRate(r,p->LastRead);
|
|
if (Lowest > Rate)
|
|
{
|
|
Lowest = Rate;
|
|
Reader = r;
|
|
}
|
|
}
|
|
}
|
|
|
|
p->LastRead = Reader;
|
|
}
|
|
|
|
Buffer = Reader->InputBuffer;
|
|
if (!Buffer)
|
|
{
|
|
// allocate input buffer
|
|
if (p->BufferUsed < BufferMax) // check again (BufferUsed is locked)
|
|
{
|
|
Buffer = Format_BufferAlloc(p,0);
|
|
if (p->BufferUsed >= BufferMax)
|
|
Result = ERR_BUFFER_FULL;
|
|
}
|
|
|
|
if (!Buffer)
|
|
{
|
|
*BufferUsed = p->BufferUsed;
|
|
LockLeave(p->BufferLock);
|
|
LockLeave(p->InputLock);
|
|
return ERR_BUFFER_FULL;
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
Buffer->FilePos = Reader->Input->Seek(Reader->Input,0,SEEK_CUR);
|
|
#endif
|
|
}
|
|
|
|
*BufferUsed = p->BufferUsed;
|
|
LockLeave(p->BufferLock);
|
|
|
|
Left = BLOCKSIZE - Buffer->Length;
|
|
if (Left > p->ReadSize)
|
|
Left = p->ReadSize;
|
|
|
|
if (Reader->Input->DataAvailable)
|
|
{
|
|
int Available = Reader->Input->DataAvailable(Reader->Input);
|
|
if (Available>=0)
|
|
{
|
|
if (!Available)
|
|
{
|
|
if (!Reader->InputBuffer)
|
|
Format_BufferRelease(Reader->Format,Buffer);
|
|
LockLeave(p->InputLock);
|
|
return ERR_NEED_MORE_DATA;
|
|
}
|
|
|
|
if (Left > Available)
|
|
Left = Available;
|
|
}
|
|
}
|
|
|
|
Length = Reader->Input->ReadBlock(Reader->Input,&Buffer->Block,Buffer->Length,Left);
|
|
|
|
if (Length < 0)
|
|
{
|
|
// failed
|
|
if (!Reader->InputBuffer)
|
|
Format_BufferRelease(Reader->Format,Buffer);
|
|
Result = ERR_DEVICE_ERROR;
|
|
}
|
|
else
|
|
{
|
|
Buffer->Length += Length;
|
|
Reader->NoMoreInput = Length == 0;
|
|
|
|
if (!Length)
|
|
{
|
|
// only return end of file if all readers are at the end
|
|
Result = ERR_END_OF_FILE;
|
|
for (No=0;No<MAXREADER;++No)
|
|
{
|
|
if (!p->Reader[No].Input)
|
|
break;
|
|
if (!p->Reader[No].NoMoreInput)
|
|
{
|
|
Result = ERR_NONE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Length && !Reader->InputBuffer)
|
|
Format_BufferRelease(Reader->Format,Buffer);
|
|
else
|
|
{
|
|
if (!Length || Buffer->Length == BLOCKSIZE || p->ProcessMinBuffer==0)
|
|
{
|
|
LockEnter(p->BufferLock);
|
|
Format_BufferInsert(Reader,Buffer);
|
|
LockLeave(p->BufferLock);
|
|
Buffer = NULL;
|
|
}
|
|
Reader->InputBuffer = Buffer;
|
|
}
|
|
}
|
|
|
|
LockLeave(p->InputLock);
|
|
return Result;
|
|
}
|
|
|
|
static NOINLINE void UpdateTotalCount(format_base* p)
|
|
{
|
|
p->TotalCount = p->StreamCount;
|
|
if (p->SubTitle)
|
|
p->Streams[p->TotalCount++] = p->SubTitle;
|
|
if (p->CoverArt)
|
|
p->Streams[p->TotalCount++] = p->CoverArt;
|
|
}
|
|
|
|
static NOINLINE void ReadCoverArt(format_base* p,stream* Input,filepos_t Pos,int Len,const tchar_t* ContentType,const tchar_t* URL)
|
|
{
|
|
uint8_t Probe[512];
|
|
int ProbeLen;
|
|
|
|
Input->Seek(Input,Pos,SEEK_SET);
|
|
ProbeLen = Input->Read(Input,Probe,min(Len,sizeof(Probe)));
|
|
if (ProbeLen>0)
|
|
{
|
|
int *i;
|
|
array List;
|
|
NodeEnumClassEx(&List,RAWIMAGE_CLASS,ContentType,URL,Probe,ProbeLen);
|
|
for (i=ARRAYBEGIN(List,int);i!=ARRAYEND(List,int);++i)
|
|
{
|
|
const tchar_t* Format = LangStr(*i,RAWIMAGE_FORMAT);
|
|
if (tcsnicmp(Format,T("vcodec/"),7)==0)
|
|
{
|
|
format_stream* s = (format_stream*) malloc(sizeof(format_stream));
|
|
if (!s)
|
|
return;
|
|
|
|
memset(s,0,sizeof(format_stream));
|
|
s->Format.Type = PACKET_VIDEO;
|
|
s->Format.Format.Video.Pixel.Flags = PF_FOURCC|PF_NOPREROTATE;
|
|
s->Format.Format.Video.Pixel.FourCC = StringToFourCC(Format+7,1);
|
|
|
|
if (Format_AllocBufferBlock(p,s,Len))
|
|
{
|
|
WriteBlock(&s->BufferBlock,0,Probe,ProbeLen);
|
|
s->Packet.Length = ProbeLen;
|
|
|
|
if (Len>ProbeLen)
|
|
s->Packet.Length += Input->ReadBlock(Input,&s->BufferBlock,ProbeLen,Len-ProbeLen);
|
|
}
|
|
|
|
if (s->Packet.Length>0)
|
|
{
|
|
s->Packet.Data[0] = s->BufferBlock.Ptr;
|
|
s->Packet.Key = 1;
|
|
s->Packet.RefTime = 0;
|
|
|
|
p->CoverArt = s;
|
|
UpdateTotalCount(p);
|
|
Format_PrepairStream(p,s);
|
|
}
|
|
else
|
|
Format_FreeStream(p,s);
|
|
break;
|
|
}
|
|
}
|
|
ArrayClear(&List);
|
|
}
|
|
}
|
|
|
|
static NOINLINE bool_t GetNonStreamingURL(format_base* p,tchar_t* URL,int URLLen,bool_t Local)
|
|
{
|
|
bool_t HasHost;
|
|
int Length;
|
|
stream* Input = p->Reader->Input;
|
|
|
|
if (!Input)
|
|
return 0;
|
|
|
|
LockEnter(p->InputLock);
|
|
if (Input->Get(Input,STREAM_URL,URL,URLLen*sizeof(tchar_t)) != ERR_NONE)
|
|
URL[0] = 0;
|
|
if (Input->Get(Input,STREAM_LENGTH,&Length,sizeof(Length)) != ERR_NONE)
|
|
Length = -1;
|
|
LockLeave(p->InputLock);
|
|
|
|
if (!URL[0])
|
|
return 0; //no URL?
|
|
|
|
GetMime(URL,NULL,0,&HasHost);
|
|
if (HasHost && (Local || Length<0))
|
|
return 0; //non local or streaming
|
|
return 1;
|
|
}
|
|
|
|
static NOINLINE bool_t OpenCoverAtr(format_base* p, const tchar_t* Base, const tchar_t* Name)
|
|
{
|
|
tchar_t Path[MAXPATH];
|
|
int Length;
|
|
stream* Input;
|
|
|
|
AbsPath(Path,TSIZEOF(Path),Name,Base);
|
|
if (FileExits(Path) && (Input = GetStream(Path,1))!=NULL)
|
|
{
|
|
//todo: cache global cover art...
|
|
|
|
if (Input->Set(Input,STREAM_URL,Path,sizeof(tchar_t)*(tcslen(Path)+1)) == ERR_NONE &&
|
|
Input->Get(Input,STREAM_LENGTH,&Length,sizeof(Length)) == ERR_NONE)
|
|
ReadCoverArt(p,Input,0,Length,NULL,Path);
|
|
|
|
Input->Set(Input,STREAM_URL,NULL,0);
|
|
NodeDelete((node*)Input);
|
|
}
|
|
return p->CoverArt != NULL;
|
|
}
|
|
|
|
static NOINLINE void FindCoverArt(format_base* p)
|
|
{
|
|
tchar_t Value[MAXPATH];
|
|
|
|
if (p->Comment.Node && NodeIsClass(p->Comment.Node->Class,PLAYER_ID) &&
|
|
((player*)p->Comment.Node)->CommentByName(p->Comment.Node,-1,PlayerComment(COMMENT_COVER),Value,TSIZEOF(Value)))
|
|
{
|
|
tchar_t* SPos = tcschr(Value,':');
|
|
tchar_t* SLen = SPos?tcschr(SPos+1,':'):NULL;
|
|
tchar_t* ContentType = SLen?tcschr(SLen+1,':'):NULL;
|
|
int Pos = SPos ? StringToInt(SPos+1,0):0;
|
|
int Len = SLen ? StringToInt(SLen+1,0):0;
|
|
|
|
if (ContentType && Len>0)
|
|
{
|
|
stream* Input = p->Reader->Input;
|
|
filepos_t SavePos;
|
|
LockEnter(p->InputLock);
|
|
|
|
SavePos = Input->Seek(Input,0,SEEK_CUR);
|
|
if (SavePos >= 0)
|
|
{
|
|
if (*(++ContentType)==0)
|
|
ContentType = NULL;
|
|
|
|
ReadCoverArt(p,Input,Pos,Len,ContentType,NULL);
|
|
Input->Seek(Input,SavePos,SEEK_SET);
|
|
}
|
|
LockLeave(p->InputLock);
|
|
}
|
|
}
|
|
else if (NodeIsClass(p->Format.Class,RAWAUDIO_CLASS) && GetNonStreamingURL(p,Value,TSIZEOF(Value),1))
|
|
{
|
|
tchar_t Base[MAXPATH];
|
|
SplitURL(Value,Base,TSIZEOF(Base),Base,TSIZEOF(Base),NULL,0,NULL,0);
|
|
|
|
if (!OpenCoverAtr(p,Base,T("cover.jpg")) &&
|
|
!OpenCoverAtr(p,Base,T("folder.jpg")) &&
|
|
!OpenCoverAtr(p,Base,T("front.jpg")) &&
|
|
!OpenCoverAtr(p,Base,T("coverart.jpg")));
|
|
}
|
|
}
|
|
|
|
static void FindSubtitles(format_base* p)
|
|
{
|
|
/*
|
|
//!SUBTITLE
|
|
tchar_t* SubExt[] = { T("sub"),T("str"),T("smi"), NULL };
|
|
tchar_t** s;
|
|
|
|
tchar_t URL[MAXPATH];
|
|
if (!GetNonStreamingURL(p,URL,TSIZEOF(URL),0))
|
|
return;
|
|
|
|
for (s=SubExt;*s && !p->SubTitle;++s)
|
|
if (SetFileExt(URL,TSIZEOF(URL),*s))
|
|
{
|
|
stream* Input = GetStream(URL,1);
|
|
if (Input)
|
|
{
|
|
bool_t Silent = 1;
|
|
Input->Set(Input,STREAM_SILENT,&Silent,sizeof(Silent));
|
|
|
|
if (Input->Set(Input,STREAM_URL,URL,sizeof(tchar_t)*(tcslen(URL)+1)) == ERR_NONE)
|
|
{
|
|
p->SubTitle = Format_LoadSubTitle(p,Input);
|
|
if (p->SubTitle)
|
|
{
|
|
UpdateTotalCount(p);
|
|
Format_PrepairStream(p,(format_stream*)p->SubTitle);
|
|
}
|
|
}
|
|
Input->Delete(Input);
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
void Format_PrepairStream(format_base* p,format_stream* Stream)
|
|
{
|
|
switch (Stream->Format.Type)
|
|
{
|
|
case PACKET_VIDEO: Stream->PacketBurst = VIDEO_BURST; break;
|
|
case PACKET_AUDIO: Stream->PacketBurst = AUDIO_BURST; break;
|
|
case PACKET_SUBTITLE: Stream->PacketBurst = 1; break;
|
|
}
|
|
|
|
if (p->ProcessMinBuffer)
|
|
p->ProcessMinBuffer = IsVideo(p) ? PROCESSMINVIDEO:PROCESSMINAUDIO;
|
|
|
|
if (Stream->Format.Type == PACKET_AUDIO && Stream->Format.Format.Audio.Format == AUDIOFMT_PCM)
|
|
{
|
|
// there could be alignement problems (with memory and with audio.BlockSize)
|
|
Stream->Fragmented = 0;
|
|
Stream->ForceMerge = 1;
|
|
}
|
|
|
|
if (p->UpdateStreams.Func)
|
|
p->UpdateStreams.Func(p->UpdateStreams.This,0,0);
|
|
|
|
if (Stream->Format.Type == PACKET_VIDEO && p->NeedSubtitles)
|
|
{
|
|
p->NeedSubtitles = 0;
|
|
FindSubtitles(p);
|
|
}
|
|
}
|
|
|
|
static NOINLINE void DefaultPalette(packetformat* p)
|
|
{
|
|
if (p->Format.Video.Pixel.BitCount >= 1 &&
|
|
p->Format.Video.Pixel.BitCount <= 8 &&
|
|
p->ExtraLength >= (4 << p->Format.Video.Pixel.BitCount))
|
|
p->Format.Video.Pixel.Palette = p->Extra;
|
|
}
|
|
|
|
int Format_BitmapInfoMem(format_stream* s,void* Data,int Length)
|
|
{
|
|
if (Length>=4)
|
|
{
|
|
char* p = (char*)Data;
|
|
int Size = INT32LE(*(int32_t*)p);
|
|
if (Length >= Size)
|
|
{
|
|
PacketFormatClear(&s->Format);
|
|
s->Format.Type = PACKET_VIDEO;
|
|
s->Format.Format.Video.Width = INT32LE(*(int32_t*)(p+4));
|
|
s->Format.Format.Video.Height = INT32LE(*(int32_t*)(p+8));
|
|
s->Format.Format.Video.Pixel.BitCount = INT16LE(*(int16_t*)(p+14));
|
|
s->Format.Format.Video.Pixel.FourCC = INT32LE(*(int32_t*)(p+16));
|
|
s->Format.Format.Video.Aspect = ASPECT_ONE; //todo
|
|
|
|
if (PacketFormatExtra(&s->Format,Size-40))
|
|
{
|
|
memcpy(s->Format.Extra,p+40,s->Format.ExtraLength);
|
|
DefaultPalette(&s->Format);
|
|
}
|
|
|
|
PacketFormatDefault(&s->Format);
|
|
}
|
|
else
|
|
Size = 4;
|
|
Length -= Size;
|
|
}
|
|
return Length;
|
|
}
|
|
|
|
void Format_BitmapInfo(format_reader* p,format_stream* s,int Length)
|
|
{
|
|
if (Length>=4)
|
|
{
|
|
int Size = p->ReadLE32(p); //size
|
|
if (Length >= Size)
|
|
{
|
|
PacketFormatClear(&s->Format);
|
|
s->Format.Type = PACKET_VIDEO;
|
|
s->Format.Format.Video.Width = p->ReadLE32(p);
|
|
s->Format.Format.Video.Height = p->ReadLE32(p);
|
|
p->ReadLE16(p); //planes
|
|
s->Format.Format.Video.Pixel.BitCount = p->ReadLE16(p);
|
|
s->Format.Format.Video.Pixel.FourCC = p->ReadLE32(p);
|
|
s->Format.Format.Video.Aspect = ASPECT_ONE; //todo
|
|
p->Skip(p,20); //SizeImage,XPelsPerMeter,YPelsPerMeter,ClrUsed,ClrImportant
|
|
|
|
if (PacketFormatExtra(&s->Format,Size-40))
|
|
{
|
|
p->Read(p,s->Format.Extra,s->Format.ExtraLength);
|
|
DefaultPalette(&s->Format);
|
|
}
|
|
|
|
PacketFormatDefault(&s->Format);
|
|
}
|
|
else
|
|
Size = 4;
|
|
|
|
Length -= Size;
|
|
}
|
|
p->Skip(p,Length);
|
|
}
|
|
|
|
int Format_WaveFormatMem(format_stream* s,void* Data,int Length)
|
|
{
|
|
if (Length >= 16)
|
|
{
|
|
char* p = (char*)Data;
|
|
|
|
PacketFormatClear(&s->Format);
|
|
s->Format.Type = PACKET_AUDIO;
|
|
s->Format.Format.Audio.Format = INT16LE(*(int16_t*)(p+0));
|
|
s->Format.Format.Audio.Channels =INT16LE(*(int16_t*)(p+2));
|
|
s->Format.Format.Audio.SampleRate = INT32LE(*(int32_t*)(p+4));
|
|
s->Format.ByteRate = INT32LE(*(int32_t*)(p+8));
|
|
s->Format.Format.Audio.BlockAlign = INT16LE(*(int16_t*)(p+12));
|
|
s->Format.Format.Audio.Bits = INT16LE(*(int16_t*)(p+14));
|
|
Length -= 16;
|
|
|
|
if (Length >= 2)
|
|
{
|
|
int Extra = INT16LE(*(int16_t*)(p+16));
|
|
Length -= 2;
|
|
if (Extra > 0 && Length >= Extra)
|
|
{
|
|
if (PacketFormatExtra(&s->Format,Extra))
|
|
memcpy(s->Format.Extra,p+18,s->Format.ExtraLength);
|
|
Length -= Extra;
|
|
}
|
|
}
|
|
|
|
PacketFormatDefault(&s->Format);
|
|
}
|
|
return Length;
|
|
}
|
|
|
|
void Format_WaveFormat(format_reader* p,format_stream* s,int Length)
|
|
{
|
|
if (Length >= 16)
|
|
{
|
|
PacketFormatClear(&s->Format);
|
|
s->Format.Type = PACKET_AUDIO;
|
|
s->Format.Format.Audio.Format = p->ReadLE16(p);
|
|
s->Format.Format.Audio.Channels = p->ReadLE16(p);
|
|
s->Format.Format.Audio.SampleRate = p->ReadLE32(p);
|
|
s->Format.ByteRate = p->ReadLE32(p);
|
|
s->Format.Format.Audio.BlockAlign = p->ReadLE16(p);
|
|
s->Format.Format.Audio.Bits = p->ReadLE16(p);
|
|
Length -= 16;
|
|
|
|
if (Length >= 2)
|
|
{
|
|
int Extra = p->ReadLE16(p);
|
|
Length -= 2;
|
|
if (Extra > 0 && Length >= Extra)
|
|
{
|
|
if (PacketFormatExtra(&s->Format,Extra))
|
|
p->Read(p,s->Format.Extra,s->Format.ExtraLength);
|
|
Length -= Extra;
|
|
}
|
|
}
|
|
|
|
PacketFormatDefault(&s->Format);
|
|
}
|
|
p->Skip(p,Length);
|
|
}
|
|
|
|
void Format_RemoveStream(format_base* p)
|
|
{
|
|
if (p->StreamCount)
|
|
{
|
|
format_stream* Stream = p->Streams[--p->StreamCount];
|
|
UpdateTotalCount(p);
|
|
if (p->FreeStream)
|
|
p->FreeStream(p,Stream);
|
|
Format_FreeStream(p,Stream);
|
|
}
|
|
}
|
|
|
|
format_stream* Format_AddStream(format_base* p, int Length)
|
|
{
|
|
format_stream* s;
|
|
|
|
if (p->StreamCount >= MAXSTREAM-1-1) //subtitle,coverart
|
|
return NULL;
|
|
|
|
s = (format_stream*) malloc(Length);
|
|
if (!s)
|
|
return NULL;
|
|
|
|
memset(s,0,Length);
|
|
s->Reader = p->Reader; // default
|
|
s->No = p->StreamCount;
|
|
s->LastTime = TIME_SYNC;
|
|
p->Streams[p->StreamCount++] = s;
|
|
UpdateTotalCount(p);
|
|
return s;
|
|
}
|
|
|
|
static int Create(format_base* p)
|
|
{
|
|
int No;
|
|
format_reader* Reader = p->Reader;
|
|
for (No=0;No<MAXREADER;++No,++Reader)
|
|
{
|
|
Reader->Format = p;
|
|
Reader->Seek = Reader_Seek;
|
|
Reader->Eof = Reader_Eof;
|
|
Reader->Skip = Reader_Skip;
|
|
Reader->Read = Reader_Read;
|
|
Reader->Read8 = Reader_Read8;
|
|
Reader->ReadLE16 = Reader_ReadLE16;
|
|
Reader->ReadBE16 = Reader_ReadBE16;
|
|
Reader->ReadLE32 = Reader_ReadLE32;
|
|
Reader->ReadBE32 = Reader_ReadBE32;
|
|
Reader->ReadLE64 = Reader_ReadLE64;
|
|
Reader->ReadBE64 = Reader_ReadBE64;
|
|
Reader->ReadAsRef = Reader_ReadAsRef;
|
|
Reader->GetReadBuffer = Reader_GetReadBuffer;
|
|
}
|
|
|
|
p->Format.Enum = (nodeenum)FormatBaseEnum;
|
|
p->Format.Get = (nodeget)FormatBaseGet;
|
|
p->Format.Set = (nodeset)FormatBaseSet;
|
|
|
|
p->Format.Sync = (fmtsync)Format_Sync;
|
|
p->Format.Read = (fmtread)Format_ReadInput;
|
|
p->Format.Process = (fmtprocess)Format_Process;
|
|
p->FillQueue = (fmtfill)Format_FillQueue;
|
|
p->Process = (fmtstreamprocess)Format_ProcessStream;
|
|
p->Sended = (fmtstream)Format_Sended;
|
|
p->FileAlign = 1;
|
|
p->Timing = 1;
|
|
p->MinHeaderLoad = BLOCKSIZE;
|
|
|
|
#ifdef TARGET_PALMOS
|
|
p->UseBufferBlock = Context()->LowMemory;
|
|
#endif
|
|
|
|
return ERR_NONE;
|
|
}
|
|
|
|
static const nodedef FormatBase =
|
|
{
|
|
sizeof(format_base)|CF_ABSTRACT,
|
|
FORMATBASE_CLASS,
|
|
FORMAT_CLASS,
|
|
PRI_DEFAULT,
|
|
(nodecreate)Create,
|
|
};
|
|
|
|
void FormatBase_Init()
|
|
{
|
|
NodeRegisterClass(&FormatBase);
|
|
}
|
|
|
|
void FormatBase_Done()
|
|
{
|
|
NodeUnRegisterClass(FORMATBASE_CLASS);
|
|
}
|