mirror of https://github.com/odrling/Aegisub
parent
c01bb02942
commit
16786725c3
|
@ -2,10 +2,7 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
@ -20,6 +17,7 @@ extern "C" {
|
||||||
#include "MatroskaParser.h"
|
#include "MatroskaParser.h"
|
||||||
#include "avisynth.h"
|
#include "avisynth.h"
|
||||||
#include "stdiostream.c"
|
#include "stdiostream.c"
|
||||||
|
#include "matroskacodecs.c"
|
||||||
|
|
||||||
class FFBase : public IClip {
|
class FFBase : public IClip {
|
||||||
private:
|
private:
|
||||||
|
@ -29,9 +27,64 @@ private:
|
||||||
protected:
|
protected:
|
||||||
VideoInfo VI;
|
VideoInfo VI;
|
||||||
|
|
||||||
|
struct FrameInfo {
|
||||||
|
int64_t DTS;
|
||||||
|
bool KeyFrame;
|
||||||
|
|
||||||
|
FrameInfo(int64_t _DTS, bool _KeyFrame) : DTS(_DTS), KeyFrame(_KeyFrame) {};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<FrameInfo> FrameToDTS;
|
||||||
|
|
||||||
|
int FindClosestKeyFrame(int Frame) {
|
||||||
|
for (unsigned int i = Frame; i > 0; i--)
|
||||||
|
if (FrameToDTS[i].KeyFrame)
|
||||||
|
return i;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int FrameFromDTS(int64_t DTS) {
|
||||||
|
for (unsigned int i = 0; i < FrameToDTS.size(); i++)
|
||||||
|
if (FrameToDTS[i].DTS == DTS)
|
||||||
|
return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ClosestFrameFromDTS(int64_t DTS) {
|
||||||
|
int Frame = 0;
|
||||||
|
int64_t BestDiff = 0xFFFFFFFFFFFFFF;
|
||||||
|
for (unsigned int i = 0; i < FrameToDTS.size(); i++) {
|
||||||
|
int64_t CurrentDiff = FFABS(FrameToDTS[i].DTS - DTS);
|
||||||
|
if (CurrentDiff < BestDiff) {
|
||||||
|
BestDiff = CurrentDiff;
|
||||||
|
Frame = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LoadFrameInfoFromFile(FILE *CacheFile) {
|
||||||
|
if (ftell(CacheFile)) {
|
||||||
|
rewind(CacheFile);
|
||||||
|
|
||||||
|
fscanf(CacheFile, "%d\n", &VI.num_frames);
|
||||||
|
|
||||||
|
for (int i = 0; i < VI.num_frames; i++) {
|
||||||
|
int64_t DTSTemp;
|
||||||
|
int KFTemp;
|
||||||
|
fscanf(CacheFile, "%lld %d\n", &DTSTemp, &KFTemp);
|
||||||
|
FrameToDTS.push_back(FrameInfo(DTSTemp, KFTemp != 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void SetOutputFormat(int CurrentFormat, IScriptEnvironment *Env) {
|
void SetOutputFormat(int CurrentFormat, IScriptEnvironment *Env) {
|
||||||
int Loss;
|
int Loss;
|
||||||
int BestFormat = avcodec_find_best_pix_fmt((1 << PIX_FMT_YUV420P) | (1 << PIX_FMT_YUYV422) | (1 << PIX_FMT_RGB32) | (1 << PIX_FMT_BGR24), CurrentFormat, 1, &Loss);
|
int BestFormat = avcodec_find_best_pix_fmt((1 << PIX_FMT_YUV420P) | (1 << PIX_FMT_YUYV422) | (1 << PIX_FMT_RGB32) | (1 << PIX_FMT_BGR24), CurrentFormat, 1 /* Required to prevent pointless RGB32 => RGB24 conversion */, &Loss);
|
||||||
|
|
||||||
switch (BestFormat) {
|
switch (BestFormat) {
|
||||||
case PIX_FMT_YUV420P: VI.pixel_type = VideoInfo::CS_I420; break;
|
case PIX_FMT_YUV420P: VI.pixel_type = VideoInfo::CS_I420; break;
|
||||||
|
@ -45,7 +98,7 @@ protected:
|
||||||
if (BestFormat != CurrentFormat) {
|
if (BestFormat != CurrentFormat) {
|
||||||
ConvertFromFormat = CurrentFormat;
|
ConvertFromFormat = CurrentFormat;
|
||||||
ConvertToFormat = BestFormat;
|
ConvertToFormat = BestFormat;
|
||||||
SWS = sws_getContext(VI.width, VI.height, ConvertFromFormat, VI.width, VI.height, ConvertToFormat, SWS_LANCZOS, NULL, NULL, NULL);
|
SWS = sws_getContext(VI.width, VI.height, ConvertFromFormat, VI.width, VI.height, ConvertToFormat, SWS_BICUBIC, NULL, NULL, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,6 +129,7 @@ protected:
|
||||||
else
|
else
|
||||||
Env->BitBlt(Dst->GetWritePtr(), Dst->GetPitch(), Frame->data[0], Frame->linesize[0], Dst->GetRowSize(), Dst->GetHeight());
|
Env->BitBlt(Dst->GetWritePtr(), Dst->GetPitch(), Frame->data[0], Frame->linesize[0], Dst->GetRowSize(), Dst->GetHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
return Dst;
|
return Dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,232 +154,18 @@ private:
|
||||||
AVFrame *Frame;
|
AVFrame *Frame;
|
||||||
int CurrentFrame;
|
int CurrentFrame;
|
||||||
|
|
||||||
struct FFMKVFrameInfo {
|
|
||||||
ulonglong DTS;
|
|
||||||
bool KeyFrame;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<FFMKVFrameInfo> FrameToDTS;
|
|
||||||
std::map<ulonglong, int> DTSToFrame;
|
|
||||||
|
|
||||||
StdIoStream ST;
|
StdIoStream ST;
|
||||||
MatroskaFile *MF;
|
MatroskaFile *MF;
|
||||||
|
|
||||||
char ErrorMessage[256];
|
char ErrorMessage[256];
|
||||||
unsigned int BufferSize;
|
unsigned int BufferSize;
|
||||||
void *Buffer;
|
uint8_t *Buffer;
|
||||||
CompressedStream *CS;
|
CompressedStream *CS;
|
||||||
|
|
||||||
int ReadNextFrame(AVFrame *Frame, ulonglong *StartTime, IScriptEnvironment* Env);
|
unsigned int ReadNextFrame(int64_t *FirstStartTime, IScriptEnvironment *Env);
|
||||||
|
int DecodeNextFrame(AVFrame *Frame, int64_t *FirstStartTime, IScriptEnvironment* Env);
|
||||||
CodecID MatroskaToFFCodecID(TrackInfo *TI) {
|
|
||||||
char *Codec = TI->CodecID;
|
|
||||||
// fourcc list from ffdshow
|
|
||||||
if (!strcmp(Codec, "V_MS/VFW/FOURCC")) {
|
|
||||||
switch (((BITMAPINFOHEADER *)TI->CodecPrivate)->biCompression) {
|
|
||||||
case MAKEFOURCC('F', 'F', 'D', 'S'):
|
|
||||||
case MAKEFOURCC('F', 'V', 'F', 'W'):
|
|
||||||
case MAKEFOURCC('X', 'V', 'I', 'D'):
|
|
||||||
case MAKEFOURCC('D', 'I', 'V', 'X'):
|
|
||||||
case MAKEFOURCC('D', 'X', '5', '0'):
|
|
||||||
case MAKEFOURCC('M', 'P', '4', 'V'):
|
|
||||||
case MAKEFOURCC('3', 'I', 'V', 'X'):
|
|
||||||
case MAKEFOURCC('W', 'V', '1', 'F'):
|
|
||||||
case MAKEFOURCC('F', 'M', 'P', '4'):
|
|
||||||
case MAKEFOURCC('S', 'M', 'P', '4'):
|
|
||||||
return CODEC_ID_MPEG4;
|
|
||||||
case MAKEFOURCC('D', 'I', 'V', '3'):
|
|
||||||
case MAKEFOURCC('D', 'V', 'X', '3'):
|
|
||||||
case MAKEFOURCC('M', 'P', '4', '3'):
|
|
||||||
return CODEC_ID_MSMPEG4V3;
|
|
||||||
case MAKEFOURCC('M', 'P', '4', '2'):
|
|
||||||
return CODEC_ID_MSMPEG4V2;
|
|
||||||
case MAKEFOURCC('M', 'P', '4', '1'):
|
|
||||||
return CODEC_ID_MSMPEG4V1;
|
|
||||||
case MAKEFOURCC('W', 'M', 'V', '1'):
|
|
||||||
return CODEC_ID_WMV1;
|
|
||||||
case MAKEFOURCC('W', 'M', 'V', '2'):
|
|
||||||
return CODEC_ID_WMV2;
|
|
||||||
case MAKEFOURCC('W', 'M', 'V', '3'):
|
|
||||||
return CODEC_ID_WMV3;
|
|
||||||
/*
|
|
||||||
case MAKEFOURCC('M', 'S', 'S', '1'):
|
|
||||||
case MAKEFOURCC('M', 'S', 'S', '2'):
|
|
||||||
case MAKEFOURCC('W', 'V', 'P', '2'):
|
|
||||||
case MAKEFOURCC('W', 'M', 'V', 'P'):
|
|
||||||
return CODEC_ID_WMV9_LIB;
|
|
||||||
*/
|
|
||||||
case MAKEFOURCC('W', 'V', 'C', '1'):
|
|
||||||
return CODEC_ID_VC1;
|
|
||||||
case MAKEFOURCC('V', 'P', '5', '0'):
|
|
||||||
return CODEC_ID_VP5;
|
|
||||||
case MAKEFOURCC('V', 'P', '6', '0'):
|
|
||||||
case MAKEFOURCC('V', 'P', '6', '1'):
|
|
||||||
case MAKEFOURCC('V', 'P', '6', '2'):
|
|
||||||
return CODEC_ID_VP6;
|
|
||||||
case MAKEFOURCC('V', 'P', '6', 'F'):
|
|
||||||
case MAKEFOURCC('F', 'L', 'V', '4'):
|
|
||||||
return CODEC_ID_VP6F;
|
|
||||||
case MAKEFOURCC('C', 'A', 'V', 'S'):
|
|
||||||
return CODEC_ID_CAVS;
|
|
||||||
case MAKEFOURCC('M', 'P', 'G', '1'):
|
|
||||||
case MAKEFOURCC('M', 'P', 'E', 'G'):
|
|
||||||
return CODEC_ID_MPEG2VIDEO; // not a typo
|
|
||||||
case MAKEFOURCC('M', 'P', 'G', '2'):
|
|
||||||
case MAKEFOURCC('E', 'M', '2', 'V'):
|
|
||||||
case MAKEFOURCC('M', 'M', 'E', 'S'):
|
|
||||||
return CODEC_ID_MPEG2VIDEO;
|
|
||||||
case MAKEFOURCC('H', '2', '6', '3'):
|
|
||||||
case MAKEFOURCC('S', '2', '6', '3'):
|
|
||||||
case MAKEFOURCC('L', '2', '6', '3'):
|
|
||||||
case MAKEFOURCC('M', '2', '6', '3'):
|
|
||||||
case MAKEFOURCC('U', '2', '6', '3'):
|
|
||||||
case MAKEFOURCC('X', '2', '6', '3'):
|
|
||||||
return CODEC_ID_H263;
|
|
||||||
case MAKEFOURCC('H', '2', '6', '4'):
|
|
||||||
case MAKEFOURCC('X', '2', '6', '4'):
|
|
||||||
case MAKEFOURCC('V', 'S', 'S', 'H'):
|
|
||||||
case MAKEFOURCC('D', 'A', 'V', 'C'):
|
|
||||||
case MAKEFOURCC('P', 'A', 'V', 'C'):
|
|
||||||
case MAKEFOURCC('A', 'V', 'C', '1'):
|
|
||||||
return CODEC_ID_H264;
|
|
||||||
case MAKEFOURCC('M', 'J', 'P', 'G'):
|
|
||||||
case MAKEFOURCC('L', 'J', 'P', 'G'):
|
|
||||||
case MAKEFOURCC('M', 'J', 'L', 'S'):
|
|
||||||
case MAKEFOURCC('J', 'P', 'E', 'G'): // questionable fourcc?
|
|
||||||
case MAKEFOURCC('A', 'V', 'R', 'N'):
|
|
||||||
case MAKEFOURCC('M', 'J', 'P', 'A'):
|
|
||||||
return CODEC_ID_MJPEG;
|
|
||||||
case MAKEFOURCC('D', 'V', 'S', 'D'):
|
|
||||||
case MAKEFOURCC('D', 'V', '2', '5'):
|
|
||||||
case MAKEFOURCC('D', 'V', '5', '0'):
|
|
||||||
case MAKEFOURCC('C', 'D', 'V', 'C'):
|
|
||||||
case MAKEFOURCC('C', 'D', 'V', '5'):
|
|
||||||
case MAKEFOURCC('D', 'V', 'I', 'S'):
|
|
||||||
case MAKEFOURCC('P', 'D', 'V', 'C'):
|
|
||||||
return CODEC_ID_DVVIDEO;
|
|
||||||
case MAKEFOURCC('H', 'F', 'Y', 'U'):
|
|
||||||
case MAKEFOURCC('F', 'F', 'V', 'H'):
|
|
||||||
return CODEC_ID_HUFFYUV;
|
|
||||||
case MAKEFOURCC('C', 'Y', 'U', 'V'):
|
|
||||||
return CODEC_ID_CYUV;
|
|
||||||
case MAKEFOURCC('A', 'S', 'V', '1'):
|
|
||||||
return CODEC_ID_ASV1;
|
|
||||||
case MAKEFOURCC('A', 'S', 'V', '2'):
|
|
||||||
return CODEC_ID_ASV2;
|
|
||||||
case MAKEFOURCC('V', 'C', 'R', '1'):
|
|
||||||
return CODEC_ID_VCR1;
|
|
||||||
case MAKEFOURCC('T', 'H', 'E', 'O'):
|
|
||||||
return CODEC_ID_THEORA;
|
|
||||||
case MAKEFOURCC('S', 'V', 'Q', '1'):
|
|
||||||
return CODEC_ID_SVQ1;
|
|
||||||
case MAKEFOURCC('S', 'V', 'Q', '3'):
|
|
||||||
return CODEC_ID_SVQ3;
|
|
||||||
case MAKEFOURCC('R', 'P', 'Z', 'A'):
|
|
||||||
return CODEC_ID_RPZA;
|
|
||||||
case MAKEFOURCC('F', 'F', 'V', '1'):
|
|
||||||
return CODEC_ID_FFV1;
|
|
||||||
case MAKEFOURCC('V', 'P', '3', '1'):
|
|
||||||
return CODEC_ID_VP3;
|
|
||||||
case MAKEFOURCC('R', 'L', 'E', '8'):
|
|
||||||
return CODEC_ID_MSRLE;
|
|
||||||
case MAKEFOURCC('M', 'S', 'Z', 'H'):
|
|
||||||
return CODEC_ID_MSZH;
|
|
||||||
case MAKEFOURCC('Z', 'L', 'I', 'B'):
|
|
||||||
return CODEC_ID_FLV1;
|
|
||||||
case MAKEFOURCC('F', 'L', 'V', '1'):
|
|
||||||
return CODEC_ID_ZLIB;
|
|
||||||
/*
|
|
||||||
case MAKEFOURCC('P', 'N', 'G', '1'):
|
|
||||||
return CODEC_ID_COREPNG;
|
|
||||||
*/
|
|
||||||
case MAKEFOURCC('M', 'P', 'N', 'G'):
|
|
||||||
return CODEC_ID_PNG;
|
|
||||||
/*
|
|
||||||
case MAKEFOURCC('A', 'V', 'I', 'S'):
|
|
||||||
return CODEC_ID_AVISYNTH;
|
|
||||||
*/
|
|
||||||
case MAKEFOURCC('C', 'R', 'A', 'M'):
|
|
||||||
return CODEC_ID_MSVIDEO1;
|
|
||||||
case MAKEFOURCC('R', 'T', '2', '1'):
|
|
||||||
return CODEC_ID_INDEO2;
|
|
||||||
case MAKEFOURCC('I', 'V', '3', '2'):
|
|
||||||
case MAKEFOURCC('I', 'V', '3', '1'):
|
|
||||||
return CODEC_ID_INDEO3;
|
|
||||||
case MAKEFOURCC('C', 'V', 'I', 'D'):
|
|
||||||
return CODEC_ID_CINEPAK;
|
|
||||||
case MAKEFOURCC('R', 'V', '1', '0'):
|
|
||||||
return CODEC_ID_RV10;
|
|
||||||
case MAKEFOURCC('R', 'V', '2', '0'):
|
|
||||||
return CODEC_ID_RV20;
|
|
||||||
case MAKEFOURCC('8', 'B', 'P', 'S'):
|
|
||||||
return CODEC_ID_8BPS;
|
|
||||||
case MAKEFOURCC('Q', 'R', 'L', 'E'):
|
|
||||||
return CODEC_ID_QTRLE;
|
|
||||||
case MAKEFOURCC('D', 'U', 'C', 'K'):
|
|
||||||
return CODEC_ID_TRUEMOTION1;
|
|
||||||
case MAKEFOURCC('T', 'M', '2', '0'):
|
|
||||||
return CODEC_ID_TRUEMOTION2;
|
|
||||||
case MAKEFOURCC('T', 'S', 'C', 'C'):
|
|
||||||
return CODEC_ID_TSCC;
|
|
||||||
case MAKEFOURCC('S', 'N', 'O', 'W'):
|
|
||||||
return CODEC_ID_SNOW;
|
|
||||||
case MAKEFOURCC('Q', 'P', 'E', 'G'):
|
|
||||||
case MAKEFOURCC('Q', '1', '_', '0'):
|
|
||||||
case MAKEFOURCC('Q', '1', '_', '1'):
|
|
||||||
return CODEC_ID_QPEG;
|
|
||||||
case MAKEFOURCC('H', '2', '6', '1'):
|
|
||||||
case MAKEFOURCC('M', '2', '6', '1'):
|
|
||||||
return CODEC_ID_H261;
|
|
||||||
case MAKEFOURCC('L', 'O', 'C', 'O'):
|
|
||||||
return CODEC_ID_LOCO;
|
|
||||||
case MAKEFOURCC('W', 'N', 'V', '1'):
|
|
||||||
return CODEC_ID_WNV1;
|
|
||||||
case MAKEFOURCC('C', 'S', 'C', 'D'):
|
|
||||||
return CODEC_ID_CSCD;
|
|
||||||
case MAKEFOURCC('Z', 'M', 'B', 'V'):
|
|
||||||
return CODEC_ID_ZMBV;
|
|
||||||
case MAKEFOURCC('U', 'L', 'T', 'I'):
|
|
||||||
return CODEC_ID_ULTI;
|
|
||||||
case MAKEFOURCC('V', 'I', 'X', 'L'):
|
|
||||||
return CODEC_ID_VIXL;
|
|
||||||
case MAKEFOURCC('A', 'A', 'S', 'C'):
|
|
||||||
return CODEC_ID_AASC;
|
|
||||||
case MAKEFOURCC('F', 'P', 'S', '1'):
|
|
||||||
return CODEC_ID_FRAPS;
|
|
||||||
default:
|
|
||||||
return CODEC_ID_NONE;
|
|
||||||
}
|
|
||||||
} else if (!strcmp(Codec, "V_MPEG4/ISO/AVC"))
|
|
||||||
return CODEC_ID_H264;
|
|
||||||
else if (!strcmp(Codec, "V_MPEG4/ISO/ASP"))
|
|
||||||
return CODEC_ID_MPEG4;
|
|
||||||
else if (!strcmp(Codec, "V_MPEG2"))
|
|
||||||
return CODEC_ID_MPEG2VIDEO;
|
|
||||||
else if (!strcmp(Codec, "V_MPEG1"))
|
|
||||||
return CODEC_ID_MPEG2VIDEO; // still not a typo
|
|
||||||
else if (!strcmp(Codec, "V_SNOW"))
|
|
||||||
return CODEC_ID_SNOW;
|
|
||||||
else if (!strcmp(Codec, "V_THEORA"))
|
|
||||||
return CODEC_ID_THEORA;
|
|
||||||
else if (!strncmp(Codec, "V_REAL/RV", 9)) {
|
|
||||||
switch (Codec[9]) {
|
|
||||||
case '1':
|
|
||||||
return CODEC_ID_RV10;
|
|
||||||
case '2':
|
|
||||||
return CODEC_ID_RV20;
|
|
||||||
case '3':
|
|
||||||
return CODEC_ID_RV30;
|
|
||||||
case '4':
|
|
||||||
return CODEC_ID_RV40;
|
|
||||||
default:
|
|
||||||
return CODEC_ID_NONE;
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
return CODEC_ID_NONE;
|
|
||||||
}
|
|
||||||
public:
|
public:
|
||||||
FFMKVSource(const char *Source, int Track, FILE *Timecodes, IScriptEnvironment* Env) {
|
FFMKVSource(const char *Source, int Track, FILE *Timecodes, bool Cache, FILE *CacheFile, IScriptEnvironment* Env) {
|
||||||
BufferSize = 0;
|
BufferSize = 0;
|
||||||
Buffer = NULL;
|
Buffer = NULL;
|
||||||
Frame = NULL;
|
Frame = NULL;
|
||||||
|
@ -344,14 +184,14 @@ public:
|
||||||
|
|
||||||
ST.fp = fopen(Source, "rb");
|
ST.fp = fopen(Source, "rb");
|
||||||
if (ST.fp == NULL)
|
if (ST.fp == NULL)
|
||||||
Env->ThrowError("FFmpegSource: Can't open '%s': %s\n", Source, strerror(errno));
|
Env->ThrowError("FFmpegSource: Can't open '%s': %s", Source, strerror(errno));
|
||||||
|
|
||||||
setvbuf(ST.fp, NULL, _IOFBF, CACHESIZE);
|
setvbuf(ST.fp, NULL, _IOFBF, CACHESIZE);
|
||||||
|
|
||||||
MF = mkv_OpenEx(&ST.base, 0, 0, ErrorMessage, sizeof(ErrorMessage));
|
MF = mkv_OpenEx(&ST.base, 0, 0, ErrorMessage, sizeof(ErrorMessage));
|
||||||
if (MF == NULL) {
|
if (MF == NULL) {
|
||||||
fclose(ST.fp);
|
fclose(ST.fp);
|
||||||
Env->ThrowError("FFmpegSource: Can't parse Matroska file: %s\n", ErrorMessage);
|
Env->ThrowError("FFmpegSource: Can't parse Matroska file: %s", ErrorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Track < 0)
|
if (Track < 0)
|
||||||
|
@ -364,8 +204,8 @@ public:
|
||||||
if (Track < 0)
|
if (Track < 0)
|
||||||
Env->ThrowError("FFmpegSource: No video track found");
|
Env->ThrowError("FFmpegSource: No video track found");
|
||||||
|
|
||||||
if ((unsigned)Track >= mkv_GetNumTracks(MF))
|
if ((unsigned int)Track >= mkv_GetNumTracks(MF))
|
||||||
Env->ThrowError("FFmpegSource: Invalid track number: %d\n", Track);
|
Env->ThrowError("FFmpegSource: Invalid track number: %d", Track);
|
||||||
|
|
||||||
TrackInfo *TI = mkv_GetTrackInfo(MF, Track);
|
TrackInfo *TI = mkv_GetTrackInfo(MF, Track);
|
||||||
|
|
||||||
|
@ -377,7 +217,7 @@ public:
|
||||||
if (TI->CompEnabled) {
|
if (TI->CompEnabled) {
|
||||||
CS = cs_Create(MF, Track, ErrorMessage, sizeof(ErrorMessage));
|
CS = cs_Create(MF, Track, ErrorMessage, sizeof(ErrorMessage));
|
||||||
if (CS == NULL)
|
if (CS == NULL)
|
||||||
Env->ThrowError("FFmpegSource: Can't create decompressor: %s\n", ErrorMessage);
|
Env->ThrowError("FFmpegSource: Can't create decompressor: %s", ErrorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
avcodec_get_context_defaults(&CodecContext);
|
avcodec_get_context_defaults(&CodecContext);
|
||||||
|
@ -399,24 +239,30 @@ public:
|
||||||
|
|
||||||
SetOutputFormat(CodecContext.pix_fmt, Env);
|
SetOutputFormat(CodecContext.pix_fmt, Env);
|
||||||
|
|
||||||
unsigned TrackNumber, FrameSize, FrameFlags;
|
// Needs to be indexed?
|
||||||
ulonglong StartTime, EndTime, FilePos;
|
if (!(Cache && LoadFrameInfoFromFile(CacheFile))) {
|
||||||
|
unsigned int TrackNumber, FrameSize, FrameFlags;
|
||||||
|
uint64_t StartTime, EndTime, FilePos;
|
||||||
|
|
||||||
while (mkv_ReadFrame(MF, 0, &TrackNumber, &StartTime, &EndTime, &FilePos, &FrameSize, &FrameFlags) == 0) {
|
while (mkv_ReadFrame(MF, 0, &TrackNumber, &StartTime, &EndTime, &FilePos, &FrameSize, &FrameFlags) == 0) {
|
||||||
FFMKVFrameInfo FI;
|
FrameToDTS.push_back(FrameInfo(StartTime, (FrameFlags & FRAME_KF) != 0));
|
||||||
FI.DTS = StartTime;
|
|
||||||
FI.KeyFrame = (FrameFlags & FRAME_KF) != 0;
|
|
||||||
|
|
||||||
FrameToDTS.push_back(FI);
|
|
||||||
DTSToFrame[StartTime] = VI.num_frames;
|
|
||||||
if (Timecodes)
|
|
||||||
fprintf(Timecodes, "%f\n", (StartTime * mkv_TruncFloat(TI->TimecodeScale)) / (double)(1000000));
|
|
||||||
VI.num_frames++;
|
VI.num_frames++;
|
||||||
}
|
}
|
||||||
|
|
||||||
Frame = avcodec_alloc_frame();
|
|
||||||
|
|
||||||
mkv_Seek(MF, FrameToDTS[0].DTS, MKVF_SEEK_TO_PREV_KEYFRAME);
|
mkv_Seek(MF, FrameToDTS[0].DTS, MKVF_SEEK_TO_PREV_KEYFRAME);
|
||||||
|
|
||||||
|
if (Cache) {
|
||||||
|
fprintf(CacheFile, "%d\n", VI.num_frames);
|
||||||
|
for (int i = 0; i < VI.num_frames; i++)
|
||||||
|
fprintf(CacheFile, "%lld %d\n", FrameToDTS[i].DTS, (int)(FrameToDTS[i].KeyFrame ? 1 : 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Timecodes)
|
||||||
|
for (int i = 0; i < VI.num_frames; i++)
|
||||||
|
fprintf(Timecodes, "%f\n", (FrameToDTS[i].DTS * mkv_TruncFloat(TI->TimecodeScale)) / (double)(1000000));
|
||||||
|
|
||||||
|
Frame = avcodec_alloc_frame();
|
||||||
}
|
}
|
||||||
|
|
||||||
~FFMKVSource() {
|
~FFMKVSource() {
|
||||||
|
@ -430,89 +276,104 @@ public:
|
||||||
PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* Env);
|
PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* Env);
|
||||||
};
|
};
|
||||||
|
|
||||||
int FFMKVSource::ReadNextFrame(AVFrame *Frame, ulonglong *StartTime, IScriptEnvironment* Env) {
|
unsigned int FFMKVSource::ReadNextFrame(int64_t *FirstStartTime, IScriptEnvironment *Env) {
|
||||||
unsigned TrackNumber, FrameFlags, FrameSize;
|
unsigned int TrackNumber, FrameFlags, FrameSize;
|
||||||
ulonglong EndTime, FilePos, StartTime2;
|
uint64_t EndTime, FilePos, StartTime;
|
||||||
*StartTime = -1;
|
*FirstStartTime = -1;
|
||||||
int FrameFinished = 0;
|
|
||||||
int Ret = -1;
|
|
||||||
|
|
||||||
while (mkv_ReadFrame(MF, 0, &TrackNumber, &StartTime2, &EndTime, &FilePos, &FrameSize, &FrameFlags) == 0) {
|
while (mkv_ReadFrame(MF, 0, &TrackNumber, &StartTime, &EndTime, &FilePos, &FrameSize, &FrameFlags) == 0) {
|
||||||
if ((longlong)*StartTime < 0)
|
if (*FirstStartTime < 0)
|
||||||
*StartTime = StartTime2;
|
*FirstStartTime = StartTime;
|
||||||
if (CS) {
|
if (CS) {
|
||||||
char CSBuffer[1024];
|
char CSBuffer[4096];
|
||||||
|
|
||||||
|
int DecompressedFrameSize = 0;
|
||||||
|
|
||||||
cs_NextFrame(CS, FilePos, FrameSize);
|
cs_NextFrame(CS, FilePos, FrameSize);
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
int ReadBytes = cs_ReadData(CS, CSBuffer, sizeof(CSBuffer));
|
int ReadBytes = cs_ReadData(CS, CSBuffer, sizeof(CSBuffer));
|
||||||
if (ReadBytes < 0)
|
if (ReadBytes < 0)
|
||||||
Env->ThrowError("FFmpegSource: Error decompressing data: %s\n", cs_GetLastError(CS));
|
Env->ThrowError("FFmpegSource: Error decompressing data: %s", cs_GetLastError(CS));
|
||||||
if (ReadBytes == 0)
|
if (ReadBytes == 0)
|
||||||
break;
|
return DecompressedFrameSize;
|
||||||
Ret = avcodec_decode_video(&CodecContext, Frame, &FrameFinished, (uint8_t *)CSBuffer, ReadBytes);
|
|
||||||
if (FrameFinished)
|
if (BufferSize < DecompressedFrameSize + ReadBytes) {
|
||||||
goto Done;
|
BufferSize = FrameSize;
|
||||||
|
Buffer = (uint8_t *)realloc(Buffer, BufferSize);
|
||||||
|
if (Buffer == NULL)
|
||||||
|
Env->ThrowError("FFmpegSource: Out of memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(Buffer + DecompressedFrameSize, CSBuffer, ReadBytes);
|
||||||
|
DecompressedFrameSize += ReadBytes;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
size_t ReadBytes;
|
|
||||||
|
|
||||||
if (fseek(ST.fp, FilePos, SEEK_SET))
|
if (fseek(ST.fp, FilePos, SEEK_SET))
|
||||||
Env->ThrowError("FFmpegSource: fseek(): %s\n", strerror(errno));
|
Env->ThrowError("FFmpegSource: fseek(): %s", strerror(errno));
|
||||||
|
|
||||||
if (BufferSize < FrameSize) {
|
if (BufferSize < FrameSize) {
|
||||||
BufferSize = FrameSize;
|
BufferSize = FrameSize;
|
||||||
Buffer = realloc(Buffer, BufferSize);
|
Buffer = (uint8_t *)realloc(Buffer, BufferSize);
|
||||||
if (Buffer == NULL)
|
if (Buffer == NULL)
|
||||||
Env->ThrowError("FFmpegSource: Out of memory\n");
|
Env->ThrowError("FFmpegSource: Out of memory");
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadBytes = fread(Buffer, 1, FrameSize, ST.fp);
|
size_t ReadBytes = fread(Buffer, 1, FrameSize, ST.fp);
|
||||||
if (ReadBytes != FrameSize) {
|
if (ReadBytes != FrameSize) {
|
||||||
if (ReadBytes == 0) {
|
if (ReadBytes == 0) {
|
||||||
if (feof(ST.fp))
|
if (feof(ST.fp))
|
||||||
fprintf(stderr, "FFmpegSource: Unexpected EOF while reading frame\n");
|
Env->ThrowError("FFmpegSource: Unexpected EOF while reading frame");
|
||||||
else
|
else
|
||||||
fprintf(stderr, "FFmpegSource: Error reading frame: %s\n", strerror(errno));
|
Env->ThrowError("FFmpegSource: Error reading frame: %s", strerror(errno));
|
||||||
} else
|
} else
|
||||||
fprintf(stderr,"FFmpegSource: Short read while reading frame\n");
|
Env->ThrowError("FFmpegSource: Short read while reading frame");
|
||||||
goto Done;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ret = avcodec_decode_video(&CodecContext, Frame, &FrameFinished, (uint8_t *)Buffer, FrameSize);
|
return FrameSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int FFMKVSource::DecodeNextFrame(AVFrame *Frame, int64_t *FirstStartTime, IScriptEnvironment* Env) {
|
||||||
|
int FrameFinished = 0;
|
||||||
|
int Ret = -1;
|
||||||
|
int FrameSize;
|
||||||
|
int64_t TempStartTime = -1;
|
||||||
|
|
||||||
|
while (FrameSize = ReadNextFrame(&TempStartTime, Env)) {
|
||||||
|
if (*FirstStartTime < 0)
|
||||||
|
*FirstStartTime = TempStartTime;
|
||||||
|
Ret = avcodec_decode_video(&CodecContext, Frame, &FrameFinished, Buffer, FrameSize);
|
||||||
|
|
||||||
if (FrameFinished)
|
if (FrameFinished)
|
||||||
goto Done;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Done:
|
|
||||||
return Ret;
|
return Ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
PVideoFrame __stdcall FFMKVSource::GetFrame(int n, IScriptEnvironment* Env) {
|
PVideoFrame __stdcall FFMKVSource::GetFrame(int n, IScriptEnvironment* Env) {
|
||||||
bool HasSeeked = false;
|
bool HasSeeked = false;
|
||||||
bool HasBiggerKF = false;
|
|
||||||
|
|
||||||
for (int i = CurrentFrame + 1; i <= n; i++)
|
if (n < CurrentFrame || FindClosestKeyFrame(n) > CurrentFrame) {
|
||||||
if (FrameToDTS[i].KeyFrame) {
|
|
||||||
HasBiggerKF = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (n < CurrentFrame || HasBiggerKF) {
|
|
||||||
mkv_Seek(MF, FrameToDTS[n].DTS, MKVF_SEEK_TO_PREV_KEYFRAME);
|
mkv_Seek(MF, FrameToDTS[n].DTS, MKVF_SEEK_TO_PREV_KEYFRAME);
|
||||||
avcodec_flush_buffers(&CodecContext);
|
avcodec_flush_buffers(&CodecContext);
|
||||||
HasSeeked = true;
|
HasSeeked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
ulonglong StartTime;
|
int64_t StartTime;
|
||||||
int Ret = ReadNextFrame(Frame, &StartTime, Env);
|
int Ret = DecodeNextFrame(Frame, &StartTime, Env);
|
||||||
|
|
||||||
if (HasSeeked) {
|
if (HasSeeked) {
|
||||||
CurrentFrame = DTSToFrame[StartTime];
|
|
||||||
HasSeeked = false;
|
HasSeeked = false;
|
||||||
|
|
||||||
|
if (StartTime < 0 || (CurrentFrame = FrameFromDTS(StartTime)) < 0)
|
||||||
|
Env->ThrowError("FFmpegSource: Frame accurate seeking not possible in this file");
|
||||||
}
|
}
|
||||||
|
|
||||||
CurrentFrame++;
|
CurrentFrame++;
|
||||||
|
@ -529,14 +390,13 @@ private:
|
||||||
AVFrame *Frame;
|
AVFrame *Frame;
|
||||||
int Track;
|
int Track;
|
||||||
int CurrentFrame;
|
int CurrentFrame;
|
||||||
std::vector<int64_t> FrameToDTS;
|
int SeekMode;
|
||||||
std::map<int64_t, int> DTSToFrame;
|
|
||||||
bool ForceSeek;
|
|
||||||
|
|
||||||
int ReadNextFrame(AVFrame *Frame, int64_t *DTS);
|
int DecodeNextFrame(AVFrame *Frame, int64_t *DTS);
|
||||||
public:
|
public:
|
||||||
FFmpegSource(const char *Source, int _Track, bool _ForceSeek, FILE *Timecodes, IScriptEnvironment* Env) : Track(_Track), ForceSeek(_ForceSeek) {
|
FFmpegSource(const char *Source, int _Track, int _SeekMode, FILE *Timecodes, bool Cache, FILE *CacheFile, IScriptEnvironment* Env) : Track(_Track), SeekMode(_SeekMode) {
|
||||||
CurrentFrame = 0;
|
CurrentFrame = 0;
|
||||||
|
Frame = NULL;
|
||||||
|
|
||||||
if (av_open_input_file(&FormatContext, Source, NULL, 0, NULL) != 0)
|
if (av_open_input_file(&FormatContext, Source, NULL, 0, NULL) != 0)
|
||||||
Env->ThrowError("FFmpegSource: Couldn't open \"%s\"", Source);
|
Env->ThrowError("FFmpegSource: Couldn't open \"%s\"", Source);
|
||||||
|
@ -574,7 +434,7 @@ public:
|
||||||
VI.height = CodecContext->height;
|
VI.height = CodecContext->height;
|
||||||
VI.fps_denominator = FormatContext->streams[Track]->time_base.num;
|
VI.fps_denominator = FormatContext->streams[Track]->time_base.num;
|
||||||
VI.fps_numerator = FormatContext->streams[Track]->time_base.den;
|
VI.fps_numerator = FormatContext->streams[Track]->time_base.den;
|
||||||
VI.num_frames = FormatContext->streams[Track]->duration;
|
VI.num_frames = (int)FormatContext->streams[Track]->duration;
|
||||||
|
|
||||||
// sanity check framerate
|
// sanity check framerate
|
||||||
if (VI.fps_denominator > VI.fps_numerator || VI.fps_denominator <= 0 || VI.fps_numerator <= 0) {
|
if (VI.fps_denominator > VI.fps_numerator || VI.fps_denominator <= 0 || VI.fps_numerator <= 0) {
|
||||||
|
@ -584,30 +444,32 @@ public:
|
||||||
|
|
||||||
SetOutputFormat(CodecContext->pix_fmt, Env);
|
SetOutputFormat(CodecContext->pix_fmt, Env);
|
||||||
|
|
||||||
// skip indexing for avi
|
// Needs to be indexed?
|
||||||
if (!strcmp(FormatContext->iformat->name, "avi") && !Timecodes) {
|
if (!(Cache && LoadFrameInfoFromFile(CacheFile))) {
|
||||||
for (int i = 0; i < VI.num_frames; i++) {
|
|
||||||
FrameToDTS.push_back(i);
|
|
||||||
DTSToFrame[i] = i;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
AVPacket Packet;
|
AVPacket Packet;
|
||||||
VI.num_frames = 0;
|
VI.num_frames = 0;
|
||||||
while (av_read_frame(FormatContext, &Packet) >= 0) {
|
while (av_read_frame(FormatContext, &Packet) >= 0) {
|
||||||
if (Packet.stream_index == Track) {
|
if (Packet.stream_index == Track) {
|
||||||
FrameToDTS.push_back(Packet.dts);
|
FrameToDTS.push_back(FrameInfo(Packet.dts, (Packet.flags & PKT_FLAG_KEY) != 0));
|
||||||
DTSToFrame[Packet.dts] = VI.num_frames;
|
|
||||||
if (Timecodes)
|
|
||||||
fprintf(Timecodes, "%f\n", (Packet.dts * FormatContext->streams[Track]->time_base.num * 1000) / (double)(FormatContext->streams[Track]->time_base.den));
|
|
||||||
VI.num_frames++;
|
VI.num_frames++;
|
||||||
}
|
}
|
||||||
av_free_packet(&Packet);
|
av_free_packet(&Packet);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Frame = avcodec_alloc_frame();
|
|
||||||
|
|
||||||
av_seek_frame(FormatContext, Track, 0, AVSEEK_FLAG_BACKWARD);
|
av_seek_frame(FormatContext, Track, 0, AVSEEK_FLAG_BACKWARD);
|
||||||
|
|
||||||
|
if (Cache) {
|
||||||
|
fprintf(CacheFile, "%d\n", VI.num_frames);
|
||||||
|
for (int i = 0; i < VI.num_frames; i++)
|
||||||
|
fprintf(CacheFile, "%lld %d\n", FrameToDTS[i].DTS, (int)(FrameToDTS[i].KeyFrame ? 1 : 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Timecodes)
|
||||||
|
for (int i = 0; i < VI.num_frames; i++)
|
||||||
|
fprintf(Timecodes, "%f\n", (FrameToDTS[i].DTS * FormatContext->streams[Track]->time_base.num * 1000) / (double)(FormatContext->streams[Track]->time_base.den));
|
||||||
|
|
||||||
|
Frame = avcodec_alloc_frame();
|
||||||
}
|
}
|
||||||
|
|
||||||
~FFmpegSource() {
|
~FFmpegSource() {
|
||||||
|
@ -619,7 +481,7 @@ public:
|
||||||
PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* Env);
|
PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* Env);
|
||||||
};
|
};
|
||||||
|
|
||||||
int FFmpegSource::ReadNextFrame(AVFrame *Frame, int64_t *DTS) {
|
int FFmpegSource::DecodeNextFrame(AVFrame *Frame, int64_t *DTS) {
|
||||||
AVPacket Packet;
|
AVPacket Packet;
|
||||||
int FrameFinished = 0;
|
int FrameFinished = 0;
|
||||||
int Ret = -1;
|
int Ret = -1;
|
||||||
|
@ -627,7 +489,6 @@ int FFmpegSource::ReadNextFrame(AVFrame *Frame, int64_t *DTS) {
|
||||||
|
|
||||||
while (av_read_frame(FormatContext, &Packet) >= 0) {
|
while (av_read_frame(FormatContext, &Packet) >= 0) {
|
||||||
if (Packet.stream_index == Track) {
|
if (Packet.stream_index == Track) {
|
||||||
|
|
||||||
Ret = avcodec_decode_video(CodecContext, Frame, &FrameFinished, Packet.data, Packet.size);
|
Ret = avcodec_decode_video(CodecContext, Frame, &FrameFinished, Packet.data, Packet.size);
|
||||||
|
|
||||||
if (*DTS < 0)
|
if (*DTS < 0)
|
||||||
|
@ -646,28 +507,44 @@ int FFmpegSource::ReadNextFrame(AVFrame *Frame, int64_t *DTS) {
|
||||||
PVideoFrame __stdcall FFmpegSource::GetFrame(int n, IScriptEnvironment* Env) {
|
PVideoFrame __stdcall FFmpegSource::GetFrame(int n, IScriptEnvironment* Env) {
|
||||||
bool HasSeeked = false;
|
bool HasSeeked = false;
|
||||||
|
|
||||||
int IndexPosition = av_index_search_timestamp(FormatContext->streams[Track], FrameToDTS[n], AVSEEK_FLAG_BACKWARD);
|
if (SeekMode == 0) {
|
||||||
int64_t NearestIndexDTS = -1;
|
if (n < CurrentFrame) {
|
||||||
if (IndexPosition >= 0)
|
av_seek_frame(FormatContext, Track, 0, AVSEEK_FLAG_BACKWARD);
|
||||||
NearestIndexDTS = FormatContext->streams[Track]->index_entries[IndexPosition].timestamp;
|
avcodec_flush_buffers(CodecContext);
|
||||||
|
CurrentFrame = 0;
|
||||||
if (n < CurrentFrame || NearestIndexDTS > FrameToDTS[CurrentFrame] || (ForceSeek && IndexPosition == -1 && n > CurrentFrame + 10)) {
|
}
|
||||||
av_seek_frame(FormatContext, Track, FrameToDTS[n], AVSEEK_FLAG_BACKWARD);
|
} else {
|
||||||
|
if (n < CurrentFrame || FindClosestKeyFrame(n) > CurrentFrame || (SeekMode == 3 && n > CurrentFrame + 10)) {
|
||||||
|
av_seek_frame(FormatContext, Track, (SeekMode == 3) ? FrameToDTS[n].DTS : FrameToDTS[FindClosestKeyFrame(n)].DTS, AVSEEK_FLAG_BACKWARD);
|
||||||
avcodec_flush_buffers(CodecContext);
|
avcodec_flush_buffers(CodecContext);
|
||||||
HasSeeked = true;
|
HasSeeked = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
int64_t DTS;
|
int64_t DTS;
|
||||||
int Ret = ReadNextFrame(Frame, &DTS);
|
int Ret = DecodeNextFrame(Frame, &DTS);
|
||||||
|
|
||||||
if (HasSeeked) {
|
if (HasSeeked) {
|
||||||
CurrentFrame = DTSToFrame[DTS];
|
HasSeeked = false;
|
||||||
HasSeeked = DTS < 0;
|
|
||||||
|
// Is the seek destination time known? Does it belong to a frame?
|
||||||
|
if (DTS < 0 || (CurrentFrame = FrameFromDTS(DTS)) < 0) {
|
||||||
|
switch (SeekMode) {
|
||||||
|
case 1:
|
||||||
|
Env->ThrowError("FFmpegSource: Frame accurate seeking not possible in this file");
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
CurrentFrame = ClosestFrameFromDTS(DTS);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Env->ThrowError("FFmpegSource: Failed assertion");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CurrentFrame++;
|
CurrentFrame++;
|
||||||
} while (CurrentFrame <= n || HasSeeked);
|
} while (CurrentFrame <= n);
|
||||||
|
|
||||||
return OutputFrame(Frame, Env);
|
return OutputFrame(Frame, Env);
|
||||||
}
|
}
|
||||||
|
@ -676,19 +553,22 @@ AVSValue __cdecl CreateFFmpegSource(AVSValue Args, void* UserData, IScriptEnviro
|
||||||
if (!Args[0].Defined())
|
if (!Args[0].Defined())
|
||||||
Env->ThrowError("FFmpegSource: No source specified");
|
Env->ThrowError("FFmpegSource: No source specified");
|
||||||
|
|
||||||
av_register_all();
|
// Generate default cache filename
|
||||||
|
char DefaultCacheFilename[MAX_PATH];
|
||||||
|
strcpy(DefaultCacheFilename, Args[0].AsString());
|
||||||
|
strcat(DefaultCacheFilename, ".ffcache");
|
||||||
|
|
||||||
AVFormatContext *FormatContext;
|
const char *Source = Args[0].AsString();
|
||||||
|
int Track = Args[1].AsInt(-1);
|
||||||
|
int SeekMode = Args[2].AsInt(1);
|
||||||
|
const char *Timecodes = Args[3].AsString("");
|
||||||
|
bool Cache = Args[4].AsBool(true);
|
||||||
|
const char *CacheFilename = Args[5].AsString(DefaultCacheFilename);
|
||||||
|
|
||||||
if (av_open_input_file(&FormatContext, Args[0].AsString(), NULL, 0, NULL) != 0)
|
if (SeekMode < 0 || SeekMode > 3)
|
||||||
Env->ThrowError("FFmpegSource: Couldn't open \"%s\"", Args[0].AsString());
|
Env->ThrowError("FFmpegSource: Invalid seek mode selected");
|
||||||
|
|
||||||
bool IsMatroska = !strcmp(FormatContext->iformat->name, "matroska");
|
|
||||||
|
|
||||||
av_close_input_file(FormatContext);
|
|
||||||
|
|
||||||
FILE *TCFile = NULL;
|
FILE *TCFile = NULL;
|
||||||
const char *Timecodes = Args[3].AsString("");
|
|
||||||
if (strcmp(Timecodes, "")) {
|
if (strcmp(Timecodes, "")) {
|
||||||
TCFile = fopen(Timecodes, "w");
|
TCFile = fopen(Timecodes, "w");
|
||||||
if (!TCFile)
|
if (!TCFile)
|
||||||
|
@ -696,21 +576,40 @@ AVSValue __cdecl CreateFFmpegSource(AVSValue Args, void* UserData, IScriptEnviro
|
||||||
fprintf(TCFile, "# timecode format v2\n");
|
fprintf(TCFile, "# timecode format v2\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FILE *CacheFile = NULL;
|
||||||
|
if (Cache) {
|
||||||
|
CacheFile = fopen(CacheFilename, "a+");
|
||||||
|
if (!CacheFile)
|
||||||
|
Env->ThrowError("FFmpegSource: Failed to open cache file");
|
||||||
|
fseek(CacheFile, 0, SEEK_END);
|
||||||
|
}
|
||||||
|
|
||||||
|
av_register_all();
|
||||||
|
AVFormatContext *FormatContext;
|
||||||
|
|
||||||
|
if (av_open_input_file(&FormatContext, Source, NULL, 0, NULL) != 0)
|
||||||
|
Env->ThrowError("FFmpegSource: Couldn't open \"%s\"", Args[0].AsString());
|
||||||
|
bool IsMatroska = !strcmp(FormatContext->iformat->name, "matroska");
|
||||||
|
av_close_input_file(FormatContext);
|
||||||
|
|
||||||
FFBase *Ret;
|
FFBase *Ret;
|
||||||
|
|
||||||
if (IsMatroska)
|
if (IsMatroska)
|
||||||
Ret = new FFMKVSource(Args[0].AsString(), Args[1].AsInt(-1), TCFile, Env);
|
Ret = new FFMKVSource(Source, Track, TCFile, Cache, CacheFile, Env);
|
||||||
else
|
else
|
||||||
Ret = new FFmpegSource(Args[0].AsString(), Args[1].AsInt(-1), Args[2].AsBool(false), TCFile, Env);
|
Ret = new FFmpegSource(Source, Track, SeekMode, TCFile, Cache, CacheFile, Env);
|
||||||
|
|
||||||
if (TCFile)
|
if (TCFile)
|
||||||
fclose(TCFile);
|
fclose(TCFile);
|
||||||
|
|
||||||
|
if (CacheFile)
|
||||||
|
fclose(CacheFile);
|
||||||
|
|
||||||
return Ret;
|
return Ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* Env) {
|
extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* Env) {
|
||||||
Env->AddFunction("FFmpegSource", "[source]s[track]i[forceseek]b[timecodes]s", CreateFFmpegSource, 0);
|
Env->AddFunction("FFmpegSource", "[source]s[track]i[seekmode]i[timecodes]s[cache]b[cachefile]s", CreateFFmpegSource, 0);
|
||||||
return "FFmpegSource";
|
return "FFmpegSource";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
FFmpegSource Documentation
|
||||||
|
</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>FFmpegSource Documentation</h1>
|
||||||
|
|
||||||
|
<h2>Changes</h2>
|
||||||
|
<ul>
|
||||||
|
|
||||||
|
<li>1.2<ul>
|
||||||
|
<li>Compiled against ffmpeg rev9451</li>
|
||||||
|
<li>Somewhat cleaner source code</li>
|
||||||
|
<li>Linear access in addition to a few other modes of seeking can now be forced</li>
|
||||||
|
<li>Can now save the index information to a file which makes subsequent file opening fast</li>
|
||||||
|
<li>No longer skips indexing for any format</li>
|
||||||
|
</ul></li>
|
||||||
|
|
||||||
|
<li>1.1<ul>
|
||||||
|
<li>Skip indexing for avi</li>
|
||||||
|
<li>Prefix all error messages with the plugin name</li>
|
||||||
|
<li>Can write v2 timecodes to a file</li>
|
||||||
|
<li>Fixed reported framerate</li>
|
||||||
|
</ul></li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Usage</h2>
|
||||||
|
<p>
|
||||||
|
<b>FFmpegSource(string source, int track = -1, int seekmode = 1, string timecodes, bool cache = true, string CacheFile)</b>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<b>source:</b>
|
||||||
|
source file
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<b>track:</b>
|
||||||
|
track number as seen by the relevant demuxer, starts from 0, -1 means will pick the first video track
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<b>seekmode:</b>
|
||||||
|
force how seeking is handled, has no effect on matroska files which always use the equivalent of seekmode=1<br />
|
||||||
|
<b>0:</b> linear access, the definition of slow but should make some formats "usable"<br />
|
||||||
|
<b>1:</b> safe normal, bases seeking decisions on the reported keyframe positions<br />
|
||||||
|
<b>2:</b> unsafe normal, same as 1 but no error will be thrown if the exact destination has to be guessed<br />
|
||||||
|
<b>3:</b> aggressive, seek in the forward direction even if no closer keyframe is known to exist, only useful for containers where avformat doesn't report keyframes properly and testing
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<b>timecodes:</b>
|
||||||
|
file to output timecodes to, if the file exists it will always be overwritten
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<b>cache:</b>
|
||||||
|
write indexing information to a file for later use
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<b>cachefile:</b>
|
||||||
|
if specified the file to store the index information in, if nothing is specified <source>.ffcache is used
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Compatibility</h2>
|
||||||
|
<ul>
|
||||||
|
<li>AVI, MKV, MP4, FLV: Frame accurate</li>
|
||||||
|
<li>OGM: Messed up first frame and seeking produces smearing with seekmode=3, incredibly slow seeking without</li>
|
||||||
|
<li>WMV: No seeking</li>
|
||||||
|
<li>VOB: No rff flags applied, frame accurate?</li>
|
||||||
|
<li>MPG: Seeking seems to be off by one or two frames now and then</li>
|
||||||
|
<li>TS: Anything except seekmode=0 is a complete waste of time</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Compiling</h2>
|
||||||
|
|
||||||
|
<p><b>zlib</b> from http://www.zlib.net/</p>
|
||||||
|
|
||||||
|
<p><b>FFmpeg svn</b> from http://ffmpeg.mplayerhq.hu/</p>
|
||||||
|
|
||||||
|
<p><b>Required Configuration:</b>
|
||||||
|
./configure --enable-shared --disable-static --enable-memalign-hack --enable-swscaler --enable-gpl</p>
|
||||||
|
|
||||||
|
<p><b>Suggested Additional Options:</b>
|
||||||
|
--disable-encoders --disable-muxers</p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,32 +0,0 @@
|
||||||
Changes
|
|
||||||
1.1
|
|
||||||
Skip indexing for avi
|
|
||||||
Prefix all error messages with the plugin name
|
|
||||||
Can write v2 timecodes to a file
|
|
||||||
Fixed reported framerate
|
|
||||||
|
|
||||||
Usage
|
|
||||||
FFmpegSource(string source, int track = -1, bool forceseek = false, string timecodes = "")
|
|
||||||
source: source file
|
|
||||||
track: video track nubmer as seen by the relevant demuxer, must be a video track
|
|
||||||
negative values means the first found video track
|
|
||||||
forceseek: seek even if the format has no registered index, only useful for containers with limited avformat support
|
|
||||||
timecodes: file to output timecodes to, implies indexing the file no matter what type
|
|
||||||
|
|
||||||
Compatibility
|
|
||||||
AVI, MKV, MP4: Frame accurate
|
|
||||||
OGM: Messed up first frame and seeking produces smearing with forceseek=true, incredibly slow seeking without
|
|
||||||
WMV: No seeking
|
|
||||||
VOB: No rff flags, otherwise it appears to work
|
|
||||||
TS: Seeking goes way off, number of total frames is probably off too
|
|
||||||
|
|
||||||
Compiling
|
|
||||||
zlib compiled DLL from http://www.zlib.net/
|
|
||||||
|
|
||||||
ffmpeg svn from http://ffmpeg.mplayerhq.hu/
|
|
||||||
|
|
||||||
required configuration
|
|
||||||
./configure --enable-shared --disable-static --enable-memalign-hack --enable-swscaler --enable-gpl
|
|
||||||
|
|
||||||
suggested additions
|
|
||||||
--disable-encoders --disable-muxers
|
|
|
@ -0,0 +1,206 @@
|
||||||
|
CodecID MatroskaToFFCodecID(TrackInfo *TI) {
|
||||||
|
char *Codec = TI->CodecID;
|
||||||
|
// fourcc list from ffdshow
|
||||||
|
if (!strcmp(Codec, "V_MS/VFW/FOURCC")) {
|
||||||
|
switch (((BITMAPINFOHEADER *)TI->CodecPrivate)->biCompression) {
|
||||||
|
case MAKEFOURCC('F', 'F', 'D', 'S'):
|
||||||
|
case MAKEFOURCC('F', 'V', 'F', 'W'):
|
||||||
|
case MAKEFOURCC('X', 'V', 'I', 'D'):
|
||||||
|
case MAKEFOURCC('D', 'I', 'V', 'X'):
|
||||||
|
case MAKEFOURCC('D', 'X', '5', '0'):
|
||||||
|
case MAKEFOURCC('M', 'P', '4', 'V'):
|
||||||
|
case MAKEFOURCC('3', 'I', 'V', 'X'):
|
||||||
|
case MAKEFOURCC('W', 'V', '1', 'F'):
|
||||||
|
case MAKEFOURCC('F', 'M', 'P', '4'):
|
||||||
|
case MAKEFOURCC('S', 'M', 'P', '4'):
|
||||||
|
return CODEC_ID_MPEG4;
|
||||||
|
case MAKEFOURCC('D', 'I', 'V', '3'):
|
||||||
|
case MAKEFOURCC('D', 'V', 'X', '3'):
|
||||||
|
case MAKEFOURCC('M', 'P', '4', '3'):
|
||||||
|
return CODEC_ID_MSMPEG4V3;
|
||||||
|
case MAKEFOURCC('M', 'P', '4', '2'):
|
||||||
|
return CODEC_ID_MSMPEG4V2;
|
||||||
|
case MAKEFOURCC('M', 'P', '4', '1'):
|
||||||
|
return CODEC_ID_MSMPEG4V1;
|
||||||
|
case MAKEFOURCC('W', 'M', 'V', '1'):
|
||||||
|
return CODEC_ID_WMV1;
|
||||||
|
case MAKEFOURCC('W', 'M', 'V', '2'):
|
||||||
|
return CODEC_ID_WMV2;
|
||||||
|
case MAKEFOURCC('W', 'M', 'V', '3'):
|
||||||
|
return CODEC_ID_WMV3;
|
||||||
|
/*
|
||||||
|
case MAKEFOURCC('M', 'S', 'S', '1'):
|
||||||
|
case MAKEFOURCC('M', 'S', 'S', '2'):
|
||||||
|
case MAKEFOURCC('W', 'V', 'P', '2'):
|
||||||
|
case MAKEFOURCC('W', 'M', 'V', 'P'):
|
||||||
|
return CODEC_ID_WMV9_LIB;
|
||||||
|
*/
|
||||||
|
case MAKEFOURCC('W', 'V', 'C', '1'):
|
||||||
|
return CODEC_ID_VC1;
|
||||||
|
case MAKEFOURCC('V', 'P', '5', '0'):
|
||||||
|
return CODEC_ID_VP5;
|
||||||
|
case MAKEFOURCC('V', 'P', '6', '0'):
|
||||||
|
case MAKEFOURCC('V', 'P', '6', '1'):
|
||||||
|
case MAKEFOURCC('V', 'P', '6', '2'):
|
||||||
|
return CODEC_ID_VP6;
|
||||||
|
case MAKEFOURCC('V', 'P', '6', 'F'):
|
||||||
|
case MAKEFOURCC('F', 'L', 'V', '4'):
|
||||||
|
return CODEC_ID_VP6F;
|
||||||
|
case MAKEFOURCC('C', 'A', 'V', 'S'):
|
||||||
|
return CODEC_ID_CAVS;
|
||||||
|
case MAKEFOURCC('M', 'P', 'G', '1'):
|
||||||
|
case MAKEFOURCC('M', 'P', 'E', 'G'):
|
||||||
|
return CODEC_ID_MPEG2VIDEO; // not a typo
|
||||||
|
case MAKEFOURCC('M', 'P', 'G', '2'):
|
||||||
|
case MAKEFOURCC('E', 'M', '2', 'V'):
|
||||||
|
case MAKEFOURCC('M', 'M', 'E', 'S'):
|
||||||
|
return CODEC_ID_MPEG2VIDEO;
|
||||||
|
case MAKEFOURCC('H', '2', '6', '3'):
|
||||||
|
case MAKEFOURCC('S', '2', '6', '3'):
|
||||||
|
case MAKEFOURCC('L', '2', '6', '3'):
|
||||||
|
case MAKEFOURCC('M', '2', '6', '3'):
|
||||||
|
case MAKEFOURCC('U', '2', '6', '3'):
|
||||||
|
case MAKEFOURCC('X', '2', '6', '3'):
|
||||||
|
return CODEC_ID_H263;
|
||||||
|
case MAKEFOURCC('H', '2', '6', '4'):
|
||||||
|
case MAKEFOURCC('X', '2', '6', '4'):
|
||||||
|
case MAKEFOURCC('V', 'S', 'S', 'H'):
|
||||||
|
case MAKEFOURCC('D', 'A', 'V', 'C'):
|
||||||
|
case MAKEFOURCC('P', 'A', 'V', 'C'):
|
||||||
|
case MAKEFOURCC('A', 'V', 'C', '1'):
|
||||||
|
return CODEC_ID_H264;
|
||||||
|
case MAKEFOURCC('M', 'J', 'P', 'G'):
|
||||||
|
case MAKEFOURCC('L', 'J', 'P', 'G'):
|
||||||
|
case MAKEFOURCC('M', 'J', 'L', 'S'):
|
||||||
|
case MAKEFOURCC('J', 'P', 'E', 'G'): // questionable fourcc?
|
||||||
|
case MAKEFOURCC('A', 'V', 'R', 'N'):
|
||||||
|
case MAKEFOURCC('M', 'J', 'P', 'A'):
|
||||||
|
return CODEC_ID_MJPEG;
|
||||||
|
case MAKEFOURCC('D', 'V', 'S', 'D'):
|
||||||
|
case MAKEFOURCC('D', 'V', '2', '5'):
|
||||||
|
case MAKEFOURCC('D', 'V', '5', '0'):
|
||||||
|
case MAKEFOURCC('C', 'D', 'V', 'C'):
|
||||||
|
case MAKEFOURCC('C', 'D', 'V', '5'):
|
||||||
|
case MAKEFOURCC('D', 'V', 'I', 'S'):
|
||||||
|
case MAKEFOURCC('P', 'D', 'V', 'C'):
|
||||||
|
return CODEC_ID_DVVIDEO;
|
||||||
|
case MAKEFOURCC('H', 'F', 'Y', 'U'):
|
||||||
|
case MAKEFOURCC('F', 'F', 'V', 'H'):
|
||||||
|
return CODEC_ID_HUFFYUV;
|
||||||
|
case MAKEFOURCC('C', 'Y', 'U', 'V'):
|
||||||
|
return CODEC_ID_CYUV;
|
||||||
|
case MAKEFOURCC('A', 'S', 'V', '1'):
|
||||||
|
return CODEC_ID_ASV1;
|
||||||
|
case MAKEFOURCC('A', 'S', 'V', '2'):
|
||||||
|
return CODEC_ID_ASV2;
|
||||||
|
case MAKEFOURCC('V', 'C', 'R', '1'):
|
||||||
|
return CODEC_ID_VCR1;
|
||||||
|
case MAKEFOURCC('T', 'H', 'E', 'O'):
|
||||||
|
return CODEC_ID_THEORA;
|
||||||
|
case MAKEFOURCC('S', 'V', 'Q', '1'):
|
||||||
|
return CODEC_ID_SVQ1;
|
||||||
|
case MAKEFOURCC('S', 'V', 'Q', '3'):
|
||||||
|
return CODEC_ID_SVQ3;
|
||||||
|
case MAKEFOURCC('R', 'P', 'Z', 'A'):
|
||||||
|
return CODEC_ID_RPZA;
|
||||||
|
case MAKEFOURCC('F', 'F', 'V', '1'):
|
||||||
|
return CODEC_ID_FFV1;
|
||||||
|
case MAKEFOURCC('V', 'P', '3', '1'):
|
||||||
|
return CODEC_ID_VP3;
|
||||||
|
case MAKEFOURCC('R', 'L', 'E', '8'):
|
||||||
|
return CODEC_ID_MSRLE;
|
||||||
|
case MAKEFOURCC('M', 'S', 'Z', 'H'):
|
||||||
|
return CODEC_ID_MSZH;
|
||||||
|
case MAKEFOURCC('Z', 'L', 'I', 'B'):
|
||||||
|
return CODEC_ID_FLV1;
|
||||||
|
case MAKEFOURCC('F', 'L', 'V', '1'):
|
||||||
|
return CODEC_ID_ZLIB;
|
||||||
|
/*
|
||||||
|
case MAKEFOURCC('P', 'N', 'G', '1'):
|
||||||
|
return CODEC_ID_COREPNG;
|
||||||
|
*/
|
||||||
|
case MAKEFOURCC('M', 'P', 'N', 'G'):
|
||||||
|
return CODEC_ID_PNG;
|
||||||
|
/*
|
||||||
|
case MAKEFOURCC('A', 'V', 'I', 'S'):
|
||||||
|
return CODEC_ID_AVISYNTH;
|
||||||
|
*/
|
||||||
|
case MAKEFOURCC('C', 'R', 'A', 'M'):
|
||||||
|
return CODEC_ID_MSVIDEO1;
|
||||||
|
case MAKEFOURCC('R', 'T', '2', '1'):
|
||||||
|
return CODEC_ID_INDEO2;
|
||||||
|
case MAKEFOURCC('I', 'V', '3', '2'):
|
||||||
|
case MAKEFOURCC('I', 'V', '3', '1'):
|
||||||
|
return CODEC_ID_INDEO3;
|
||||||
|
case MAKEFOURCC('C', 'V', 'I', 'D'):
|
||||||
|
return CODEC_ID_CINEPAK;
|
||||||
|
case MAKEFOURCC('R', 'V', '1', '0'):
|
||||||
|
return CODEC_ID_RV10;
|
||||||
|
case MAKEFOURCC('R', 'V', '2', '0'):
|
||||||
|
return CODEC_ID_RV20;
|
||||||
|
case MAKEFOURCC('8', 'B', 'P', 'S'):
|
||||||
|
return CODEC_ID_8BPS;
|
||||||
|
case MAKEFOURCC('Q', 'R', 'L', 'E'):
|
||||||
|
return CODEC_ID_QTRLE;
|
||||||
|
case MAKEFOURCC('D', 'U', 'C', 'K'):
|
||||||
|
return CODEC_ID_TRUEMOTION1;
|
||||||
|
case MAKEFOURCC('T', 'M', '2', '0'):
|
||||||
|
return CODEC_ID_TRUEMOTION2;
|
||||||
|
case MAKEFOURCC('T', 'S', 'C', 'C'):
|
||||||
|
return CODEC_ID_TSCC;
|
||||||
|
case MAKEFOURCC('S', 'N', 'O', 'W'):
|
||||||
|
return CODEC_ID_SNOW;
|
||||||
|
case MAKEFOURCC('Q', 'P', 'E', 'G'):
|
||||||
|
case MAKEFOURCC('Q', '1', '_', '0'):
|
||||||
|
case MAKEFOURCC('Q', '1', '_', '1'):
|
||||||
|
return CODEC_ID_QPEG;
|
||||||
|
case MAKEFOURCC('H', '2', '6', '1'):
|
||||||
|
case MAKEFOURCC('M', '2', '6', '1'):
|
||||||
|
return CODEC_ID_H261;
|
||||||
|
case MAKEFOURCC('L', 'O', 'C', 'O'):
|
||||||
|
return CODEC_ID_LOCO;
|
||||||
|
case MAKEFOURCC('W', 'N', 'V', '1'):
|
||||||
|
return CODEC_ID_WNV1;
|
||||||
|
case MAKEFOURCC('C', 'S', 'C', 'D'):
|
||||||
|
return CODEC_ID_CSCD;
|
||||||
|
case MAKEFOURCC('Z', 'M', 'B', 'V'):
|
||||||
|
return CODEC_ID_ZMBV;
|
||||||
|
case MAKEFOURCC('U', 'L', 'T', 'I'):
|
||||||
|
return CODEC_ID_ULTI;
|
||||||
|
case MAKEFOURCC('V', 'I', 'X', 'L'):
|
||||||
|
return CODEC_ID_VIXL;
|
||||||
|
case MAKEFOURCC('A', 'A', 'S', 'C'):
|
||||||
|
return CODEC_ID_AASC;
|
||||||
|
case MAKEFOURCC('F', 'P', 'S', '1'):
|
||||||
|
return CODEC_ID_FRAPS;
|
||||||
|
default:
|
||||||
|
return CODEC_ID_NONE;
|
||||||
|
}
|
||||||
|
} else if (!strcmp(Codec, "V_MPEG4/ISO/AVC"))
|
||||||
|
return CODEC_ID_H264;
|
||||||
|
else if (!strcmp(Codec, "V_MPEG4/ISO/ASP"))
|
||||||
|
return CODEC_ID_MPEG4;
|
||||||
|
else if (!strcmp(Codec, "V_MPEG2"))
|
||||||
|
return CODEC_ID_MPEG2VIDEO;
|
||||||
|
else if (!strcmp(Codec, "V_MPEG1"))
|
||||||
|
return CODEC_ID_MPEG2VIDEO; // still not a typo
|
||||||
|
else if (!strcmp(Codec, "V_SNOW"))
|
||||||
|
return CODEC_ID_SNOW;
|
||||||
|
else if (!strcmp(Codec, "V_THEORA"))
|
||||||
|
return CODEC_ID_THEORA;
|
||||||
|
else if (!strncmp(Codec, "V_REAL/RV", 9)) {
|
||||||
|
switch (Codec[9]) {
|
||||||
|
case '1':
|
||||||
|
return CODEC_ID_RV10;
|
||||||
|
case '2':
|
||||||
|
return CODEC_ID_RV20;
|
||||||
|
case '3':
|
||||||
|
return CODEC_ID_RV30;
|
||||||
|
case '4':
|
||||||
|
return CODEC_ID_RV40;
|
||||||
|
default:
|
||||||
|
return CODEC_ID_NONE;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
return CODEC_ID_NONE;
|
||||||
|
}
|
Loading…
Reference in New Issue