Update ffms2 to r312. Fixes a bunch of rather critical bugs.
Originally committed to SVN as r4487.
This commit is contained in:
parent
60b9e5dee6
commit
ea6dac551e
17 changed files with 445 additions and 382 deletions
|
@ -22,7 +22,7 @@
|
|||
#define FFMS_H
|
||||
|
||||
// Version format: major - minor - micro - bump
|
||||
#define FFMS_VERSION ((2 << 24) | (13 << 16)| (0 << 8) | 1)
|
||||
#define FFMS_VERSION ((2 << 24) | (13 << 16) | (1 << 8) | 0)
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
@ -36,11 +36,15 @@
|
|||
|
||||
#ifdef _WIN32
|
||||
# define FFMS_CC __stdcall
|
||||
# ifdef _MSC_VER
|
||||
# ifdef FFMS_EXPORTS
|
||||
# define FFMS_API(ret) EXTERN_C __declspec(dllexport) ret FFMS_CC
|
||||
# else
|
||||
# define FFMS_API(ret) EXTERN_C __declspec(dllimport) ret FFMS_CC
|
||||
# endif
|
||||
# else
|
||||
# define FFMS_API(ret) EXTERN_C ret FFMS_CC
|
||||
# endif
|
||||
#else
|
||||
# define FFMS_CC
|
||||
# define FFMS_API(ret) EXTERN_C ret FFMS_CC
|
||||
|
@ -253,6 +257,7 @@ FFMS_API(void) FFMS_ResetOutputFormatV(FFMS_VideoSource *V);
|
|||
FFMS_API(int) FFMS_SetPP(FFMS_VideoSource *V, const char *PP, FFMS_ErrorInfo *ErrorInfo);
|
||||
FFMS_API(void) FFMS_ResetPP(FFMS_VideoSource *V);
|
||||
FFMS_API(void) FFMS_DestroyIndex(FFMS_Index *Index);
|
||||
FFMS_API(int) FFMS_GetSourceType(FFMS_Index *Index);
|
||||
FFMS_API(int) FFMS_GetFirstTrackOfType(FFMS_Index *Index, int TrackType, FFMS_ErrorInfo *ErrorInfo);
|
||||
FFMS_API(int) FFMS_GetFirstIndexedTrackOfType(FFMS_Index *Index, int TrackType, FFMS_ErrorInfo *ErrorInfo);
|
||||
FFMS_API(int) FFMS_GetNumTracks(FFMS_Index *Index);
|
||||
|
|
|
@ -51,10 +51,17 @@
|
|||
# warning "Your FFmpeg is too old to support reporting colorspace and luma range information. The corresponding fields of FFMS_VideoProperties will be set to 0. Please update FFmpeg to get rid of this warning."
|
||||
# endif
|
||||
# endif
|
||||
# if (LIBAVCODEC_VERSION_INT) < (AV_VERSION_INT(52,30,2))
|
||||
# define AV_PKT_FLAG_KEY PKT_FLAG_KEY
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef AV_PKT_FLAG_KEY
|
||||
# define AV_PKT_FLAG_KEY PKT_FLAG_KEY
|
||||
#ifdef LIBSWSCALE_VERSION_INT
|
||||
# if (LIBSWSCALE_VERSION_INT) < (AV_VERSION_INT(0,8,0))
|
||||
# define FFMS_SWS_CONST_PARAM
|
||||
# else
|
||||
# define FFMS_SWS_CONST_PARAM const
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
|
|
|
@ -104,7 +104,7 @@ FFMS_AudioSource::FFMS_AudioSource(const char *SourceFile, FFMS_Index *Index, in
|
|||
throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT,
|
||||
"Not an audio track");
|
||||
|
||||
if (Index[Track].size() == 0)
|
||||
if (Index->at(Track).size() == 0)
|
||||
throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT,
|
||||
"Audio track contains no audio frames");
|
||||
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2004-2008 Mike Matsnev. All Rights Reserved.
|
||||
*
|
||||
* $Id: CoParser.h,v 1.21 2008/03/29 15:41:28 mike Exp $
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef COPARSER_H
|
||||
#define COPARSER_H
|
||||
|
||||
// Generic multimedia container parsers support
|
||||
|
||||
// random access stream, this will be provided by
|
||||
// IMMContainer's client
|
||||
interface __declspec(uuid("8E192E9F-E536-4027-8D46-664CC7A102C5")) IMMStream;
|
||||
interface IMMStream : public IUnknown {
|
||||
// read count bytes starting at position
|
||||
STDMETHOD(Read)( unsigned long long position,
|
||||
void *buffer,
|
||||
unsigned int *count) = 0;
|
||||
|
||||
// scan the file starting at position for the signature
|
||||
// signature can't have zero bytes
|
||||
STDMETHOD(Scan)( unsigned long long *position,
|
||||
unsigned int signature) = 0;
|
||||
};
|
||||
|
||||
interface __declspec(uuid("A237C873-C6AD-422E-90DB-7CB4627DCFD9")) IMMStreamOpen;
|
||||
interface IMMStreamOpen : public IUnknown {
|
||||
STDMETHOD(Open)(LPCWSTR name) = 0;
|
||||
};
|
||||
|
||||
interface __declspec(uuid("D8FF7213-6E09-4256-A2E5-5872C798B128")) IMMFrame;
|
||||
interface IMMFrame : public IMediaSample2 {
|
||||
// track number must be the same as returned by
|
||||
// IMMContainer->EnumTracks() iterator
|
||||
STDMETHOD_(unsigned, GetTrack)() = 0;
|
||||
STDMETHOD(SetTrack)(unsigned) = 0;
|
||||
|
||||
STDMETHOD_(unsigned, GetPre)() = 0;
|
||||
STDMETHOD(SetPre)(unsigned) = 0;
|
||||
};
|
||||
|
||||
interface __declspec(uuid("B8324E2A-21A9-46A1-8922-70C55D06311A")) IMMErrorInfo;
|
||||
interface IMMErrorInfo : public IUnknown {
|
||||
STDMETHOD(LogError)(BSTR message) = 0; // message is owned by the caller
|
||||
STDMETHOD(LogWarning)(BSTR message) = 0;
|
||||
};
|
||||
|
||||
interface __declspec(uuid("C7120EDB-528C-4ebe-BB53-DA8E70E618EE")) IMemAlloc;
|
||||
interface IMemAlloc : public IUnknown {
|
||||
STDMETHOD(GetBuffer)(HANDLE hAbortEvt, DWORD size, IMMFrame **pS) = 0;
|
||||
};
|
||||
|
||||
// container itself should support IPropertyBag and return
|
||||
// at least Duration[UI8] (in ns) property
|
||||
// if a containter supports multiple segments in the same
|
||||
// physical file it should return SegmentTop[UI8] property
|
||||
// that return the offset of the first byte after this
|
||||
// segment's end
|
||||
interface __declspec(uuid("A369001B-F292-45f7-A942-84F9C8C0718A")) IMMContainer;
|
||||
interface IMMContainer : public IUnknown {
|
||||
STDMETHOD(Open)( IMMStream *stream,
|
||||
unsigned long long position,
|
||||
IMMErrorInfo *einfo,
|
||||
IMemAlloc *alloc) = 0;
|
||||
STDMETHOD(GetProgress)(unsigned long long *cur,unsigned long long *max) = 0;
|
||||
STDMETHOD(AbortOpen)() = 0;
|
||||
|
||||
// pu->Next() returns objects supporting IPropertyBag interface
|
||||
STDMETHOD(EnumTracks)(IEnumUnknown **pu) = 0;
|
||||
|
||||
// pu->Next() returns objects supporting IProperyBag and IEnumUnknown interfaces
|
||||
STDMETHOD(EnumEditions)(IEnumUnknown **pu) = 0;
|
||||
|
||||
// pu->Next() returns objects supporting IProperyBag and IMMStream interfaces
|
||||
STDMETHOD(EnumAttachments)(IEnumUnknown **pu) = 0;
|
||||
|
||||
// S_FALSE is end of stream, S_OK next valid frame returned, E_ABORT wait aborted
|
||||
STDMETHOD(ReadFrame)(HANDLE hAbortEvt, IMMFrame **frame) = 0;
|
||||
|
||||
// seeking
|
||||
STDMETHOD(Seek)(unsigned long long timecode,unsigned flags) = 0;
|
||||
};
|
||||
|
||||
/* FIXME: duplicated in matroska parser
|
||||
enum { // Track type
|
||||
TT_VIDEO = 1,
|
||||
TT_AUDIO = 2,
|
||||
TT_SUBS = 17,
|
||||
|
||||
TT_INTERLEAVED = 0x10001,
|
||||
};
|
||||
*/
|
||||
|
||||
enum { // Seek flags
|
||||
MMSF_PREV_KF = 1,
|
||||
MMSF_NEXT_KF = 2
|
||||
};
|
||||
|
||||
/* Track properties [IPropertyBag from EnumTracks->Next()]
|
||||
Name Type Optional
|
||||
DefaultDuration UI8 Yes in ns
|
||||
Video.Interlaced BOOL Yes
|
||||
Video.DisplayWidth UI4 Yes
|
||||
Video.DisplayHeight UI4 Yes
|
||||
Video.PixelWidth UI4 No
|
||||
Video.PixelHeight UI4 No
|
||||
CodecID BSTR No
|
||||
Type UI4 No TT_* enumeration
|
||||
CodecPrivate ARRAY|UI1 Yes
|
||||
Audio.Channels UI4 No
|
||||
Audio.BitDepth UI4 Yes
|
||||
Audio.SamplingFreq UI4 No
|
||||
Audio.OutputSamplingFreq UI4 Yes
|
||||
Language BSTR Yes
|
||||
Name BSTR Yes
|
||||
FOURCC UI4 Yes
|
||||
*/
|
||||
|
||||
#endif
|
|
@ -25,7 +25,9 @@
|
|||
#include "audiosource.h"
|
||||
#include "indexing.h"
|
||||
|
||||
|
||||
#ifdef FFMS_WIN_DEBUG
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
static bool FFmpegInited = false;
|
||||
bool HasHaaliMPEG = false;
|
||||
|
@ -71,6 +73,9 @@ void av_log_windebug_callback(void* ptr, int level, const char* fmt, va_list vl)
|
|||
FFMS_API(void) FFMS_Init(int CPUFeatures) {
|
||||
if (!FFmpegInited) {
|
||||
av_register_all();
|
||||
#if defined(_WIN32) && defined(FFMS_USE_UTF8_PATHS)
|
||||
ffms_patch_lavf_file_open();
|
||||
#endif
|
||||
#ifdef FFMS_WIN_DEBUG
|
||||
av_log_set_callback(av_log_windebug_callback);
|
||||
av_log_set_level(AV_LOG_INFO);
|
||||
|
@ -98,6 +103,8 @@ FFMS_API(void) FFMS_SetLogLevel(int Level) {
|
|||
}
|
||||
|
||||
FFMS_API(FFMS_VideoSource *) FFMS_CreateVideoSource(const char *SourceFile, int Track, FFMS_Index *Index, int Threads, int SeekMode, FFMS_ErrorInfo *ErrorInfo) {
|
||||
if (Threads < 1)
|
||||
Threads = 1;
|
||||
try {
|
||||
switch (Index->Decoder) {
|
||||
case FFMS_SOURCE_LAVF:
|
||||
|
@ -227,6 +234,10 @@ FFMS_API(void) FFMS_DestroyIndex(FFMS_Index *Index) {
|
|||
delete Index;
|
||||
}
|
||||
|
||||
FFMS_API(int) FFMS_GetSourceType(FFMS_Index *Index) {
|
||||
return Index->Decoder;
|
||||
}
|
||||
|
||||
FFMS_API(int) FFMS_GetFirstTrackOfType(FFMS_Index *Index, int TrackType, FFMS_ErrorInfo *ErrorInfo) {
|
||||
ClearErrorInfo(ErrorInfo);
|
||||
for (int i = 0; i < static_cast<int>(Index->size()); i++)
|
||||
|
|
|
@ -1,143 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2004-2008 Mike Matsnev. All Rights Reserved.
|
||||
*
|
||||
* $Id: guids.h,v 1.16 2008/03/29 15:41:30 mike Exp $
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GUIDS_H
|
||||
#define GUIDS_H
|
||||
|
||||
// {941A4793-A705-4312-8DFC-C11CA05F397E}
|
||||
DEFINE_GUID(CLSID_RealAudioDecoder,
|
||||
0x941A4793, 0xA705, 0x4312, 0x8D, 0xFC, 0xC1, 0x1C, 0xA0, 0x5F, 0x39, 0x7E);
|
||||
|
||||
// {B8CBBAD8-AA1A-4cea-B95E-730041A55EF0}
|
||||
DEFINE_GUID(MEDIASUBTYPE_WavpackHybrid,
|
||||
0xb8cbbad8, 0xaa1a, 0x4cea, 0xb9, 0x5e, 0x73, 0x0, 0x41, 0xa5, 0x5e, 0xf0);
|
||||
|
||||
// {1541C5C0-CDDF-477d-BC0A-86F8AE7F8354}
|
||||
DEFINE_GUID(MEDIASUBTYPE_FLAC_FRAMED,
|
||||
0x1541c5c0, 0xcddf, 0x477d, 0xbc, 0xa, 0x86, 0xf8, 0xae, 0x7f, 0x83, 0x54);
|
||||
|
||||
// {8D2FD10B-5841-4a6b-8905-588FEC1ADED9}
|
||||
DEFINE_GUID(MEDIASUBTYPE_Vorbis2,
|
||||
0x8d2fd10b, 0x5841, 0x4a6b, 0x89, 0x5, 0x58, 0x8f, 0xec, 0x1a, 0xde, 0xd9);
|
||||
|
||||
// {B36E107F-A938-4387-93C7-55E966757473}
|
||||
DEFINE_GUID(FORMAT_VorbisFormat2,
|
||||
0xb36e107f, 0xa938, 0x4387, 0x93, 0xc7, 0x55, 0xe9, 0x66, 0x75, 0x74, 0x73);
|
||||
|
||||
// {E487EB08-6B26-4be9-9DD3-993434D313FD}
|
||||
DEFINE_GUID(MEDIATYPE_Subtitle,
|
||||
0xe487eb08, 0x6b26, 0x4be9, 0x9d, 0xd3, 0x99, 0x34, 0x34, 0xd3, 0x13, 0xfd);
|
||||
|
||||
// {87C0B230-03A8-4fdf-8010-B27A5848200D}
|
||||
DEFINE_GUID(MEDIASUBTYPE_UTF8,
|
||||
0x87c0b230, 0x3a8, 0x4fdf, 0x80, 0x10, 0xb2, 0x7a, 0x58, 0x48, 0x20, 0xd);
|
||||
|
||||
// {3020560F-255A-4ddc-806E-6C5CC6DCD70A}
|
||||
DEFINE_GUID(MEDIASUBTYPE_SSA,
|
||||
0x3020560f, 0x255a, 0x4ddc, 0x80, 0x6e, 0x6c, 0x5c, 0xc6, 0xdc, 0xd7, 0xa);
|
||||
|
||||
// {326444F7-686F-47ff-A4B2-C8C96307B4C2}
|
||||
DEFINE_GUID(MEDIASUBTYPE_ASS,
|
||||
0x326444f7, 0x686f, 0x47ff, 0xa4, 0xb2, 0xc8, 0xc9, 0x63, 0x7, 0xb4, 0xc2);
|
||||
|
||||
// {370689E7-B226-4f67-978D-F10BC1A9C6AE}
|
||||
DEFINE_GUID(MEDIASUBTYPE_ASS2,
|
||||
0x370689e7, 0xb226, 0x4f67, 0x97, 0x8d, 0xf1, 0xb, 0xc1, 0xa9, 0xc6, 0xae);
|
||||
|
||||
// {B753B29A-0A96-45be-985F-68351D9CAB90}
|
||||
DEFINE_GUID(MEDIASUBTYPE_USF,
|
||||
0xb753b29a, 0xa96, 0x45be, 0x98, 0x5f, 0x68, 0x35, 0x1d, 0x9c, 0xab, 0x90);
|
||||
|
||||
// {F7239E31-9599-4e43-8DD5-FBAF75CF37F1}
|
||||
DEFINE_GUID(MEDIASUBTYPE_VOBSUB,
|
||||
0xf7239e31, 0x9599, 0x4e43, 0x8d, 0xd5, 0xfb, 0xaf, 0x75, 0xcf, 0x37, 0xf1);
|
||||
|
||||
// {0B10E53F-ABF9-4581-BE9C-2C9A5EC6F2E0}
|
||||
DEFINE_GUID(MEDIASUBTYPE_VOBSUB2,
|
||||
0xb10e53f, 0xabf9, 0x4581, 0xbe, 0x9c, 0x2c, 0x9a, 0x5e, 0xc6, 0xf2, 0xe0);
|
||||
|
||||
// {5965E924-63F9-4a64-B71E-F75188FD6384}
|
||||
DEFINE_GUID(MEDIASUBTYPE_DXRSub,
|
||||
0x5965e924, 0x63f9, 0x4a64, 0xb7, 0x1e, 0xf7, 0x51, 0x88, 0xfd, 0x63, 0x84);
|
||||
|
||||
// {A33D2F7D-96BC-4337-B23B-A8B9FBC295E9}
|
||||
DEFINE_GUID(FORMAT_SubtitleInfo,
|
||||
0xa33d2f7d, 0x96bc, 0x4337, 0xb2, 0x3b, 0xa8, 0xb9, 0xfb, 0xc2, 0x95, 0xe9);
|
||||
|
||||
DEFINE_GUID(MEDIASUBTYPE_AC3,
|
||||
0x00002000, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71);
|
||||
|
||||
DEFINE_GUID(MEDIASUBTYPE_EAC3,
|
||||
0x0000EAC3, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71);
|
||||
|
||||
DEFINE_GUID(MEDIASUBTYPE_DTS,
|
||||
0x00002001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71);
|
||||
|
||||
// {B855F837-194F-4d9a-9358-E31ED6B49D03}
|
||||
DEFINE_GUID(MEDIASUBTYPE_VC1,
|
||||
0xb855f837, 0x194f, 0x4d9a, 0x93, 0x58, 0xe3, 0x1e, 0xd6, 0xb4, 0x9d, 0x3);
|
||||
|
||||
// {93A22E7A-5091-45ef-BA61-6DA26156A5D0}
|
||||
DEFINE_GUID(CLSID_DirectVobSubFilter,
|
||||
0x93a22e7a, 0x5091, 0x45ef, 0xba, 0x61, 0x6d, 0xa2, 0x61, 0x56, 0xa5, 0xd0);
|
||||
|
||||
// {9852A670-F845-491b-9BE6-EBD841B8A613}
|
||||
DEFINE_GUID(CLSID_DirectVobSubFilter2,
|
||||
0x9852a670, 0xf845, 0x491b, 0x9b, 0xe6, 0xeb, 0xd8, 0x41, 0xb8, 0xa6, 0x13);
|
||||
|
||||
// {F6D90F11-9C73-11D3-B32E-00C04F990BB4}
|
||||
DEFINE_GUID(CLSID_MSXML2,
|
||||
0xF6D90F11, 0x9C73, 0x11D3, 0xB3, 0x2E, 0x00, 0xC0, 0x4F, 0x99, 0x0B, 0xB4);
|
||||
|
||||
// {9F062738-CD84-4F54-A3C4-BD5EB44F416B}
|
||||
DEFINE_GUID(CLSID_SonicAudioDec,
|
||||
0x9F062738, 0xCD84, 0x4F54, 0xA3, 0xC4, 0xBD, 0x5E, 0xB4, 0x4F, 0x41, 0x6B);
|
||||
|
||||
// {53D9DE0B-FC61-4650-9773-74D13CC7E582}
|
||||
DEFINE_GUID(CLSID_DiskFile,
|
||||
0x53d9de0b, 0xfc61, 0x4650, 0x97, 0x73, 0x74, 0xd1, 0x3c, 0xc7, 0xe5, 0x82);
|
||||
|
||||
// {BD4FB4BE-809D-487b-ADD6-F7D164247E52}
|
||||
DEFINE_GUID(CLSID_HTTPStream,
|
||||
0xbd4fb4be, 0x809d, 0x487b, 0xad, 0xd6, 0xf7, 0xd1, 0x64, 0x24, 0x7e, 0x52);
|
||||
|
||||
// {90C7D10E-CE9A-479b-A238-1A0F2396DE43}
|
||||
DEFINE_GUID(CLSID_MemAlloc,
|
||||
0x90c7d10e, 0xce9a, 0x479b, 0xa2, 0x38, 0x1a, 0xf, 0x23, 0x96, 0xde, 0x43);
|
||||
|
||||
DEFINE_GUID(MEDIASUBTYPE_WVC1,
|
||||
0x31435657, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71);
|
||||
|
||||
DEFINE_GUID(MEDIASUBTYPE_WMV3,
|
||||
0x33564d57, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71);
|
||||
|
||||
// FIXME: move somewhere else?
|
||||
DEFINE_GUID(HAALI_MPEG_PARSER,
|
||||
0xB841F346, 0x4835, 0x4de8, 0xAA, 0x5E, 0x2E, 0x7C, 0xD2, 0xD4, 0xC4, 0x35);
|
||||
|
||||
DEFINE_GUID(HAALI_OGG_PARSER,
|
||||
0xDB43B405, 0x43AA, 0x4F01, 0x82, 0xD8, 0xD8, 0x4D, 0x47, 0xE6, 0x01, 0x9C);
|
||||
|
||||
//DB43B405-43AA-4F01-82D8-D84D47E6019C
|
||||
|
||||
typedef struct tagVORBISFORMAT2
|
||||
{
|
||||
DWORD Channels;
|
||||
DWORD SamplesPerSec;
|
||||
DWORD BitsPerSample;
|
||||
DWORD HeaderSize[3]; // 0: Identification, 1: Comment, 2: Setup
|
||||
} VORBISFORMAT2, *PVORBISFORMAT2, FAR *LPVORBISFORMAT2;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
DWORD dwOffset;
|
||||
CHAR IsoLang[4]; // three letter lang code + terminating zero
|
||||
WCHAR TrackName[256]; // 256 bytes ought to be enough for everyone :)
|
||||
} SUBTITLEINFO;
|
||||
#pragma pack(pop)
|
||||
|
||||
#endif
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
extern "C" {
|
||||
#include <libavutil/sha1.h>
|
||||
#include <zlib.h>
|
||||
}
|
||||
|
||||
#define INDEXID 0x53920873
|
||||
|
@ -51,6 +52,7 @@ struct TrackHeader {
|
|||
uint32_t Frames;
|
||||
int64_t Num;
|
||||
int64_t Den;
|
||||
uint32_t UseDTS;
|
||||
};
|
||||
|
||||
SharedVideoContext::SharedVideoContext(bool FreeCodecContext) {
|
||||
|
@ -155,8 +157,11 @@ int FFMS_Track::ClosestFrameFromPTS(int64_t PTS) {
|
|||
int FFMS_Track::FindClosestVideoKeyFrame(int Frame) {
|
||||
Frame = FFMIN(FFMAX(Frame, 0), static_cast<int>(size()) - 1);
|
||||
for (int i = Frame; i > 0; i--)
|
||||
if (at(i).KeyFrame)
|
||||
return i;
|
||||
if (at(i).KeyFrame) {
|
||||
for (int j = i; j >= 0; j--)
|
||||
if (at(at(j).OriginalPos).KeyFrame)
|
||||
return j;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -174,12 +179,14 @@ FFMS_Track::FFMS_Track() {
|
|||
this->TT = FFMS_TYPE_UNKNOWN;
|
||||
this->TB.Num = 0;
|
||||
this->TB.Den = 0;
|
||||
this->UseDTS = false;
|
||||
}
|
||||
|
||||
FFMS_Track::FFMS_Track(int64_t Num, int64_t Den, FFMS_TrackType TT) {
|
||||
FFMS_Track::FFMS_Track(int64_t Num, int64_t Den, FFMS_TrackType TT, bool UseDTS) {
|
||||
this->TT = TT;
|
||||
this->TB.Num = Num;
|
||||
this->TB.Den = Den;
|
||||
this->UseDTS = UseDTS;
|
||||
}
|
||||
|
||||
void FFMS_Index::CalculateFileSignature(const char *Filename, int64_t *Filesize, uint8_t Digest[20]) {
|
||||
|
@ -264,6 +271,31 @@ bool FFMS_Index::CompareFileSignature(const char *Filename) {
|
|||
return (CFilesize == Filesize && !memcmp(CDigest, Digest, sizeof(Digest)));
|
||||
}
|
||||
|
||||
#define CHUNK 65536
|
||||
|
||||
static unsigned int z_def(ffms_fstream *IndexStream, z_stream *stream, void *in, size_t in_sz, int finish) {
|
||||
unsigned int total = 0, have;
|
||||
int ret;
|
||||
char out[CHUNK];
|
||||
|
||||
if (!finish && (in_sz == 0 || in == NULL)) return 0;
|
||||
|
||||
stream->next_in = (Bytef*) in;
|
||||
stream->avail_in = in_sz;
|
||||
do {
|
||||
do {
|
||||
stream->avail_out = CHUNK;
|
||||
stream->next_out = (Bytef*) out;
|
||||
ret = deflate(stream, finish ? Z_FINISH : Z_NO_FLUSH);
|
||||
have = CHUNK - stream->avail_out;
|
||||
if (have) IndexStream->write(out, have);
|
||||
total += have;
|
||||
} while (stream->avail_out == 0);
|
||||
} while (finish && ret != Z_STREAM_END);
|
||||
if (finish) deflateEnd(stream);
|
||||
return total;
|
||||
}
|
||||
|
||||
void FFMS_Index::WriteIndex(const char *IndexFile) {
|
||||
ffms_fstream IndexStream(IndexFile, std::ios::out | std::ios::binary | std::ios::trunc);
|
||||
|
||||
|
@ -273,6 +305,12 @@ void FFMS_Index::WriteIndex(const char *IndexFile) {
|
|||
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
|
||||
}
|
||||
|
||||
z_stream stream;
|
||||
memset(&stream, 0, sizeof(z_stream));
|
||||
if (deflateInit(&stream, 9) != Z_OK) {
|
||||
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, "Failed to initialize zlib");
|
||||
}
|
||||
|
||||
// Write the index file header
|
||||
IndexHeader IH;
|
||||
IH.Id = INDEXID;
|
||||
|
@ -287,18 +325,72 @@ void FFMS_Index::WriteIndex(const char *IndexFile) {
|
|||
IH.FileSize = Filesize;
|
||||
memcpy(IH.FileSignature, Digest, sizeof(Digest));
|
||||
|
||||
IndexStream.write(reinterpret_cast<char *>(&IH), sizeof(IH));
|
||||
z_def(&IndexStream, &stream, &IH, sizeof(IndexHeader), 0);
|
||||
|
||||
for (unsigned int i = 0; i < IH.Tracks; i++) {
|
||||
FFMS_Track &ctrack = at(i);
|
||||
TrackHeader TH;
|
||||
TH.TT = at(i).TT;
|
||||
TH.Frames = at(i).size();
|
||||
TH.Num = at(i).TB.Num;;
|
||||
TH.Den = at(i).TB.Den;
|
||||
TH.TT = ctrack.TT;
|
||||
TH.Frames = ctrack.size();
|
||||
TH.Num = ctrack.TB.Num;;
|
||||
TH.Den = ctrack.TB.Den;
|
||||
TH.UseDTS = ctrack.UseDTS;
|
||||
|
||||
IndexStream.write(reinterpret_cast<char *>(&TH), sizeof(TH));
|
||||
IndexStream.write(reinterpret_cast<char *>(FFMS_GET_VECTOR_PTR(at(i))), TH.Frames * sizeof(TFrameInfo));
|
||||
FFMS_Track temptrack;
|
||||
temptrack.resize(TH.Frames);
|
||||
|
||||
if (TH.Frames)
|
||||
temptrack[0] = ctrack[0];
|
||||
|
||||
for (size_t j = 1; j < ctrack.size(); j++) {
|
||||
temptrack[j] = ctrack[j];
|
||||
temptrack[j].FilePos = ctrack[j].FilePos - ctrack[j - 1].FilePos;
|
||||
temptrack[j].OriginalPos = ctrack[j].OriginalPos - ctrack[j - 1].OriginalPos;
|
||||
temptrack[j].PTS = ctrack[j].PTS - ctrack[j - 1].PTS;
|
||||
temptrack[j].SampleStart = ctrack[j].SampleStart - ctrack[j - 1].SampleStart;
|
||||
}
|
||||
|
||||
z_def(&IndexStream, &stream, &TH, sizeof(TrackHeader), 0);
|
||||
if (TH.Frames)
|
||||
z_def(&IndexStream, &stream, FFMS_GET_VECTOR_PTR(temptrack), TH.Frames * sizeof(TFrameInfo), 0);
|
||||
}
|
||||
z_def(&IndexStream, &stream, NULL, 0, 1);
|
||||
}
|
||||
|
||||
static unsigned int z_inf(ffms_fstream *Index, z_stream *stream, void *in, size_t in_sz, void *out, size_t out_sz) {
|
||||
int ret;
|
||||
|
||||
if (out_sz == 0 || out == 0) return 0;
|
||||
stream->next_out = (Bytef*) out;
|
||||
stream->avail_out = out_sz;
|
||||
|
||||
do {
|
||||
if (stream->avail_in) memmove(in, stream->next_in, stream->avail_in);
|
||||
Index->read(((char*)in) + stream->avail_in, in_sz - stream->avail_in);
|
||||
stream->next_in = (Bytef*) in;
|
||||
stream->avail_in += Index->gcount();
|
||||
|
||||
ret = inflate(stream, Z_SYNC_FLUSH);
|
||||
switch (ret) {
|
||||
case Z_NEED_DICT:
|
||||
inflateEnd(stream);
|
||||
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, "Failed to read data: Dictionary error.");
|
||||
break;
|
||||
case Z_DATA_ERROR:
|
||||
inflateEnd(stream);
|
||||
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, "Failed to read data: Data error.");
|
||||
break;
|
||||
case Z_MEM_ERROR:
|
||||
inflateEnd(stream);
|
||||
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, "Failed to read data: Memory error.");
|
||||
break;
|
||||
case Z_STREAM_END:
|
||||
inflateEnd(stream);
|
||||
return out_sz - stream->avail_out;
|
||||
}
|
||||
|
||||
} while (stream->avail_out);
|
||||
return out_sz;
|
||||
}
|
||||
|
||||
void FFMS_Index::ReadIndex(const char *IndexFile) {
|
||||
|
@ -310,9 +402,16 @@ void FFMS_Index::ReadIndex(const char *IndexFile) {
|
|||
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
|
||||
}
|
||||
|
||||
z_stream stream;
|
||||
memset(&stream, 0, sizeof(z_stream));
|
||||
unsigned char in[CHUNK];
|
||||
if (inflateInit(&stream) != Z_OK) {
|
||||
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, "Failed to initialize zlib");
|
||||
}
|
||||
|
||||
// Read the index file header
|
||||
IndexHeader IH;
|
||||
Index.read(reinterpret_cast<char *>(&IH), sizeof(IH));
|
||||
z_inf(&Index, &stream, &in, CHUNK, &IH, sizeof(IndexHeader));
|
||||
if (IH.Id != INDEXID) {
|
||||
std::ostringstream buf;
|
||||
buf << "'" << IndexFile << "' is not a valid index file";
|
||||
|
@ -344,12 +443,20 @@ void FFMS_Index::ReadIndex(const char *IndexFile) {
|
|||
try {
|
||||
for (unsigned int i = 0; i < IH.Tracks; i++) {
|
||||
TrackHeader TH;
|
||||
Index.read(reinterpret_cast<char *>(&TH), sizeof(TH));
|
||||
push_back(FFMS_Track(TH.Num, TH.Den, static_cast<FFMS_TrackType>(TH.TT)));
|
||||
z_inf(&Index, &stream, &in, CHUNK, &TH, sizeof(TrackHeader));
|
||||
push_back(FFMS_Track(TH.Num, TH.Den, static_cast<FFMS_TrackType>(TH.TT), TH.UseDTS != 0));
|
||||
FFMS_Track &ctrack = at(i);
|
||||
|
||||
if (TH.Frames) {
|
||||
at(i).resize(TH.Frames);
|
||||
Index.read(reinterpret_cast<char *>(FFMS_GET_VECTOR_PTR(at(i))), TH.Frames * sizeof(TFrameInfo));
|
||||
ctrack.resize(TH.Frames);
|
||||
z_inf(&Index, &stream, &in, CHUNK, FFMS_GET_VECTOR_PTR(ctrack), TH.Frames * sizeof(TFrameInfo));
|
||||
}
|
||||
|
||||
for (size_t j = 1; j < ctrack.size(); j++) {
|
||||
ctrack[j].FilePos = ctrack[j].FilePos + ctrack[j - 1].FilePos;
|
||||
ctrack[j].OriginalPos = ctrack[j].OriginalPos + ctrack[j - 1].OriginalPos;
|
||||
ctrack[j].PTS = ctrack[j].PTS + ctrack[j - 1].PTS;
|
||||
ctrack[j].SampleStart = ctrack[j].SampleStart + ctrack[j - 1].SampleStart;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -361,7 +468,7 @@ void FFMS_Index::ReadIndex(const char *IndexFile) {
|
|||
}
|
||||
|
||||
FFMS_Index::FFMS_Index() {
|
||||
// this comment documents nothing
|
||||
|
||||
}
|
||||
|
||||
FFMS_Index::FFMS_Index(int64_t Filesize, uint8_t Digest[20]) {
|
||||
|
|
|
@ -84,6 +84,7 @@ class FFMS_Track : public std::vector<TFrameInfo> {
|
|||
public:
|
||||
FFMS_TrackType TT;
|
||||
FFMS_TrackTimeBase TB;
|
||||
bool UseDTS;
|
||||
|
||||
int FindClosestVideoKeyFrame(int Frame);
|
||||
int FindClosestAudioKeyFrame(int64_t Sample);
|
||||
|
@ -92,7 +93,7 @@ public:
|
|||
void WriteTimecodes(const char *TimecodeFile);
|
||||
|
||||
FFMS_Track();
|
||||
FFMS_Track(int64_t Num, int64_t Den, FFMS_TrackType TT);
|
||||
FFMS_Track(int64_t Num, int64_t Den, FFMS_TrackType TT, bool UseDTS = false);
|
||||
};
|
||||
|
||||
class FFMS_Index : public std::vector<FFMS_Track> {
|
||||
|
|
|
@ -152,10 +152,10 @@ void FFLAVFAudio::GetAudio(void *Buf, int64_t Start, int64_t Count) {
|
|||
while (av_read_frame(FormatContext, &Packet) >= 0) {
|
||||
if (Packet.stream_index == AudioTrack) {
|
||||
if (LastPTS < 0) {
|
||||
LastPTS = Packet.dts;
|
||||
} else if (LastPTS != Packet.dts) {
|
||||
LastPTS = Packet.pts;
|
||||
} else if (LastPTS != Packet.pts) {
|
||||
for (size_t i = 0; i < Frames.size(); i++)
|
||||
if (Frames[i].PTS == Packet.dts) {
|
||||
if (Frames[i].PTS == Packet.pts) {
|
||||
// The current match was consumed
|
||||
CurrentAudioBlock = i + 1;
|
||||
break;
|
||||
|
|
|
@ -88,9 +88,13 @@ FFMS_Index *FFLAVFIndexer::DoIndexing() {
|
|||
AVPacket Packet, TempPacket;
|
||||
InitNullPacket(Packet);
|
||||
InitNullPacket(TempPacket);
|
||||
std::vector<int64_t> LastValidTS;
|
||||
LastValidTS.resize(FormatContext->nb_streams, ffms_av_nopts_value);
|
||||
|
||||
while (av_read_frame(FormatContext, &Packet) >= 0) {
|
||||
// Update progress
|
||||
if (IC) {
|
||||
// FormatContext->pb can apparently be NULL when opening images.
|
||||
if (IC && FormatContext->pb) {
|
||||
if ((*IC)(FormatContext->pb->pos, FormatContext->file_size, ICPrivate))
|
||||
throw FFMS_Exception(FFMS_ERROR_CANCELLED, FFMS_ERROR_USER,
|
||||
"Cancelled by user");
|
||||
|
@ -102,12 +106,24 @@ FFMS_Index *FFLAVFIndexer::DoIndexing() {
|
|||
int OBSize;
|
||||
int RepeatPict = -1;
|
||||
|
||||
// Duplicated code
|
||||
if (!(*TrackIndices)[Packet.stream_index].UseDTS && Packet.pts != ffms_av_nopts_value)
|
||||
LastValidTS[Packet.stream_index] = Packet.pts;
|
||||
if (LastValidTS[Packet.stream_index] == ffms_av_nopts_value)
|
||||
(*TrackIndices)[Packet.stream_index].UseDTS = true;
|
||||
if ((*TrackIndices)[Packet.stream_index].UseDTS && Packet.dts != ffms_av_nopts_value)
|
||||
LastValidTS[Packet.stream_index] = Packet.dts;
|
||||
if (LastValidTS[Packet.stream_index] == ffms_av_nopts_value)
|
||||
throw FFMS_Exception(FFMS_ERROR_INDEXING, FFMS_ERROR_PARSER,
|
||||
"Invalid initial pts and dts");
|
||||
//
|
||||
|
||||
if (VideoContexts[Packet.stream_index].Parser) {
|
||||
av_parser_parse2(VideoContexts[Packet.stream_index].Parser, VideoContexts[Packet.stream_index].CodecContext, &OB, &OBSize, Packet.data, Packet.size, Packet.pts, Packet.dts, Packet.pos);
|
||||
RepeatPict = VideoContexts[Packet.stream_index].Parser->repeat_pict;
|
||||
}
|
||||
|
||||
(*TrackIndices)[Packet.stream_index].push_back(TFrameInfo::VideoFrameInfo(Packet.dts, RepeatPict, (Packet.flags & AV_PKT_FLAG_KEY) ? 1 : 0));
|
||||
(*TrackIndices)[Packet.stream_index].push_back(TFrameInfo::VideoFrameInfo(LastValidTS[Packet.stream_index], RepeatPict, (Packet.flags & AV_PKT_FLAG_KEY) ? 1 : 0));
|
||||
} else if (FormatContext->streams[Packet.stream_index]->codec->codec_type == CODEC_TYPE_AUDIO && (IndexMask & (1 << Packet.stream_index))) {
|
||||
int64_t StartSample = AudioContexts[Packet.stream_index].CurrentSample;
|
||||
AVCodecContext *AudioCodecContext = FormatContext->streams[Packet.stream_index]->codec;
|
||||
|
@ -115,6 +131,18 @@ FFMS_Index *FFLAVFIndexer::DoIndexing() {
|
|||
TempPacket.size = Packet.size;
|
||||
TempPacket.flags = Packet.flags;
|
||||
|
||||
// Duplicated code
|
||||
if (!(*TrackIndices)[Packet.stream_index].UseDTS && Packet.pts != ffms_av_nopts_value)
|
||||
LastValidTS[Packet.stream_index] = Packet.pts;
|
||||
if (LastValidTS[Packet.stream_index] == ffms_av_nopts_value)
|
||||
(*TrackIndices)[Packet.stream_index].UseDTS = true;
|
||||
if ((*TrackIndices)[Packet.stream_index].UseDTS && Packet.dts != ffms_av_nopts_value)
|
||||
LastValidTS[Packet.stream_index] = Packet.dts;
|
||||
if (LastValidTS[Packet.stream_index] == ffms_av_nopts_value)
|
||||
throw FFMS_Exception(FFMS_ERROR_INDEXING, FFMS_ERROR_PARSER,
|
||||
"Invalid initial pts and dts");
|
||||
//
|
||||
|
||||
while (TempPacket.size > 0) {
|
||||
int dbsize = AVCODEC_MAX_AUDIO_FRAME_SIZE*10;
|
||||
int Ret = avcodec_decode_audio3(AudioCodecContext, &DecodingBuffer[0], &dbsize, &TempPacket);
|
||||
|
@ -146,7 +174,7 @@ FFMS_Index *FFLAVFIndexer::DoIndexing() {
|
|||
WriteAudio(AudioContexts[Packet.stream_index], TrackIndices.get(), Packet.stream_index, dbsize);
|
||||
}
|
||||
|
||||
(*TrackIndices)[Packet.stream_index].push_back(TFrameInfo::AudioFrameInfo(Packet.dts, StartSample,
|
||||
(*TrackIndices)[Packet.stream_index].push_back(TFrameInfo::AudioFrameInfo(LastValidTS[Packet.stream_index], StartSample,
|
||||
static_cast<unsigned int>(AudioContexts[Packet.stream_index].CurrentSample - StartSample), (Packet.flags & AV_PKT_FLAG_KEY) ? 1 : 0));
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,8 @@ FFLAVFVideo::FFLAVFVideo(const char *SourceFile, int Track, FFMS_Index *Index,
|
|||
"Video track is unseekable");
|
||||
|
||||
CodecContext = FormatContext->streams[VideoTrack]->codec;
|
||||
CodecContext->thread_count = Threads;
|
||||
if (avcodec_thread_init(CodecContext, Threads))
|
||||
CodecContext->thread_count = 1;
|
||||
|
||||
Codec = avcodec_find_decoder(CodecContext->codec_id);
|
||||
if (Codec == NULL)
|
||||
|
@ -96,10 +97,19 @@ FFLAVFVideo::FFLAVFVideo(const char *SourceFile, int Track, FFMS_Index *Index,
|
|||
}
|
||||
|
||||
// Adjust framerate to match the duration of the first frame
|
||||
if (Frames.size() >= 2) {
|
||||
unsigned int PTSDiff = (unsigned int)FFMAX(Frames[1].PTS - Frames[0].PTS, 1);
|
||||
for (size_t i = 1; i < Frames.size(); i++) {
|
||||
if (Frames[i].PTS != Frames[0].PTS) {
|
||||
unsigned int PTSDiff = (unsigned int)(Frames[i].PTS - Frames[0].PTS);
|
||||
VP.FPSDenominator *= PTSDiff;
|
||||
VP.FPSDenominator /= i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// attempt to correct framerate to the proper NTSC fraction, if applicable
|
||||
CorrectNTSCRationalFramerate(&VP.FPSNumerator, &VP.FPSDenominator);
|
||||
// correct the timebase, if necessary
|
||||
CorrectTimebase(&VP, &Frames.TB);
|
||||
|
||||
// Cannot "output" to PPFrame without doing all other initialization
|
||||
// This is the additional mess required for seekmode=-1 to work in a reasonable way
|
||||
|
@ -116,24 +126,33 @@ void FFLAVFVideo::DecodeNextFrame(int64_t *AStartTime) {
|
|||
int FrameFinished = 0;
|
||||
*AStartTime = -1;
|
||||
|
||||
if (InitialDecode == -1) {
|
||||
if (DelayCounter > CodecContext->has_b_frames) {
|
||||
DelayCounter--;
|
||||
goto Done;
|
||||
} else {
|
||||
InitialDecode = 0;
|
||||
}
|
||||
}
|
||||
|
||||
while (av_read_frame(FormatContext, &Packet) >= 0) {
|
||||
if (Packet.stream_index == VideoTrack) {
|
||||
if (*AStartTime < 0)
|
||||
if (*AStartTime < 0) {
|
||||
if (Frames.UseDTS)
|
||||
*AStartTime = Packet.dts;
|
||||
else
|
||||
*AStartTime = Packet.pts;
|
||||
}
|
||||
|
||||
avcodec_decode_video2(CodecContext, DecodeFrame, &FrameFinished, &Packet);
|
||||
|
||||
if (CodecContext->codec_id == CODEC_ID_MPEG4) {
|
||||
if (IsPackedFrame(Packet)) {
|
||||
MPEG4Counter++;
|
||||
} else if (IsNVOP(Packet) && MPEG4Counter && FrameFinished) {
|
||||
MPEG4Counter--;
|
||||
} else if (IsNVOP(Packet) && !MPEG4Counter && !FrameFinished) {
|
||||
if (!FrameFinished)
|
||||
DelayCounter++;
|
||||
if (DelayCounter > CodecContext->has_b_frames && !InitialDecode) {
|
||||
av_free_packet(&Packet);
|
||||
goto Done;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
av_free_packet(&Packet);
|
||||
|
||||
|
@ -152,7 +171,8 @@ void FFLAVFVideo::DecodeNextFrame(int64_t *AStartTime) {
|
|||
goto Error;
|
||||
|
||||
Error:
|
||||
Done:;
|
||||
Done:
|
||||
if (InitialDecode == 1) InitialDecode = -1;
|
||||
}
|
||||
|
||||
FFMS_Frame *FFLAVFVideo::GetFrame(int n) {
|
||||
|
@ -173,6 +193,8 @@ FFMS_Frame *FFLAVFVideo::GetFrame(int n) {
|
|||
av_seek_frame(FormatContext, VideoTrack, Frames[0].PTS, AVSEEK_FLAG_BACKWARD);
|
||||
avcodec_flush_buffers(CodecContext);
|
||||
CurrentFrame = 0;
|
||||
DelayCounter = 0;
|
||||
InitialDecode = 1;
|
||||
}
|
||||
} else {
|
||||
// 10 frames is used as a margin to prevent excessive seeking since the predicted best keyframe isn't always selected by avformat
|
||||
|
@ -183,6 +205,8 @@ ReSeek:
|
|||
AVSEEK_FLAG_BACKWARD);
|
||||
avcodec_flush_buffers(CodecContext);
|
||||
HasSeeked = true;
|
||||
DelayCounter = 0;
|
||||
InitialDecode = 1;
|
||||
}
|
||||
}
|
||||
} else if (n < CurrentFrame) {
|
||||
|
@ -192,11 +216,14 @@ ReSeek:
|
|||
|
||||
do {
|
||||
int64_t StartTime;
|
||||
if (CurrentFrame+CodecContext->has_b_frames >= n)
|
||||
CodecContext->skip_frame = AVDISCARD_DEFAULT;
|
||||
else
|
||||
CodecContext->skip_frame = AVDISCARD_NONREF;
|
||||
DecodeNextFrame(&StartTime);
|
||||
|
||||
if (HasSeeked) {
|
||||
HasSeeked = false;
|
||||
MPEG4Counter = 0;
|
||||
|
||||
// Is the seek destination time known? Does it belong to a frame?
|
||||
if (StartTime < 0 || (CurrentFrame = Frames.FrameFromPTS(StartTime)) < 0) {
|
||||
|
|
|
@ -27,6 +27,10 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
@ -34,11 +38,19 @@
|
|||
#include <setjmp.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifdef _MSC_VER
|
||||
// MS names some functions differently
|
||||
#define alloca _alloca
|
||||
#define inline __inline
|
||||
|
||||
#else /* _MSC_VER */
|
||||
// support for building with MinGW on Windows
|
||||
#include <malloc.h>
|
||||
#endif /* _MSC_VER */
|
||||
#include <tchar.h>
|
||||
#endif /* _WIN32 */
|
||||
|
||||
#ifdef HAVE_ALLOCA_H
|
||||
#include <alloca.h>
|
||||
#endif
|
||||
|
||||
#ifndef EVCBUG
|
||||
|
@ -286,7 +298,8 @@ static void myvsnprintf_int(char **pdest,char *de,int width,int zero,
|
|||
static void myvsnprintf(char *dest,unsigned dsize,const char *fmt,va_list ap) {
|
||||
// s,d,x,u,ll
|
||||
char *de = dest + dsize - 1;
|
||||
int state = 0, width, zero, neg, ll;
|
||||
int state, width, zero, neg, ll;
|
||||
state = width = zero = neg = ll = 0;
|
||||
|
||||
if (dsize <= 1) {
|
||||
if (dsize > 0)
|
||||
|
@ -904,16 +917,6 @@ static void readLangCC(MatroskaFile *mf, ulonglong len, char lcc[4]) {
|
|||
#define STRGETA(f,v,len) STRGETF(f,v,len,myalloca)
|
||||
#define STRGETM(f,v,len) STRGETF(f,v,len,f->cache->memalloc)
|
||||
|
||||
static int IsWritingApp(MatroskaFile *mf,const char *str) {
|
||||
const char *cp = mf->Seg.WritingApp;
|
||||
if (!cp)
|
||||
return 0;
|
||||
|
||||
while (*str && *str++==*cp++) ;
|
||||
|
||||
return !*str;
|
||||
}
|
||||
|
||||
static void parseEBML(MatroskaFile *mf,ulonglong toplen) {
|
||||
ulonglong v;
|
||||
char buf[32];
|
||||
|
@ -1212,10 +1215,6 @@ static void parseAudioInfo(MatroskaFile *mf,ulonglong toplen,struct TrackInfo *t
|
|||
break;
|
||||
case 0x6264: // BitDepth
|
||||
v = readUInt(mf,(unsigned)len);
|
||||
#if 0
|
||||
if ((v<1 || v>255) && !IsWritingApp(mf,"AVI-Mux GUI"))
|
||||
errorjmp(mf,"Invalid BitDepth: %d",(int)v);
|
||||
#endif
|
||||
ti->AV.Audio.BitDepth = (unsigned char)v;
|
||||
break;
|
||||
ENDFOR(mf);
|
||||
|
@ -1245,7 +1244,7 @@ static void parseTrackEntry(MatroskaFile *mf,ulonglong toplen) {
|
|||
ulonglong v;
|
||||
char *cp = NULL, *cs = NULL;
|
||||
size_t cplen = 0, cslen = 0, cpadd = 0;
|
||||
unsigned CompScope, num_comp = 0;
|
||||
unsigned CompScope = 0, num_comp = 0;
|
||||
|
||||
if (mf->nTracks >= MAX_TRACKS)
|
||||
errorjmp(mf,"Too many tracks.");
|
||||
|
|
|
@ -76,7 +76,8 @@ FFMatroskaVideo::FFMatroskaVideo(const char *SourceFile, int Track,
|
|||
}
|
||||
|
||||
CodecContext = avcodec_alloc_context();
|
||||
CodecContext->thread_count = Threads;
|
||||
if (avcodec_thread_init(CodecContext, Threads))
|
||||
CodecContext->thread_count = 1;
|
||||
|
||||
Codec = avcodec_find_decoder(MatroskaToFFCodecID(TI->CodecID, TI->CodecPrivate));
|
||||
if (Codec == NULL)
|
||||
|
@ -127,6 +128,11 @@ FFMatroskaVideo::FFMatroskaVideo(const char *SourceFile, int Track,
|
|||
VP.FPSNumerator = 1000000;
|
||||
}
|
||||
|
||||
// attempt to correct framerate to the proper NTSC fraction, if applicable
|
||||
CorrectNTSCRationalFramerate(&VP.FPSNumerator, &VP.FPSDenominator);
|
||||
// correct the timebase, if necessary
|
||||
CorrectTimebase(&VP, &Frames.TB);
|
||||
|
||||
// Output the already decoded frame so it isn't wasted
|
||||
OutputFrame(DecodeFrame);
|
||||
|
||||
|
@ -148,6 +154,15 @@ void FFMatroskaVideo::DecodeNextFrame() {
|
|||
|
||||
unsigned int FrameSize;
|
||||
|
||||
if (InitialDecode == -1) {
|
||||
if (DelayCounter > CodecContext->has_b_frames) {
|
||||
DelayCounter--;
|
||||
goto Done;
|
||||
} else {
|
||||
InitialDecode = 0;
|
||||
}
|
||||
}
|
||||
|
||||
while (PacketNumber < Frames.size()) {
|
||||
// The additional indirection is because the packets are stored in
|
||||
// presentation order and not decoding order, this is unnoticable
|
||||
|
@ -167,15 +182,10 @@ void FFMatroskaVideo::DecodeNextFrame() {
|
|||
|
||||
avcodec_decode_video2(CodecContext, DecodeFrame, &FrameFinished, &Packet);
|
||||
|
||||
if (CodecContext->codec_id == CODEC_ID_MPEG4) {
|
||||
if (IsPackedFrame(Packet)) {
|
||||
MPEG4Counter++;
|
||||
} else if (IsNVOP(Packet) && MPEG4Counter && FrameFinished) {
|
||||
MPEG4Counter--;
|
||||
} else if (IsNVOP(Packet) && !MPEG4Counter && !FrameFinished) {
|
||||
if (!FrameFinished)
|
||||
DelayCounter++;
|
||||
if (DelayCounter > CodecContext->has_b_frames && !InitialDecode)
|
||||
goto Done;
|
||||
}
|
||||
}
|
||||
|
||||
if (FrameFinished)
|
||||
goto Done;
|
||||
|
@ -192,7 +202,8 @@ void FFMatroskaVideo::DecodeNextFrame() {
|
|||
goto Error;
|
||||
|
||||
Error:
|
||||
Done:;
|
||||
Done:
|
||||
if (InitialDecode == 1) InitialDecode = -1;
|
||||
}
|
||||
|
||||
FFMS_Frame *FFMatroskaVideo::GetFrame(int n) {
|
||||
|
@ -203,13 +214,18 @@ FFMS_Frame *FFMatroskaVideo::GetFrame(int n) {
|
|||
|
||||
int ClosestKF = Frames.FindClosestVideoKeyFrame(n);
|
||||
if (CurrentFrame > n || ClosestKF > CurrentFrame + 10) {
|
||||
MPEG4Counter = 0;
|
||||
DelayCounter = 0;
|
||||
InitialDecode = 1;
|
||||
PacketNumber = ClosestKF;
|
||||
CurrentFrame = ClosestKF;
|
||||
avcodec_flush_buffers(CodecContext);
|
||||
}
|
||||
|
||||
do {
|
||||
if (CurrentFrame+CodecContext->has_b_frames >= n)
|
||||
CodecContext->skip_frame = AVDISCARD_DEFAULT;
|
||||
else
|
||||
CodecContext->skip_frame = AVDISCARD_NONREF;
|
||||
DecodeNextFrame();
|
||||
CurrentFrame++;
|
||||
} while (CurrentFrame <= n);
|
||||
|
|
|
@ -20,13 +20,19 @@
|
|||
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "utils.h"
|
||||
#include "indexing.h"
|
||||
|
||||
#ifdef FFMS_USE_UTF8_PATHS
|
||||
#ifdef _WIN32
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# include <windows.h>
|
||||
#ifdef FFMS_USE_UTF8_PATHS
|
||||
# include <io.h>
|
||||
# include <fcntl.h>
|
||||
extern "C" {
|
||||
# include "libavutil/avstring.h"
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
|
@ -202,20 +208,6 @@ void InitNullPacket(AVPacket &pkt) {
|
|||
pkt.size = 0;
|
||||
}
|
||||
|
||||
bool IsPackedFrame(AVPacket &pkt) {
|
||||
for (int i = 0; i < pkt.size - 5; i++)
|
||||
if (pkt.data[i] == 0x00 && pkt.data[i + 1] == 0x00 && pkt.data[i + 2] == 0x01 && pkt.data[i + 3] == 0xB6 && (pkt.data[i + 4] & 0x40))
|
||||
for (i = i + 5; i < pkt.size - 5; i++)
|
||||
if (pkt.data[i] == 0x00 && pkt.data[i + 1] == 0x00 && pkt.data[i + 2] == 0x01 && pkt.data[i + 3] == 0xB6 && (pkt.data[i + 4] & 0xC0) == 0x80)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsNVOP(AVPacket &pkt) {
|
||||
static const uint8_t MPEG4NVOP[] = { 0x00, 0x00, 0x01, 0xB6 };
|
||||
return (pkt.size >= 4 && pkt.size <= 8) && !memcmp(pkt.data, MPEG4NVOP, 4);
|
||||
}
|
||||
|
||||
void FillAP(FFMS_AudioProperties &AP, AVCodecContext *CTX, FFMS_Track &Frames) {
|
||||
AP.SampleFormat = static_cast<FFMS_SampleFormat>(CTX->sample_fmt);
|
||||
AP.BitsPerSample = av_get_bits_per_sample_format(CTX->sample_fmt);
|
||||
|
@ -281,6 +273,8 @@ CodecID MatroskaToFFCodecID(char *Codec, void *CodecPrivate, unsigned int FourCC
|
|||
case 32: CID = CODEC_ID_PCM_S32BE; break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return CID;
|
||||
|
@ -367,24 +361,52 @@ void InitializeCodecContextFromHaaliInfo(CComQIPtr<IPropertyBag> pBag, AVCodecCo
|
|||
|
||||
#endif
|
||||
|
||||
|
||||
// All this filename chikanery that follows is supposed to make sure both local
|
||||
// codepage (used by avisynth etc) and UTF8 (potentially used by API users) strings
|
||||
// work correctly on Win32.
|
||||
// It's a really ugly hack, and I blame Microsoft for it.
|
||||
#ifdef _WIN32
|
||||
static wchar_t *dup_char_to_wchar(const char *s, unsigned int cp) {
|
||||
wchar_t *w;
|
||||
int l;
|
||||
if (!(l = MultiByteToWideChar(cp, MB_ERR_INVALID_CHARS, s, -1, NULL, 0)))
|
||||
return NULL;
|
||||
if (!(w = (wchar_t *)malloc(l * sizeof(wchar_t))))
|
||||
return NULL;
|
||||
if (MultiByteToWideChar(cp, MB_ERR_INVALID_CHARS, s, -1 , w, l) <= 0) {
|
||||
free(w);
|
||||
w = NULL;
|
||||
}
|
||||
return w;
|
||||
}
|
||||
#endif
|
||||
|
||||
FILE *ffms_fopen(const char *filename, const char *mode) {
|
||||
#ifdef _WIN32
|
||||
int codepage = CP_ACP;
|
||||
#ifdef FFMS_USE_UTF8_PATHS
|
||||
// Hack: support utf8-in-char* filenames on windows
|
||||
wchar_t filename_wide[MAX_PATH*2];
|
||||
// 64 characters of mode string ought to be more than enough for everyone
|
||||
wchar_t mode_wide[64];
|
||||
if ((MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, filename, -1, filename_wide, MAX_PATH) > 0)
|
||||
&& (MultiByteToWideChar(CP_ACP, NULL, mode, -1, mode_wide, 60) > 0))
|
||||
return _wfopen(filename_wide, mode_wide);
|
||||
codepage = CP_UTF8;
|
||||
#endif
|
||||
FILE *ret;
|
||||
wchar_t *filename_wide = dup_char_to_wchar(filename, codepage);
|
||||
wchar_t *mode_wide = dup_char_to_wchar(mode, codepage);
|
||||
if (filename_wide && mode_wide)
|
||||
ret = _wfopen(filename_wide, mode_wide);
|
||||
else
|
||||
return fopen(filename, mode);
|
||||
ret = fopen(filename, mode);
|
||||
|
||||
free(filename_wide);
|
||||
free(mode_wide);
|
||||
|
||||
return ret;
|
||||
#else
|
||||
return fopen(filename, mode);
|
||||
#endif
|
||||
#endif /* _WIN32 */
|
||||
}
|
||||
|
||||
size_t ffms_mbstowcs (wchar_t *wcstr, const char *mbstr, size_t max) {
|
||||
#ifdef FFMS_USE_UTF8_PATHS
|
||||
#if defined(_WIN32) && defined(FFMS_USE_UTF8_PATHS)
|
||||
// try utf8 first
|
||||
int len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mbstr, -1, NULL, 0);
|
||||
if (len > 0) {
|
||||
|
@ -404,22 +426,85 @@ size_t ffms_mbstowcs (wchar_t *wcstr, const char *mbstr, size_t max) {
|
|||
|
||||
// ffms_fstream stuff
|
||||
void ffms_fstream::open(const char *filename, std::ios_base::openmode mode) {
|
||||
#ifdef FFMS_USE_UTF8_PATHS
|
||||
// Hack: support utf8-in-char* filenames on windows
|
||||
wchar_t filename_wide[MAX_PATH*2];
|
||||
if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, filename, -1, filename_wide, MAX_PATH) > 0)
|
||||
// Unlike MSVC, mingw's iostream library doesn't have an fstream overload
|
||||
// that takes a wchar_t* filename, which means you can't open unicode
|
||||
// filenames with it on Windows. gg.
|
||||
#if defined(_WIN32) && !defined(__MINGW32__)
|
||||
unsigned int codepage = CP_ACP;
|
||||
#if defined(FFMS_USE_UTF8_PATHS)
|
||||
codepage = CP_UTF8;
|
||||
#endif /* defined(FFMS_USE_UTF8_PATHS) */
|
||||
wchar_t *filename_wide = dup_char_to_wchar(filename, codepage);
|
||||
if (filename_wide)
|
||||
std::fstream::open(filename_wide, mode);
|
||||
else
|
||||
std::fstream::open(filename, mode);
|
||||
#else
|
||||
|
||||
free(filename_wide);
|
||||
#else /* defined(_WIN32) && !defined(__MINGW32__) */
|
||||
std::fstream::open(filename, mode);
|
||||
#endif
|
||||
#endif /* defined(_WIN32) && !defined(__MINGW32__) */
|
||||
}
|
||||
|
||||
ffms_fstream::ffms_fstream(const char *filename, std::ios_base::openmode mode) {
|
||||
open(filename, mode);
|
||||
}
|
||||
|
||||
|
||||
#if defined(_WIN32) && defined(FFMS_USE_UTF8_PATHS)
|
||||
int ffms_wchar_open(const char *fname, int oflags, int pmode) {
|
||||
wchar_t *wfname = dup_char_to_wchar(fname, CP_UTF8);
|
||||
if (wfname) {
|
||||
int ret = _wopen(wfname, oflags, pmode);
|
||||
av_free(wfname);
|
||||
return ret;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int ffms_lavf_file_open(URLContext *h, const char *filename, int flags) {
|
||||
int access;
|
||||
int fd;
|
||||
|
||||
av_strstart(filename, "file:", &filename);
|
||||
|
||||
if (flags & URL_RDWR) {
|
||||
access = _O_CREAT | _O_TRUNC | _O_RDWR;
|
||||
} else if (flags & URL_WRONLY) {
|
||||
access = _O_CREAT | _O_TRUNC | _O_WRONLY;
|
||||
} else {
|
||||
access = _O_RDONLY;
|
||||
}
|
||||
#ifdef _O_BINARY
|
||||
access |= _O_BINARY;
|
||||
#endif
|
||||
fd = ffms_wchar_open(filename, access, 0666);
|
||||
if (fd == -1)
|
||||
return AVERROR(ENOENT);
|
||||
h->priv_data = (void *) (intptr_t) fd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Hijack lavf's file protocol handler's open function and use our own instead.
|
||||
// Hack by nielsm.
|
||||
void ffms_patch_lavf_file_open() {
|
||||
extern URLProtocol *first_protocol;
|
||||
URLProtocol *proto = first_protocol;
|
||||
while (proto != NULL) {
|
||||
if (strcmp("file", proto->name) == 0) {
|
||||
break;
|
||||
}
|
||||
proto = proto->next;
|
||||
}
|
||||
if (proto != NULL) {
|
||||
proto->url_open = &ffms_lavf_file_open;
|
||||
}
|
||||
}
|
||||
#endif /* defined(_WIN32) && defined(FFMS_USE_UTF8_PATHS) */
|
||||
|
||||
// End of filename hackery.
|
||||
|
||||
|
||||
#ifdef HAALISOURCE
|
||||
|
||||
CComPtr<IMMContainer> HaaliOpenFile(const char *SourceFile, enum FFMS_Sources SourceMode) {
|
||||
|
@ -478,3 +563,39 @@ void LAVFOpenFile(const char *SourceFile, AVFormatContext *&FormatContext) {
|
|||
"Couldn't find stream information");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// attempt to correct framerate to the proper NTSC fraction, if applicable
|
||||
// code stolen from Perian
|
||||
void CorrectNTSCRationalFramerate(int *Num, int *Den) {
|
||||
AVRational TempFPS;
|
||||
TempFPS.den = *Num; // not a typo
|
||||
TempFPS.num = *Den; // still not a typo
|
||||
|
||||
av_reduce(&TempFPS.num, &TempFPS.den, TempFPS.num, TempFPS.den, INT_MAX);
|
||||
|
||||
if (TempFPS.num == 1) {
|
||||
*Num = TempFPS.den;
|
||||
*Den = TempFPS.num;
|
||||
}
|
||||
else {
|
||||
double FTimebase = av_q2d(TempFPS);
|
||||
double NearestNTSC = floor(FTimebase * 1001.0 + 0.5) / 1001.0;
|
||||
const double SmallInterval = 1.0/120.0;
|
||||
|
||||
if (fabs(FTimebase - NearestNTSC) < SmallInterval) {
|
||||
*Num = int((1001.0 / FTimebase) + 0.5);
|
||||
*Den = 1001;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// correct the timebase if it is invalid
|
||||
void CorrectTimebase(FFMS_VideoProperties *VP, FFMS_TrackTimeBase *TTimebase) {
|
||||
double Timebase = (double)TTimebase->Num / TTimebase->Den;
|
||||
double FPS = (double)VP->FPSNumerator / VP->FPSDenominator;
|
||||
if ((1000/Timebase) / FPS < 1) {
|
||||
TTimebase->Den = VP->FPSNumerator;
|
||||
TTimebase->Num = (int64_t)VP->FPSDenominator * 1000;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -142,8 +142,6 @@ FFMS_TrackType HaaliTrackTypeToFFTrackType(int TT);
|
|||
void ReadFrame(uint64_t FilePos, unsigned int &FrameSize, CompressedStream *CS, MatroskaReaderContext &Context);
|
||||
bool AudioFMTIsFloat(SampleFormat FMT);
|
||||
void InitNullPacket(AVPacket &pkt);
|
||||
bool IsPackedFrame(AVPacket &pkt);
|
||||
bool IsNVOP(AVPacket &pkt);
|
||||
void FillAP(FFMS_AudioProperties &AP, AVCodecContext *CTX, FFMS_Track &Frames);
|
||||
#ifdef HAALISOURCE
|
||||
unsigned vtSize(VARIANT &vt);
|
||||
|
@ -154,9 +152,14 @@ void InitializeCodecContextFromMatroskaTrackInfo(TrackInfo *TI, AVCodecContext *
|
|||
CodecID MatroskaToFFCodecID(char *Codec, void *CodecPrivate, unsigned int FourCC = 0, unsigned int BitsPerSample = 0);
|
||||
FILE *ffms_fopen(const char *filename, const char *mode);
|
||||
size_t ffms_mbstowcs (wchar_t *wcstr, const char *mbstr, size_t max);
|
||||
#if defined(_WIN32) && defined(FFMS_USE_UTF8_PATHS)
|
||||
void ffms_patch_lavf_file_open();
|
||||
#endif
|
||||
#ifdef HAALISOURCE
|
||||
CComPtr<IMMContainer> HaaliOpenFile(const char *SourceFile, enum FFMS_Sources SourceMode);
|
||||
#endif
|
||||
void LAVFOpenFile(const char *SourceFile, AVFormatContext *&FormatContext);
|
||||
void CorrectNTSCRationalFramerate(int *Num, int *Den);
|
||||
void CorrectTimebase(FFMS_VideoProperties *VP, FFMS_TrackTimeBase *TTimebase);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -102,14 +102,14 @@ FFMS_Frame *FFMS_VideoSource::OutputFrame(AVFrame *Frame) {
|
|||
if (PPMode) {
|
||||
pp_postprocess(const_cast<const uint8_t **>(Frame->data), Frame->linesize, PPFrame.data, PPFrame.linesize, CodecContext->width, CodecContext->height, Frame->qscale_table, Frame->qstride, PPMode, PPContext, Frame->pict_type | (Frame->qscale_type ? PP_PICT_TYPE_QP2 : 0));
|
||||
if (SWS) {
|
||||
sws_scale(SWS, PPFrame.data, PPFrame.linesize, 0, CodecContext->height, SWSFrame.data, SWSFrame.linesize);
|
||||
sws_scale(SWS, const_cast<FFMS_SWS_CONST_PARAM uint8_t **>(PPFrame.data), PPFrame.linesize, 0, CodecContext->height, SWSFrame.data, SWSFrame.linesize);
|
||||
CopyAVPictureFields(SWSFrame, LocalFrame);
|
||||
} else {
|
||||
CopyAVPictureFields(PPFrame, LocalFrame);
|
||||
}
|
||||
} else {
|
||||
if (SWS) {
|
||||
sws_scale(SWS, Frame->data, Frame->linesize, 0, CodecContext->height, SWSFrame.data, SWSFrame.linesize);
|
||||
sws_scale(SWS, const_cast<FFMS_SWS_CONST_PARAM uint8_t **>(Frame->data), Frame->linesize, 0, CodecContext->height, SWSFrame.data, SWSFrame.linesize);
|
||||
CopyAVPictureFields(SWSFrame, LocalFrame);
|
||||
} else {
|
||||
// Special case to avoid ugly casts
|
||||
|
@ -148,7 +148,7 @@ FFMS_VideoSource::FFMS_VideoSource(const char *SourceFile, FFMS_Index *Index, in
|
|||
throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT,
|
||||
"Not a video track");
|
||||
|
||||
if (Index[Track].size() == 0)
|
||||
if (Index->at(Track).size() == 0)
|
||||
throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT,
|
||||
"Video track contains no frames");
|
||||
|
||||
|
@ -162,7 +162,8 @@ FFMS_VideoSource::FFMS_VideoSource(const char *SourceFile, FFMS_Index *Index, in
|
|||
SWS = NULL;
|
||||
LastFrameNum = 0;
|
||||
CurrentFrame = 1;
|
||||
MPEG4Counter = 0;
|
||||
DelayCounter = 0;
|
||||
InitialDecode = 1;
|
||||
CodecContext = NULL;
|
||||
LastFrameHeight = -1;
|
||||
LastFrameWidth = -1;
|
||||
|
|
|
@ -73,7 +73,8 @@ protected:
|
|||
FFMS_Track Frames;
|
||||
int VideoTrack;
|
||||
int CurrentFrame;
|
||||
int MPEG4Counter;
|
||||
int DelayCounter;
|
||||
int InitialDecode;
|
||||
AVCodecContext *CodecContext;
|
||||
|
||||
FFMS_VideoSource(const char *SourceFile, FFMS_Index *Index, int Track);
|
||||
|
|
Loading…
Reference in a new issue