mirror of https://github.com/odrling/Aegisub
parent
67d8412a9a
commit
4b87a7a9ad
|
@ -107,7 +107,7 @@ bool FFBase::SaveTimecodesToFile(const char *ATimecodeFile, int64_t ScaleD, int6
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef FLAC_CACHE
|
||||||
static FLAC__StreamDecoderReadStatus FLACStreamDecoderReadCallback(const FLAC__StreamDecoder *ADecoder, FLAC__byte ABuffer[], size_t *ABytes, FFBase *AOwner) {
|
static FLAC__StreamDecoderReadStatus FLACStreamDecoderReadCallback(const FLAC__StreamDecoder *ADecoder, FLAC__byte ABuffer[], size_t *ABytes, FFBase *AOwner) {
|
||||||
if(*ABytes > 0) {
|
if(*ABytes > 0) {
|
||||||
*ABytes = fread(ABuffer, sizeof(FLAC__byte), *ABytes, AOwner->FCFile);
|
*ABytes = fread(ABuffer, sizeof(FLAC__byte), *ABytes, AOwner->FCFile);
|
||||||
|
@ -182,6 +182,7 @@ static void FLACStreamDecoderMetadataCallback(const FLAC__StreamDecoder *ADecode
|
||||||
static void FLACStreamDecoderErrorCallback(const FLAC__StreamDecoder *ADecoder, FLAC__StreamDecoderErrorStatus AStatus, FFBase *AOwner) {
|
static void FLACStreamDecoderErrorCallback(const FLAC__StreamDecoder *ADecoder, FLAC__StreamDecoderErrorStatus AStatus, FFBase *AOwner) {
|
||||||
AOwner->FCError = true;
|
AOwner->FCError = true;
|
||||||
}
|
}
|
||||||
|
#endif // FLAC_CACHE
|
||||||
|
|
||||||
bool FFBase::OpenAudioCache(const char *AAudioCacheFile, const char *ASource, int AAudioTrack, IScriptEnvironment *Env) {
|
bool FFBase::OpenAudioCache(const char *AAudioCacheFile, const char *ASource, int AAudioTrack, IScriptEnvironment *Env) {
|
||||||
char DefaultCacheFilename[1024];
|
char DefaultCacheFilename[1024];
|
||||||
|
@ -205,7 +206,8 @@ bool FFBase::OpenAudioCache(const char *AAudioCacheFile, const char *ASource, in
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If FLAC?
|
#ifdef FLAC_CACHE
|
||||||
|
// is FLAC?
|
||||||
FLACAudioCache = FLAC__stream_decoder_new();
|
FLACAudioCache = FLAC__stream_decoder_new();
|
||||||
if (FLAC__stream_decoder_init_stream(FLACAudioCache,
|
if (FLAC__stream_decoder_init_stream(FLACAudioCache,
|
||||||
&(FLAC__StreamDecoderReadCallback)FLACStreamDecoderReadCallback,
|
&(FLAC__StreamDecoderReadCallback)FLACStreamDecoderReadCallback,
|
||||||
|
@ -225,6 +227,7 @@ bool FFBase::OpenAudioCache(const char *AAudioCacheFile, const char *ASource, in
|
||||||
}
|
}
|
||||||
FLAC__stream_decoder_delete(FLACAudioCache);
|
FLAC__stream_decoder_delete(FLACAudioCache);
|
||||||
FLACAudioCache = NULL;
|
FLACAudioCache = NULL;
|
||||||
|
#endif // FLAC_CACHE
|
||||||
|
|
||||||
// Raw audio
|
// Raw audio
|
||||||
VI.num_audio_samples = VI.AudioSamplesFromBytes(CacheSize);
|
VI.num_audio_samples = VI.AudioSamplesFromBytes(CacheSize);
|
||||||
|
@ -234,6 +237,7 @@ bool FFBase::OpenAudioCache(const char *AAudioCacheFile, const char *ASource, in
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef FLAC_CACHE
|
||||||
static FLAC__StreamEncoderWriteStatus FLACStreamEncoderWriteCallback(const FLAC__StreamEncoder *ASncoder, const FLAC__byte ABuffer[], size_t ABytes, unsigned ABamples, unsigned ACurrentFrame, FFBase *AOwner) {
|
static FLAC__StreamEncoderWriteStatus FLACStreamEncoderWriteCallback(const FLAC__StreamEncoder *ASncoder, const FLAC__byte ABuffer[], size_t ABytes, unsigned ABamples, unsigned ACurrentFrame, FFBase *AOwner) {
|
||||||
fwrite(ABuffer, sizeof(FLAC__byte), ABytes, AOwner->FCFile);
|
fwrite(ABuffer, sizeof(FLAC__byte), ABytes, AOwner->FCFile);
|
||||||
if(ferror(AOwner->FCFile))
|
if(ferror(AOwner->FCFile))
|
||||||
|
@ -289,6 +293,7 @@ void FFBase::CloseFLACCacheWriter(FLAC__StreamEncoder *AFSE) {
|
||||||
fclose(FCFile);
|
fclose(FCFile);
|
||||||
FCFile = NULL;
|
FCFile = NULL;
|
||||||
}
|
}
|
||||||
|
#endif // FLAC_CACHE
|
||||||
|
|
||||||
FILE *FFBase::NewRawCacheWriter(const char *AAudioCacheFile, const char *ASource, int AAudioTrack, IScriptEnvironment *Env) {
|
FILE *FFBase::NewRawCacheWriter(const char *AAudioCacheFile, const char *ASource, int AAudioTrack, IScriptEnvironment *Env) {
|
||||||
char DefaultCacheFilename[1024];
|
char DefaultCacheFilename[1024];
|
||||||
|
@ -392,16 +397,18 @@ PVideoFrame FFBase::OutputFrame(AVFrame *AFrame, IScriptEnvironment *Env) {
|
||||||
return Dst;
|
return Dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
void __stdcall FFBase::GetAudio(void* Buf, __int64 Start, __int64 Count, IScriptEnvironment* Env) {
|
void FFBase::GetAudio(void* Buf, __int64 Start, __int64 Count, IScriptEnvironment* Env) {
|
||||||
if (AudioCacheType == acRaw) {
|
if (AudioCacheType == acRaw) {
|
||||||
_fseeki64(RawAudioCache, VI.BytesFromAudioSamples(Start), SEEK_SET);
|
_fseeki64(RawAudioCache, VI.BytesFromAudioSamples(Start), SEEK_SET);
|
||||||
fread(Buf, 1, VI.BytesFromAudioSamples(Count), RawAudioCache);
|
fread(Buf, 1, VI.BytesFromAudioSamples(Count), RawAudioCache);
|
||||||
|
#ifdef FLAC_CACHE
|
||||||
} else if (AudioCacheType == acFLAC) {
|
} else if (AudioCacheType == acFLAC) {
|
||||||
FCCount = Count;
|
FCCount = Count;
|
||||||
FCBuffer = Buf;
|
FCBuffer = Buf;
|
||||||
FLAC__stream_decoder_seek_absolute(FLACAudioCache, Start);
|
FLAC__stream_decoder_seek_absolute(FLACAudioCache, Start);
|
||||||
while (FCCount > 0)
|
while (FCCount > 0)
|
||||||
FLAC__stream_decoder_process_single(FLACAudioCache);
|
FLAC__stream_decoder_process_single(FLACAudioCache);
|
||||||
|
#endif // FLAC_CACHE
|
||||||
} else {
|
} else {
|
||||||
Env->ThrowError("FFmpegSource: Audio requested but none available");
|
Env->ThrowError("FFmpegSource: Audio requested but none available");
|
||||||
}
|
}
|
||||||
|
@ -412,12 +419,14 @@ FFBase::FFBase() {
|
||||||
AudioCacheType = acNone;
|
AudioCacheType = acNone;
|
||||||
FCError = false;
|
FCError = false;
|
||||||
RawAudioCache = NULL;
|
RawAudioCache = NULL;
|
||||||
FLACAudioCache = NULL;
|
|
||||||
PPContext = NULL;
|
PPContext = NULL;
|
||||||
PPMode = NULL;
|
PPMode = NULL;
|
||||||
SWS = NULL;
|
SWS = NULL;
|
||||||
DecodingBuffer = new uint8_t[AVCODEC_MAX_AUDIO_FRAME_SIZE];
|
DecodingBuffer = new uint8_t[AVCODEC_MAX_AUDIO_FRAME_SIZE];
|
||||||
|
#ifdef FLAC_CACHE
|
||||||
|
FLACAudioCache = NULL;
|
||||||
FLACBuffer = new FLAC__int32[AVCODEC_MAX_AUDIO_FRAME_SIZE];
|
FLACBuffer = new FLAC__int32[AVCODEC_MAX_AUDIO_FRAME_SIZE];
|
||||||
|
#endif // FLAC_CACHE
|
||||||
FCFile = NULL;
|
FCFile = NULL;
|
||||||
ConvertToFormat = PIX_FMT_NONE;
|
ConvertToFormat = PIX_FMT_NONE;
|
||||||
memset(&PPPicture, 0, sizeof(PPPicture));
|
memset(&PPPicture, 0, sizeof(PPPicture));
|
||||||
|
@ -426,13 +435,15 @@ FFBase::FFBase() {
|
||||||
|
|
||||||
FFBase::~FFBase() {
|
FFBase::~FFBase() {
|
||||||
delete [] DecodingBuffer;
|
delete [] DecodingBuffer;
|
||||||
delete [] FLACBuffer;
|
|
||||||
if (RawAudioCache)
|
if (RawAudioCache)
|
||||||
fclose(RawAudioCache);
|
fclose(RawAudioCache);
|
||||||
|
#ifdef FLAC_CACHE
|
||||||
|
delete [] FLACBuffer;
|
||||||
if (FLACAudioCache) {
|
if (FLACAudioCache) {
|
||||||
FLAC__stream_decoder_finish(FLACAudioCache);
|
FLAC__stream_decoder_finish(FLACAudioCache);
|
||||||
FLAC__stream_decoder_delete(FLACAudioCache);
|
FLAC__stream_decoder_delete(FLACAudioCache);
|
||||||
}
|
}
|
||||||
|
#endif // FLAC_CACHE
|
||||||
if (FCFile)
|
if (FCFile)
|
||||||
fclose(FCFile);
|
fclose(FCFile);
|
||||||
if (SWS)
|
if (SWS)
|
||||||
|
|
|
@ -19,165 +19,208 @@
|
||||||
// THE SOFTWARE.
|
// THE SOFTWARE.
|
||||||
|
|
||||||
#include "ffmpegsource.h"
|
#include "ffmpegsource.h"
|
||||||
#include "stdiostream.c"
|
|
||||||
|
|
||||||
class FFMatroskaSource : public FFBase {
|
int FFMatroskaSource::GetTrackIndex(int Index, unsigned char ATrackType, IScriptEnvironment *Env) {
|
||||||
private:
|
if (Index == -1)
|
||||||
StdIoStream ST;
|
for (unsigned int i = 0; i < mkv_GetNumTracks(MF); i++)
|
||||||
unsigned int BufferSize;
|
if (mkv_GetTrackInfo(MF, i)->Type == ATrackType) {
|
||||||
CompressedStream *VideoCS;
|
Index = i;
|
||||||
CompressedStream *AudioCS;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
AVCodecContext *VideoCodecContext;
|
if (Index == -1)
|
||||||
|
Env->ThrowError("FFmpegSource: No %s track found", (ATrackType & TT_VIDEO) ? "video" : "audio");
|
||||||
|
if (Index <= -2)
|
||||||
|
return -2;
|
||||||
|
|
||||||
MatroskaFile *MF;
|
if (Index >= (int)mkv_GetNumTracks(MF))
|
||||||
char ErrorMessage[256];
|
Env->ThrowError("FFmpegSource: Invalid %s track number", (ATrackType & TT_VIDEO) ? "video" : "audio");
|
||||||
uint8_t *Buffer;
|
|
||||||
|
|
||||||
int CurrentFrame;
|
TrackInfo *TI = mkv_GetTrackInfo(MF, Index);
|
||||||
|
|
||||||
int ReadFrame(uint64_t AFilePos, unsigned int AFrameSize, CompressedStream *ACS, IScriptEnvironment *Env);
|
if (TI->Type != ATrackType)
|
||||||
int DecodeNextFrame(AVFrame *AFrame, int64_t *AFirstStartTime, IScriptEnvironment* Env);
|
Env->ThrowError("FFmpegSource: Selected track is not %s", (ATrackType & TT_VIDEO) ? "video" : "audio");
|
||||||
|
|
||||||
int GetTrackIndex(int Index, unsigned char ATrackType, IScriptEnvironment *Env) {
|
return Index;
|
||||||
if (Index == -1)
|
}
|
||||||
for (unsigned int i = 0; i < mkv_GetNumTracks(MF); i++)
|
|
||||||
if (mkv_GetTrackInfo(MF, i)->Type == ATrackType) {
|
|
||||||
Index = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Index == -1)
|
FFMatroskaSource::FFMatroskaSource(const char *ASource, int AVideoTrack, int AAudioTrack, const char *ATimecodes, bool AVCache, const char *AVideoCache, const char *AAudioCache, int AACCompression, const char *APPString, int AQuality, IScriptEnvironment* Env) {
|
||||||
Env->ThrowError("FFmpegSource: No %s track found", (ATrackType & TT_VIDEO) ? "video" : "audio");
|
CurrentFrame = 0;
|
||||||
if (Index <= -2)
|
int VideoTrack;
|
||||||
return -2;
|
int AudioTrack;
|
||||||
|
unsigned int TrackMask = ~0;
|
||||||
|
AVCodecContext *AudioCodecContext = NULL;
|
||||||
|
AVCodec *AudioCodec = NULL;
|
||||||
|
VideoCodecContext = NULL;
|
||||||
|
AVCodec *VideoCodec = NULL;
|
||||||
|
TrackInfo *VideoTI = NULL;
|
||||||
|
BufferSize = 0;
|
||||||
|
Buffer = NULL;
|
||||||
|
VideoCS = NULL;
|
||||||
|
AudioCS = NULL;
|
||||||
|
|
||||||
if (Index >= (int)mkv_GetNumTracks(MF))
|
memset(&ST,0,sizeof(ST));
|
||||||
Env->ThrowError("FFmpegSource: Invalid %s track number", (ATrackType & TT_VIDEO) ? "video" : "audio");
|
ST.base.read = (int (__cdecl *)(InputStream *,ulonglong,void *,int))StdIoRead;
|
||||||
|
ST.base.scan = (longlong (__cdecl *)(InputStream *,ulonglong,unsigned int))StdIoScan;
|
||||||
|
ST.base.getcachesize = (unsigned int (__cdecl *)(InputStream *))StdIoGetCacheSize;
|
||||||
|
ST.base.geterror = (const char *(__cdecl *)(InputStream *))StdIoGetLastError;
|
||||||
|
ST.base.memalloc = (void *(__cdecl *)(InputStream *,size_t))StdIoMalloc;
|
||||||
|
ST.base.memrealloc = (void *(__cdecl *)(InputStream *,void *,size_t))StdIoRealloc;
|
||||||
|
ST.base.memfree = (void (__cdecl *)(InputStream *,void *)) StdIoFree;
|
||||||
|
ST.base.progress = (int (__cdecl *)(InputStream *,ulonglong,ulonglong))StdIoProgress;
|
||||||
|
|
||||||
TrackInfo *TI = mkv_GetTrackInfo(MF, Index);
|
ST.fp = fopen(ASource, "rb");
|
||||||
|
if (ST.fp == NULL)
|
||||||
|
Env->ThrowError("FFmpegSource: Can't open '%s': %s", ASource, strerror(errno));
|
||||||
|
|
||||||
if (TI->Type != ATrackType)
|
setvbuf(ST.fp, NULL, _IOFBF, CACHESIZE);
|
||||||
Env->ThrowError("FFmpegSource: Selected track is not %s", (ATrackType & TT_VIDEO) ? "video" : "audio");
|
|
||||||
|
|
||||||
return Index;
|
MF = mkv_OpenEx(&ST.base, 0, 0, ErrorMessage, sizeof(ErrorMessage));
|
||||||
|
if (MF == NULL) {
|
||||||
|
fclose(ST.fp);
|
||||||
|
Env->ThrowError("FFmpegSource: Can't parse Matroska file: %s", ErrorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
VideoTrack = GetTrackIndex(AVideoTrack, TT_VIDEO, Env);
|
||||||
FFMatroskaSource(const char *ASource, int AVideoTrack, int AAudioTrack, const char *ATimecodes, bool AVCache, const char *AVideoCache, const char *AAudioCache, int AACCompression, const char *APPString, int AQuality, IScriptEnvironment* Env) {
|
AudioTrack = GetTrackIndex(AAudioTrack, TT_AUDIO, Env);
|
||||||
CurrentFrame = 0;
|
|
||||||
int VideoTrack;
|
|
||||||
int AudioTrack;
|
|
||||||
unsigned int TrackMask = ~0;
|
|
||||||
AVCodecContext *AudioCodecContext = NULL;
|
|
||||||
AVCodec *AudioCodec = NULL;
|
|
||||||
VideoCodecContext = NULL;
|
|
||||||
AVCodec *VideoCodec = NULL;
|
|
||||||
TrackInfo *VideoTI = NULL;
|
|
||||||
BufferSize = 0;
|
|
||||||
Buffer = NULL;
|
|
||||||
VideoCS = NULL;
|
|
||||||
AudioCS = NULL;
|
|
||||||
|
|
||||||
memset(&ST,0,sizeof(ST));
|
bool VCacheIsValid = true;
|
||||||
ST.base.read = (int (__cdecl *)(InputStream *,ulonglong,void *,int))StdIoRead;
|
bool ACacheIsValid = true;
|
||||||
ST.base.scan = (longlong (__cdecl *)(InputStream *,ulonglong,unsigned int))StdIoScan;
|
|
||||||
ST.base.getcachesize = (unsigned int (__cdecl *)(InputStream *))StdIoGetCacheSize;
|
|
||||||
ST.base.geterror = (const char *(__cdecl *)(InputStream *))StdIoGetLastError;
|
|
||||||
ST.base.memalloc = (void *(__cdecl *)(InputStream *,size_t))StdIoMalloc;
|
|
||||||
ST.base.memrealloc = (void *(__cdecl *)(InputStream *,void *,size_t))StdIoRealloc;
|
|
||||||
ST.base.memfree = (void (__cdecl *)(InputStream *,void *)) StdIoFree;
|
|
||||||
ST.base.progress = (int (__cdecl *)(InputStream *,ulonglong,ulonglong))StdIoProgress;
|
|
||||||
|
|
||||||
ST.fp = fopen(ASource, "rb");
|
if (VideoTrack >= 0) {
|
||||||
if (ST.fp == NULL)
|
VCacheIsValid = LoadFrameInfoFromFile(AVideoCache, ASource, VideoTrack);
|
||||||
Env->ThrowError("FFmpegSource: Can't open '%s': %s", ASource, strerror(errno));
|
|
||||||
|
|
||||||
setvbuf(ST.fp, NULL, _IOFBF, CACHESIZE);
|
VideoTI = mkv_GetTrackInfo(MF, VideoTrack);
|
||||||
|
|
||||||
MF = mkv_OpenEx(&ST.base, 0, 0, ErrorMessage, sizeof(ErrorMessage));
|
if (VideoTI->CompEnabled) {
|
||||||
if (MF == NULL) {
|
VideoCS = cs_Create(MF, VideoTrack, ErrorMessage, sizeof(ErrorMessage));
|
||||||
fclose(ST.fp);
|
if (VideoCS == NULL)
|
||||||
Env->ThrowError("FFmpegSource: Can't parse Matroska file: %s", ErrorMessage);
|
Env->ThrowError("FFmpegSource: Can't create decompressor: %s", ErrorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoTrack = GetTrackIndex(AVideoTrack, TT_VIDEO, Env);
|
VideoCodecContext = avcodec_alloc_context();
|
||||||
AudioTrack = GetTrackIndex(AAudioTrack, TT_AUDIO, Env);
|
VideoCodecContext->extradata = (uint8_t *)VideoTI->CodecPrivate;
|
||||||
|
VideoCodecContext->extradata_size = VideoTI->CodecPrivateSize;
|
||||||
|
|
||||||
bool VCacheIsValid = true;
|
VideoCodec = avcodec_find_decoder(MatroskaToFFCodecID(VideoTI));
|
||||||
bool ACacheIsValid = true;
|
if (VideoCodec == NULL)
|
||||||
|
Env->ThrowError("FFmpegSource: Video codec not found");
|
||||||
|
|
||||||
if (VideoTrack >= 0) {
|
if (avcodec_open(VideoCodecContext, VideoCodec) < 0)
|
||||||
VCacheIsValid = LoadFrameInfoFromFile(AVideoCache, ASource, VideoTrack);
|
Env->ThrowError("FFmpegSource: Could not open video codec");
|
||||||
|
|
||||||
VideoTI = mkv_GetTrackInfo(MF, VideoTrack);
|
// Fix for mpeg2 and other formats where decoding a frame is necessary to get information about the stream
|
||||||
|
if (VideoCodecContext->pix_fmt == PIX_FMT_NONE) {
|
||||||
if (VideoTI->CompEnabled) {
|
mkv_SetTrackMask(MF, ~(1 << VideoTrack));
|
||||||
VideoCS = cs_Create(MF, VideoTrack, ErrorMessage, sizeof(ErrorMessage));
|
int64_t Dummy;
|
||||||
if (VideoCS == NULL)
|
DecodeNextFrame(DecodeFrame, &Dummy, Env);
|
||||||
Env->ThrowError("FFmpegSource: Can't create decompressor: %s", ErrorMessage);
|
mkv_Seek(MF, 0, MKVF_SEEK_TO_PREV_KEYFRAME);
|
||||||
}
|
|
||||||
|
|
||||||
VideoCodecContext = avcodec_alloc_context();
|
|
||||||
VideoCodecContext->extradata = (uint8_t *)VideoTI->CodecPrivate;
|
|
||||||
VideoCodecContext->extradata_size = VideoTI->CodecPrivateSize;
|
|
||||||
|
|
||||||
VideoCodec = avcodec_find_decoder(MatroskaToFFCodecID(VideoTI));
|
|
||||||
if (VideoCodec == NULL)
|
|
||||||
Env->ThrowError("FFmpegSource: Video codec not found");
|
|
||||||
|
|
||||||
if (avcodec_open(VideoCodecContext, VideoCodec) < 0)
|
|
||||||
Env->ThrowError("FFmpegSource: Could not open video codec");
|
|
||||||
|
|
||||||
// Fix for mpeg2 and other formats where decoding a frame is necessary to get information about the stream
|
|
||||||
if (VideoCodecContext->pix_fmt == PIX_FMT_NONE) {
|
|
||||||
mkv_SetTrackMask(MF, ~(1 << VideoTrack));
|
|
||||||
int64_t Dummy;
|
|
||||||
DecodeNextFrame(DecodeFrame, &Dummy, Env);
|
|
||||||
mkv_Seek(MF, 0, MKVF_SEEK_TO_PREV_KEYFRAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
VI.image_type = VideoInfo::IT_TFF;
|
|
||||||
VI.width = VideoTI->AV.Video.PixelWidth;
|
|
||||||
VI.height = VideoTI->AV.Video.PixelHeight;
|
|
||||||
VI.fps_denominator = 1;
|
|
||||||
VI.fps_numerator = 30;
|
|
||||||
|
|
||||||
SetOutputFormat(VideoCodecContext->pix_fmt, Env);
|
|
||||||
InitPP(VI.width, VI.height, APPString, AQuality, VideoCodecContext->pix_fmt, Env);
|
|
||||||
|
|
||||||
if (!VCacheIsValid)
|
|
||||||
TrackMask &= ~(1 << VideoTrack);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AudioTrack >= 0) {
|
VI.image_type = VideoInfo::IT_TFF;
|
||||||
TrackInfo *AudioTI = mkv_GetTrackInfo(MF, AudioTrack);
|
VI.width = VideoTI->AV.Video.PixelWidth;
|
||||||
|
VI.height = VideoTI->AV.Video.PixelHeight;
|
||||||
|
VI.fps_denominator = 1;
|
||||||
|
VI.fps_numerator = 30;
|
||||||
|
|
||||||
if (AudioTI->CompEnabled) {
|
SetOutputFormat(VideoCodecContext->pix_fmt, Env);
|
||||||
AudioCS = cs_Create(MF, AudioTrack, ErrorMessage, sizeof(ErrorMessage));
|
InitPP(VI.width, VI.height, APPString, AQuality, VideoCodecContext->pix_fmt, Env);
|
||||||
if (AudioCS == NULL)
|
|
||||||
Env->ThrowError("FFmpegSource: Can't create decompressor: %s", ErrorMessage);
|
if (!VCacheIsValid)
|
||||||
|
TrackMask &= ~(1 << VideoTrack);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AudioTrack >= 0) {
|
||||||
|
TrackInfo *AudioTI = mkv_GetTrackInfo(MF, AudioTrack);
|
||||||
|
|
||||||
|
if (AudioTI->CompEnabled) {
|
||||||
|
AudioCS = cs_Create(MF, AudioTrack, ErrorMessage, sizeof(ErrorMessage));
|
||||||
|
if (AudioCS == NULL)
|
||||||
|
Env->ThrowError("FFmpegSource: Can't create decompressor: %s", ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioCodecContext = avcodec_alloc_context();
|
||||||
|
AudioCodecContext->extradata = (uint8_t *)AudioTI->CodecPrivate;
|
||||||
|
AudioCodecContext->extradata_size = AudioTI->CodecPrivateSize;
|
||||||
|
|
||||||
|
AudioCodec = avcodec_find_decoder(MatroskaToFFCodecID(AudioTI));
|
||||||
|
if (AudioCodec == NULL)
|
||||||
|
Env->ThrowError("FFmpegSource: Audio codec not found");
|
||||||
|
|
||||||
|
if (avcodec_open(AudioCodecContext, AudioCodec) < 0)
|
||||||
|
Env->ThrowError("FFmpegSource: Could not open audio codec");
|
||||||
|
|
||||||
|
// Fix for ac3 and other codecs where decoding a block of audio is required to get information about it
|
||||||
|
if (AudioCodecContext->channels == 0 || AudioCodecContext->sample_rate == 0) {
|
||||||
|
mkv_SetTrackMask(MF, ~(1 << AudioTrack));
|
||||||
|
uint64_t StartTime, EndTime, FilePos;
|
||||||
|
unsigned int Track, FrameFlags, FrameSize;
|
||||||
|
mkv_ReadFrame(MF, 0, &Track, &StartTime, &EndTime, &FilePos, &FrameSize, &FrameFlags);
|
||||||
|
|
||||||
|
uint8_t DecodingBuffer[AVCODEC_MAX_AUDIO_FRAME_SIZE];
|
||||||
|
int Size = ReadFrame(FilePos, FrameSize, AudioCS, Env);
|
||||||
|
uint8_t *Data = Buffer;
|
||||||
|
|
||||||
|
while (Size > 0) {
|
||||||
|
int TempOutputBufSize = AVCODEC_MAX_AUDIO_FRAME_SIZE;
|
||||||
|
int Ret = avcodec_decode_audio2(AudioCodecContext, (int16_t *)DecodingBuffer, &TempOutputBufSize, Data, Size);
|
||||||
|
if (Ret < 0)
|
||||||
|
Env->ThrowError("FFmpegSource: Audio decoding error");
|
||||||
|
|
||||||
|
Size -= Ret;
|
||||||
|
Data += Ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioCodecContext = avcodec_alloc_context();
|
mkv_Seek(MF, 0, MKVF_SEEK_TO_PREV_KEYFRAME);
|
||||||
AudioCodecContext->extradata = (uint8_t *)AudioTI->CodecPrivate;
|
}
|
||||||
AudioCodecContext->extradata_size = AudioTI->CodecPrivateSize;
|
|
||||||
|
|
||||||
AudioCodec = avcodec_find_decoder(MatroskaToFFCodecID(AudioTI));
|
VI.nchannels = AudioCodecContext->channels;
|
||||||
if (AudioCodec == NULL)
|
VI.audio_samples_per_second = AudioCodecContext->sample_rate;
|
||||||
Env->ThrowError("FFmpegSource: Audio codec not found");
|
|
||||||
|
|
||||||
if (avcodec_open(AudioCodecContext, AudioCodec) < 0)
|
switch (AudioCodecContext->sample_fmt) {
|
||||||
Env->ThrowError("FFmpegSource: Could not open audio codec");
|
case SAMPLE_FMT_U8: VI.sample_type = SAMPLE_INT8; AACCompression = -1; break;
|
||||||
|
case SAMPLE_FMT_S16: VI.sample_type = SAMPLE_INT16; break;
|
||||||
|
case SAMPLE_FMT_S24: VI.sample_type = SAMPLE_INT24; AACCompression = -1; break;
|
||||||
|
case SAMPLE_FMT_S32: VI.sample_type = SAMPLE_INT32; AACCompression = -1; break;
|
||||||
|
case SAMPLE_FMT_FLT: VI.sample_type = SAMPLE_FLOAT; AACCompression = -1; break;
|
||||||
|
default:
|
||||||
|
Env->ThrowError("FFmpegSource: Unsupported/unknown sample format");
|
||||||
|
}
|
||||||
|
|
||||||
// Fix for ac3 and other codecs where decoding a block of audio is required to get information about it
|
ACacheIsValid = OpenAudioCache(AAudioCache, ASource, AudioTrack, Env);
|
||||||
if (AudioCodecContext->channels == 0 || AudioCodecContext->sample_rate == 0) {
|
if (!ACacheIsValid)
|
||||||
mkv_SetTrackMask(MF, ~(1 << AudioTrack));
|
TrackMask &= ~(1 << AudioTrack);
|
||||||
uint64_t StartTime, EndTime, FilePos;
|
}
|
||||||
unsigned int Track, FrameFlags, FrameSize;
|
|
||||||
mkv_ReadFrame(MF, 0, &Track, &StartTime, &EndTime, &FilePos, &FrameSize, &FrameFlags);
|
|
||||||
|
|
||||||
uint8_t DecodingBuffer[AVCODEC_MAX_AUDIO_FRAME_SIZE];
|
mkv_SetTrackMask(MF, TrackMask);
|
||||||
|
|
||||||
|
// Needs to be indexed?
|
||||||
|
if (!ACacheIsValid || !VCacheIsValid) {
|
||||||
|
#ifdef FLAC_CACHE
|
||||||
|
FLAC__StreamEncoder *FSE = NULL;
|
||||||
|
#endif // FLAC_CACHE
|
||||||
|
FILE *RawCache = NULL;
|
||||||
|
if (!ACacheIsValid)
|
||||||
|
if (AACCompression >= 0)
|
||||||
|
AudioCacheType = acFLAC;
|
||||||
|
else
|
||||||
|
AudioCacheType = acRaw;
|
||||||
|
|
||||||
|
switch (AudioCacheType) {
|
||||||
|
#ifdef FLAC_CACHE
|
||||||
|
case acFLAC: FSE = NewFLACCacheWriter(AAudioCache, ASource, AudioTrack, AACCompression, Env); break;
|
||||||
|
#endif // FLAC_CACHE
|
||||||
|
case acRaw: RawCache = NewRawCacheWriter(AAudioCache, ASource, AudioTrack, Env); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t StartTime, EndTime, FilePos;
|
||||||
|
unsigned int Track, FrameFlags, FrameSize;
|
||||||
|
|
||||||
|
while (mkv_ReadFrame(MF, 0, &Track, &StartTime, &EndTime, &FilePos, &FrameSize, &FrameFlags) == 0)
|
||||||
|
if (Track == VideoTrack && !VCacheIsValid) {
|
||||||
|
FrameToDTS.push_back(FrameInfo(StartTime, (FrameFlags & FRAME_KF) != 0));
|
||||||
|
VI.num_frames++;
|
||||||
|
} else if (Track == AudioTrack && !ACacheIsValid) {
|
||||||
int Size = ReadFrame(FilePos, FrameSize, AudioCS, Env);
|
int Size = ReadFrame(FilePos, FrameSize, AudioCS, Env);
|
||||||
uint8_t *Data = Buffer;
|
uint8_t *Data = Buffer;
|
||||||
|
|
||||||
|
@ -187,138 +230,79 @@ public:
|
||||||
if (Ret < 0)
|
if (Ret < 0)
|
||||||
Env->ThrowError("FFmpegSource: Audio decoding error");
|
Env->ThrowError("FFmpegSource: Audio decoding error");
|
||||||
|
|
||||||
|
int DecodedSamples = VI.AudioSamplesFromBytes(TempOutputBufSize);
|
||||||
|
|
||||||
Size -= Ret;
|
Size -= Ret;
|
||||||
Data += Ret;
|
Data += Ret;
|
||||||
}
|
VI.num_audio_samples += DecodedSamples;
|
||||||
|
|
||||||
mkv_Seek(MF, 0, MKVF_SEEK_TO_PREV_KEYFRAME);
|
if (AudioCacheType == acRaw) {
|
||||||
|
fwrite(DecodingBuffer, 1, TempOutputBufSize, RawCache);
|
||||||
|
#ifdef FLAC_CACHE
|
||||||
|
} else if (AudioCacheType == acFLAC) {
|
||||||
|
for (int i = 0; i < DecodedSamples * VI.nchannels; i++)
|
||||||
|
FLACBuffer[i] = ((int16_t *)DecodingBuffer)[i];
|
||||||
|
FLAC__stream_encoder_process_interleaved(FSE, FLACBuffer, DecodedSamples);
|
||||||
|
#endif // FLAC_CACHE
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VI.nchannels = AudioCodecContext->channels;
|
if (!ACacheIsValid) {
|
||||||
VI.audio_samples_per_second = AudioCodecContext->sample_rate;
|
switch (AudioCacheType) {
|
||||||
|
#ifdef FLAC_CACHE
|
||||||
switch (AudioCodecContext->sample_fmt) {
|
case acFLAC: CloseFLACCacheWriter(FSE); break;
|
||||||
case SAMPLE_FMT_U8: VI.sample_type = SAMPLE_INT8; AACCompression = -1; break;
|
#endif // FLAC_CACHE
|
||||||
case SAMPLE_FMT_S16: VI.sample_type = SAMPLE_INT16; break;
|
case acRaw: CloseRawCacheWriter(RawCache); break;
|
||||||
case SAMPLE_FMT_S24: VI.sample_type = SAMPLE_INT24; AACCompression = -1; break;
|
|
||||||
case SAMPLE_FMT_S32: VI.sample_type = SAMPLE_INT32; AACCompression = -1; break;
|
|
||||||
case SAMPLE_FMT_FLT: VI.sample_type = SAMPLE_FLOAT; AACCompression = -1; break;
|
|
||||||
default:
|
|
||||||
Env->ThrowError("FFmpegSource: Unsupported/unknown sample format");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ACacheIsValid = OpenAudioCache(AAudioCache, ASource, AudioTrack, Env);
|
ACacheIsValid = OpenAudioCache(AAudioCache, ASource, AudioTrack, Env);
|
||||||
if (!ACacheIsValid)
|
if (!ACacheIsValid)
|
||||||
TrackMask &= ~(1 << AudioTrack);
|
Env->ThrowError("FFmpegSource: Failed to open newly created audio cache for reading");
|
||||||
}
|
}
|
||||||
|
|
||||||
mkv_SetTrackMask(MF, TrackMask);
|
if (VideoTrack >= 0 && VI.num_frames == 0)
|
||||||
|
Env->ThrowError("FFmpegSource: Video track contains no frames");
|
||||||
|
|
||||||
// Needs to be indexed?
|
if (AudioTrack >= 0 && VI.num_audio_samples == 0)
|
||||||
if (!ACacheIsValid || !VCacheIsValid) {
|
Env->ThrowError("FFmpegSource: Audio track contains no samples");
|
||||||
|
|
||||||
FLAC__StreamEncoder *FSE = NULL;
|
if (VideoTrack >= 0)
|
||||||
FILE *RawCache = NULL;
|
mkv_Seek(MF, FrameToDTS.front().DTS, MKVF_SEEK_TO_PREV_KEYFRAME);
|
||||||
if (!ACacheIsValid)
|
|
||||||
if (AACCompression >= 0)
|
|
||||||
AudioCacheType = acFLAC;
|
|
||||||
else
|
|
||||||
AudioCacheType = acRaw;
|
|
||||||
|
|
||||||
switch (AudioCacheType) {
|
if (AVCache && !VCacheIsValid)
|
||||||
case acFLAC: FSE = NewFLACCacheWriter(AAudioCache, ASource, AudioTrack, AACCompression, Env); break;
|
if (!SaveFrameInfoToFile(AVideoCache, ASource, VideoTrack))
|
||||||
case acRaw: RawCache = NewRawCacheWriter(AAudioCache, ASource, AudioTrack, Env); break;
|
Env->ThrowError("FFmpegSource: Failed to write video cache info");
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t StartTime, EndTime, FilePos;
|
|
||||||
unsigned int Track, FrameFlags, FrameSize;
|
|
||||||
|
|
||||||
while (mkv_ReadFrame(MF, 0, &Track, &StartTime, &EndTime, &FilePos, &FrameSize, &FrameFlags) == 0)
|
|
||||||
if (Track == VideoTrack && !VCacheIsValid) {
|
|
||||||
FrameToDTS.push_back(FrameInfo(StartTime, (FrameFlags & FRAME_KF) != 0));
|
|
||||||
VI.num_frames++;
|
|
||||||
} else if (Track == AudioTrack && !ACacheIsValid) {
|
|
||||||
int Size = ReadFrame(FilePos, FrameSize, AudioCS, Env);
|
|
||||||
uint8_t *Data = Buffer;
|
|
||||||
|
|
||||||
while (Size > 0) {
|
|
||||||
int TempOutputBufSize = AVCODEC_MAX_AUDIO_FRAME_SIZE;
|
|
||||||
int Ret = avcodec_decode_audio2(AudioCodecContext, (int16_t *)DecodingBuffer, &TempOutputBufSize, Data, Size);
|
|
||||||
if (Ret < 0)
|
|
||||||
Env->ThrowError("FFmpegSource: Audio decoding error");
|
|
||||||
|
|
||||||
int DecodedSamples = VI.AudioSamplesFromBytes(TempOutputBufSize);
|
|
||||||
|
|
||||||
Size -= Ret;
|
|
||||||
Data += Ret;
|
|
||||||
VI.num_audio_samples += DecodedSamples;
|
|
||||||
|
|
||||||
if (AudioCacheType == acFLAC) {
|
|
||||||
for (int i = 0; i < DecodedSamples * VI.nchannels; i++)
|
|
||||||
FLACBuffer[i] = ((int16_t *)DecodingBuffer)[i];
|
|
||||||
FLAC__stream_encoder_process_interleaved(FSE, FLACBuffer, DecodedSamples);
|
|
||||||
} else if (AudioCacheType == acRaw) {
|
|
||||||
fwrite(DecodingBuffer, 1, TempOutputBufSize, RawCache);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ACacheIsValid) {
|
|
||||||
switch (AudioCacheType) {
|
|
||||||
case acFLAC: CloseFLACCacheWriter(FSE); break;
|
|
||||||
case acRaw: CloseRawCacheWriter(RawCache); break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ACacheIsValid = OpenAudioCache(AAudioCache, ASource, AudioTrack, Env);
|
|
||||||
if (!ACacheIsValid)
|
|
||||||
Env->ThrowError("FFmpegSource: Failed to open newly created audio cache for reading");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (VideoTrack >= 0 && VI.num_frames == 0)
|
|
||||||
Env->ThrowError("FFmpegSource: Video track contains no frames");
|
|
||||||
|
|
||||||
if (AudioTrack >= 0 && VI.num_audio_samples == 0)
|
|
||||||
Env->ThrowError("FFmpegSource: Audio track contains no samples");
|
|
||||||
|
|
||||||
if (VideoTrack >= 0)
|
|
||||||
mkv_Seek(MF, FrameToDTS.front().DTS, MKVF_SEEK_TO_PREV_KEYFRAME);
|
|
||||||
|
|
||||||
if (AVCache && !VCacheIsValid)
|
|
||||||
if (!SaveFrameInfoToFile(AVideoCache, ASource, VideoTrack))
|
|
||||||
Env->ThrowError("FFmpegSource: Failed to write video cache info");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (AudioTrack >= 0) {
|
|
||||||
avcodec_close(AudioCodecContext);
|
|
||||||
av_free(AudioCodecContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (VideoTrack >= 0) {
|
|
||||||
mkv_SetTrackMask(MF, ~(1 << VideoTrack));
|
|
||||||
|
|
||||||
// Calculate the average framerate
|
|
||||||
if (FrameToDTS.size() >= 2) {
|
|
||||||
double DTSDiff = (double)(FrameToDTS.back().DTS - FrameToDTS.front().DTS);
|
|
||||||
VI.fps_denominator = (unsigned int)(DTSDiff * mkv_TruncFloat(VideoTI->TimecodeScale) / (double)1000 / (double)(VI.num_frames - 1) + 0.5);
|
|
||||||
VI.fps_numerator = 1000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!SaveTimecodesToFile(ATimecodes, mkv_TruncFloat(VideoTI->TimecodeScale), 1000000))
|
|
||||||
Env->ThrowError("FFmpegSource: Failed to write timecodes");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~FFMatroskaSource() {
|
if (AudioTrack >= 0) {
|
||||||
free(Buffer);
|
avcodec_close(AudioCodecContext);
|
||||||
mkv_Close(MF);
|
av_free(AudioCodecContext);
|
||||||
fclose(ST.fp);
|
|
||||||
if (VideoCodecContext)
|
|
||||||
avcodec_close(VideoCodecContext);
|
|
||||||
av_free(VideoCodecContext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* Env);
|
if (VideoTrack >= 0) {
|
||||||
};
|
mkv_SetTrackMask(MF, ~(1 << VideoTrack));
|
||||||
|
|
||||||
|
// Calculate the average framerate
|
||||||
|
if (FrameToDTS.size() >= 2) {
|
||||||
|
double DTSDiff = (double)(FrameToDTS.back().DTS - FrameToDTS.front().DTS);
|
||||||
|
VI.fps_denominator = (unsigned int)(DTSDiff * mkv_TruncFloat(VideoTI->TimecodeScale) / (double)1000 / (double)(VI.num_frames - 1) + 0.5);
|
||||||
|
VI.fps_numerator = 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SaveTimecodesToFile(ATimecodes, mkv_TruncFloat(VideoTI->TimecodeScale), 1000000))
|
||||||
|
Env->ThrowError("FFmpegSource: Failed to write timecodes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FFMatroskaSource::~FFMatroskaSource() {
|
||||||
|
free(Buffer);
|
||||||
|
mkv_Close(MF);
|
||||||
|
fclose(ST.fp);
|
||||||
|
if (VideoCodecContext)
|
||||||
|
avcodec_close(VideoCodecContext);
|
||||||
|
av_free(VideoCodecContext);
|
||||||
|
}
|
||||||
|
|
||||||
int FFMatroskaSource::ReadFrame(uint64_t AFilePos, unsigned int AFrameSize, CompressedStream *ACS, IScriptEnvironment *Env) {
|
int FFMatroskaSource::ReadFrame(uint64_t AFilePos, unsigned int AFrameSize, CompressedStream *ACS, IScriptEnvironment *Env) {
|
||||||
if (ACS) {
|
if (ACS) {
|
||||||
|
@ -401,7 +385,7 @@ Done:
|
||||||
return Ret;
|
return Ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
PVideoFrame __stdcall FFMatroskaSource::GetFrame(int n, IScriptEnvironment* Env) {
|
PVideoFrame FFMatroskaSource::GetFrame(int n, IScriptEnvironment* Env) {
|
||||||
bool HasSeeked = false;
|
bool HasSeeked = false;
|
||||||
|
|
||||||
if (n < CurrentFrame || FindClosestKeyFrame(n) > CurrentFrame) {
|
if (n < CurrentFrame || FindClosestKeyFrame(n) > CurrentFrame) {
|
||||||
|
@ -426,53 +410,3 @@ PVideoFrame __stdcall FFMatroskaSource::GetFrame(int n, IScriptEnvironment* Env)
|
||||||
|
|
||||||
return OutputFrame(DecodeFrame, Env);
|
return OutputFrame(DecodeFrame, Env);
|
||||||
}
|
}
|
||||||
|
|
||||||
AVSValue __cdecl CreateFFmpegSource(AVSValue Args, void* UserData, IScriptEnvironment* Env) {
|
|
||||||
if (!UserData) {
|
|
||||||
av_register_all();
|
|
||||||
UserData = (void *)-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Args[0].Defined())
|
|
||||||
Env->ThrowError("FFmpegSource: No source specified");
|
|
||||||
|
|
||||||
const char *Source = Args[0].AsString();
|
|
||||||
int VTrack = Args[1].AsInt(-1);
|
|
||||||
int ATrack = Args[2].AsInt(-2);
|
|
||||||
const char *Timecodes = Args[3].AsString("");
|
|
||||||
bool VCache = Args[4].AsBool(true);
|
|
||||||
const char *VCacheFile = Args[5].AsString("");
|
|
||||||
const char *ACacheFile = Args[6].AsString("");
|
|
||||||
int ACCompression = Args[7].AsInt(-1);
|
|
||||||
const char *PPString = Args[8].AsString("");
|
|
||||||
int PPQuality = Args[9].AsInt(PP_QUALITY_MAX);
|
|
||||||
int SeekMode = Args[10].AsInt(1);
|
|
||||||
|
|
||||||
if (VTrack <= -2 && ATrack <= -2)
|
|
||||||
Env->ThrowError("FFmpegSource: No tracks selected");
|
|
||||||
|
|
||||||
if (ACCompression < -1 || ACCompression > 8)
|
|
||||||
Env->ThrowError("FFmpegSource: Invalid audio cache compression selected");
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (IsMatroska)
|
|
||||||
return new FFMatroskaSource(Source, VTrack, ATrack, Timecodes, VCache, VCacheFile, ACacheFile, ACCompression, PPString, PPQuality, Env);
|
|
||||||
else
|
|
||||||
return new FFmpegSource(Source, VTrack, ATrack, Timecodes, VCache, VCacheFile, ACacheFile, ACCompression, PPString, PPQuality, SeekMode, Env);
|
|
||||||
}
|
|
||||||
|
|
||||||
AVSValue __cdecl CreateFFPP(AVSValue Args, void* UserData, IScriptEnvironment* Env) {
|
|
||||||
return new FFPP(Args[0].AsClip(), Args[1].AsString(""), Args[2].AsInt(PP_QUALITY_MAX), Env);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* Env) {
|
|
||||||
Env->AddFunction("FFmpegSource", "[source]s[vtrack]i[atrack]i[timecodes]s[vcache]b[vcachefile]s[acachefile]s[accompression]i[pp]s[ppquality]i[seekmode]i", CreateFFmpegSource, 0);
|
|
||||||
Env->AddFunction("FFPP", "c[pp]s[ppquality]i", CreateFFPP, 0);
|
|
||||||
return "FFmpegSource";
|
|
||||||
};
|
|
||||||
|
|
|
@ -126,8 +126,9 @@ FFmpegSource::FFmpegSource(const char *ASource, int AVideoTrack, int AAudioTrack
|
||||||
|
|
||||||
// Needs to be indexed?
|
// Needs to be indexed?
|
||||||
if (!ACacheIsValid || !VCacheIsValid) {
|
if (!ACacheIsValid || !VCacheIsValid) {
|
||||||
|
#ifdef FLAC_CACHE
|
||||||
FLAC__StreamEncoder *FSE = NULL;
|
FLAC__StreamEncoder *FSE = NULL;
|
||||||
|
#endif // FLAC_CACHE
|
||||||
FILE *RawCache = NULL;
|
FILE *RawCache = NULL;
|
||||||
if (!ACacheIsValid)
|
if (!ACacheIsValid)
|
||||||
if (AACCompression >= 0)
|
if (AACCompression >= 0)
|
||||||
|
@ -136,7 +137,9 @@ FFmpegSource::FFmpegSource(const char *ASource, int AVideoTrack, int AAudioTrack
|
||||||
AudioCacheType = acRaw;
|
AudioCacheType = acRaw;
|
||||||
|
|
||||||
switch (AudioCacheType) {
|
switch (AudioCacheType) {
|
||||||
|
#ifdef FLAC_CACHE
|
||||||
case acFLAC: FSE = NewFLACCacheWriter(AAudioCache, ASource, AudioTrack, AACCompression, Env); break;
|
case acFLAC: FSE = NewFLACCacheWriter(AAudioCache, ASource, AudioTrack, AACCompression, Env); break;
|
||||||
|
#endif // FLAC_CACHE
|
||||||
case acRaw: RawCache = NewRawCacheWriter(AAudioCache, ASource, AudioTrack, Env); break;
|
case acRaw: RawCache = NewRawCacheWriter(AAudioCache, ASource, AudioTrack, Env); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,12 +164,14 @@ FFmpegSource::FFmpegSource(const char *ASource, int AVideoTrack, int AAudioTrack
|
||||||
Data += Ret;
|
Data += Ret;
|
||||||
VI.num_audio_samples += DecodedSamples;
|
VI.num_audio_samples += DecodedSamples;
|
||||||
|
|
||||||
if (AudioCacheType == acFLAC) {
|
if (AudioCacheType == acRaw) {
|
||||||
|
fwrite(DecodingBuffer, 1, TempOutputBufSize, RawCache);
|
||||||
|
#ifdef FLAC_CACHE
|
||||||
|
} else if (AudioCacheType == acFLAC) {
|
||||||
for (int i = 0; i < DecodedSamples * VI.nchannels; i++)
|
for (int i = 0; i < DecodedSamples * VI.nchannels; i++)
|
||||||
FLACBuffer[i] = ((int16_t *)DecodingBuffer)[i];
|
FLACBuffer[i] = ((int16_t *)DecodingBuffer)[i];
|
||||||
FLAC__stream_encoder_process_interleaved(FSE, FLACBuffer, DecodedSamples);
|
FLAC__stream_encoder_process_interleaved(FSE, FLACBuffer, DecodedSamples);
|
||||||
} else if (AudioCacheType == acRaw) {
|
#endif // FLAC_CACHE
|
||||||
fwrite(DecodingBuffer, 1, TempOutputBufSize, RawCache);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -176,7 +181,9 @@ FFmpegSource::FFmpegSource(const char *ASource, int AVideoTrack, int AAudioTrack
|
||||||
|
|
||||||
if (!ACacheIsValid) {
|
if (!ACacheIsValid) {
|
||||||
switch (AudioCacheType) {
|
switch (AudioCacheType) {
|
||||||
|
#ifdef FLAC_CACHE
|
||||||
case acFLAC: CloseFLACCacheWriter(FSE); break;
|
case acFLAC: CloseFLACCacheWriter(FSE); break;
|
||||||
|
#endif // FLAC_CACHE
|
||||||
case acRaw: CloseRawCacheWriter(RawCache); break;
|
case acRaw: CloseRawCacheWriter(RawCache); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,7 +256,7 @@ Done:
|
||||||
return Ret;
|
return Ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
PVideoFrame __stdcall FFmpegSource::GetFrame(int n, IScriptEnvironment* Env) {
|
PVideoFrame FFmpegSource::GetFrame(int n, IScriptEnvironment* Env) {
|
||||||
bool HasSeeked = false;
|
bool HasSeeked = false;
|
||||||
int ClosestKF = FindClosestKeyFrame(n);
|
int ClosestKF = FindClosestKeyFrame(n);
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,28 @@
|
||||||
|
// Copyright (c) 2007 Fredrik Mellbin
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
#ifndef FFMPEGSOURCE_H
|
||||||
|
#define FFMPEGSOURCE_H
|
||||||
|
|
||||||
|
#define FLAC_CACHE
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -7,14 +32,18 @@
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <io.h>
|
#include <io.h>
|
||||||
|
|
||||||
|
#ifdef FLAC_CACHE
|
||||||
#include <stream_decoder.h>
|
#include <stream_decoder.h>
|
||||||
#include <stream_encoder.h>
|
#include <stream_encoder.h>
|
||||||
|
#endif // FLAC_CACHE
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <ffmpeg\avformat.h>
|
#include <ffmpeg\avformat.h>
|
||||||
#include <ffmpeg\avcodec.h>
|
#include <ffmpeg\avcodec.h>
|
||||||
#include <ffmpeg\swscale.h>
|
#include <ffmpeg\swscale.h>
|
||||||
#include <postproc\postprocess.h>
|
#include <postproc\postprocess.h>
|
||||||
|
|
||||||
|
#include "stdiostream.h"
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "MatroskaParser.h"
|
#include "MatroskaParser.h"
|
||||||
|
@ -50,12 +79,15 @@ private:
|
||||||
protected:
|
protected:
|
||||||
VideoInfo VI;
|
VideoInfo VI;
|
||||||
AVFrame *DecodeFrame;
|
AVFrame *DecodeFrame;
|
||||||
FILE *RawAudioCache;
|
|
||||||
FLAC__StreamDecoder *FLACAudioCache;
|
|
||||||
AudioCacheFormat AudioCacheType;
|
AudioCacheFormat AudioCacheType;
|
||||||
|
FILE *RawAudioCache;
|
||||||
|
|
||||||
|
#ifdef FLAC_CACHE
|
||||||
|
FLAC__StreamDecoder *FLACAudioCache;
|
||||||
|
FLAC__int32 *FLACBuffer;
|
||||||
|
#endif // FLAC_CACHE
|
||||||
|
|
||||||
uint8_t *DecodingBuffer;
|
uint8_t *DecodingBuffer;
|
||||||
FLAC__int32 *FLACBuffer;
|
|
||||||
|
|
||||||
struct FrameInfo {
|
struct FrameInfo {
|
||||||
int64_t DTS;
|
int64_t DTS;
|
||||||
|
@ -73,8 +105,10 @@ protected:
|
||||||
bool SaveTimecodesToFile(const char *ATimecodeFile, int64_t ScaleD, int64_t ScaleN);
|
bool SaveTimecodesToFile(const char *ATimecodeFile, int64_t ScaleD, int64_t ScaleN);
|
||||||
|
|
||||||
bool OpenAudioCache(const char *AAudioCacheFile, const char *ASource, int AAudioTrack, IScriptEnvironment *Env);
|
bool OpenAudioCache(const char *AAudioCacheFile, const char *ASource, int AAudioTrack, IScriptEnvironment *Env);
|
||||||
|
#ifdef FLAC_CACHE
|
||||||
FLAC__StreamEncoder *FFBase::NewFLACCacheWriter(const char *AAudioCacheFile, const char *ASource, int AAudioTrack, int ACompression, IScriptEnvironment *Env);
|
FLAC__StreamEncoder *FFBase::NewFLACCacheWriter(const char *AAudioCacheFile, const char *ASource, int AAudioTrack, int ACompression, IScriptEnvironment *Env);
|
||||||
void FFBase::CloseFLACCacheWriter(FLAC__StreamEncoder *AFSE);
|
void FFBase::CloseFLACCacheWriter(FLAC__StreamEncoder *AFSE);
|
||||||
|
#endif // FLAC_CACHE
|
||||||
FILE *FFBase::NewRawCacheWriter(const char *AAudioCacheFile, const char *ASource, int AAudioTrack, IScriptEnvironment *Env);
|
FILE *FFBase::NewRawCacheWriter(const char *AAudioCacheFile, const char *ASource, int AAudioTrack, IScriptEnvironment *Env);
|
||||||
void FFBase::CloseRawCacheWriter(FILE *ARawCache);
|
void FFBase::CloseRawCacheWriter(FILE *ARawCache);
|
||||||
|
|
||||||
|
@ -113,3 +147,26 @@ public:
|
||||||
~FFmpegSource();
|
~FFmpegSource();
|
||||||
PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* Env);
|
PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* Env);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class FFMatroskaSource : public FFBase {
|
||||||
|
private:
|
||||||
|
StdIoStream ST;
|
||||||
|
unsigned int BufferSize;
|
||||||
|
CompressedStream *VideoCS;
|
||||||
|
CompressedStream *AudioCS;
|
||||||
|
AVCodecContext *VideoCodecContext;
|
||||||
|
MatroskaFile *MF;
|
||||||
|
char ErrorMessage[256];
|
||||||
|
uint8_t *Buffer;
|
||||||
|
int CurrentFrame;
|
||||||
|
|
||||||
|
int ReadFrame(uint64_t AFilePos, unsigned int AFrameSize, CompressedStream *ACS, IScriptEnvironment *Env);
|
||||||
|
int DecodeNextFrame(AVFrame *AFrame, int64_t *AFirstStartTime, IScriptEnvironment* Env);
|
||||||
|
int GetTrackIndex(int Index, unsigned char ATrackType, IScriptEnvironment *Env);
|
||||||
|
public:
|
||||||
|
FFMatroskaSource(const char *ASource, int AVideoTrack, int AAudioTrack, const char *ATimecodes, bool AVCache, const char *AVideoCache, const char *AAudioCache, int AACCompression, const char *APPString, int AQuality, IScriptEnvironment* Env);
|
||||||
|
~FFMatroskaSource();
|
||||||
|
PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* Env);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -11,9 +11,12 @@ FFmpegSource Documentation
|
||||||
<ul>
|
<ul>
|
||||||
<li>1.11<ul>
|
<li>1.11<ul>
|
||||||
<li>Now officially uses the MIT license</li>
|
<li>Now officially uses the MIT license</li>
|
||||||
|
<li>Much cleaner source</li>
|
||||||
|
<li>Can be compiled without support for compressing the audio cache with FLAC</li>
|
||||||
<li>Supports more audio formats in matroska</li>
|
<li>Supports more audio formats in matroska</li>
|
||||||
<li>RGB24 output no longer has swapped colors if the video is converted to it for output</li>
|
<li>RGB24 output no longer has swapped colors if the video is converted to it for output (there still seems to be some bugs lurking when conversion is done with libswscale)</li>
|
||||||
<li>Fixed an access violation on close when no audio is opened (introduced in 1.10)</li>
|
<li>Fixed an access violation on close when no audio is opened (introduced in 1.10)</li>
|
||||||
|
<li>Updated FFmpeg to rev 10423</li>
|
||||||
</ul></li>
|
</ul></li>
|
||||||
|
|
||||||
<li>1.10<ul>
|
<li>1.10<ul>
|
||||||
|
@ -222,7 +225,9 @@ tn:64:128:256
|
||||||
|
|
||||||
<p><b>FFmpeg svn</b> from http://ffmpeg.mplayerhq.hu/</p>
|
<p><b>FFmpeg svn</b> from http://ffmpeg.mplayerhq.hu/</p>
|
||||||
|
|
||||||
<p><b>Required Configuration:</b>
|
<p><b>FLAC (optional)</b> from http://flac.sourceforge.net/</p>
|
||||||
|
|
||||||
|
<p><b>Required FFmpeg Configuration:</b>
|
||||||
./configure --enable-shared --disable-static --enable-memalign-hack --enable-gpl --enable-swscaler</p>
|
./configure --enable-shared --disable-static --enable-memalign-hack --enable-gpl --enable-swscaler</p>
|
||||||
|
|
||||||
<p><b>Suggested Additional Options:</b>
|
<p><b>Suggested Additional Options:</b>
|
||||||
|
|
|
@ -70,7 +70,7 @@ FFPP::~FFPP() {
|
||||||
avpicture_free(&OutputPicture);
|
avpicture_free(&OutputPicture);
|
||||||
}
|
}
|
||||||
|
|
||||||
PVideoFrame __stdcall FFPP::GetFrame(int n, IScriptEnvironment* Env) {
|
PVideoFrame FFPP::GetFrame(int n, IScriptEnvironment* Env) {
|
||||||
PVideoFrame Src = child->GetFrame(n, Env);
|
PVideoFrame Src = child->GetFrame(n, Env);
|
||||||
PVideoFrame Dst = Env->NewVideoFrame(vi);
|
PVideoFrame Dst = Env->NewVideoFrame(vi);
|
||||||
|
|
||||||
|
|
|
@ -46,4 +46,59 @@ int GetSWSCPUFlags(IScriptEnvironment *Env) {
|
||||||
CPUFlags |= SWS_CPU_CAPS_3DNOW;
|
CPUFlags |= SWS_CPU_CAPS_3DNOW;
|
||||||
|
|
||||||
return Flags;
|
return Flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AVSValue __cdecl CreateFFmpegSource(AVSValue Args, void* UserData, IScriptEnvironment* Env) {
|
||||||
|
if (!UserData) {
|
||||||
|
av_register_all();
|
||||||
|
UserData = (void *)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Args[0].Defined())
|
||||||
|
Env->ThrowError("FFmpegSource: No source specified");
|
||||||
|
|
||||||
|
const char *Source = Args[0].AsString();
|
||||||
|
int VTrack = Args[1].AsInt(-1);
|
||||||
|
int ATrack = Args[2].AsInt(-2);
|
||||||
|
const char *Timecodes = Args[3].AsString("");
|
||||||
|
bool VCache = Args[4].AsBool(true);
|
||||||
|
const char *VCacheFile = Args[5].AsString("");
|
||||||
|
const char *ACacheFile = Args[6].AsString("");
|
||||||
|
int ACCompression = Args[7].AsInt(-1);
|
||||||
|
const char *PPString = Args[8].AsString("");
|
||||||
|
int PPQuality = Args[9].AsInt(PP_QUALITY_MAX);
|
||||||
|
int SeekMode = Args[10].AsInt(1);
|
||||||
|
|
||||||
|
if (VTrack <= -2 && ATrack <= -2)
|
||||||
|
Env->ThrowError("FFmpegSource: No tracks selected");
|
||||||
|
|
||||||
|
#ifdef FLAC_CACHE
|
||||||
|
if (ACCompression < -1 || ACCompression > 8)
|
||||||
|
#else
|
||||||
|
if (ACCompression != -1)
|
||||||
|
#endif // FLAC_CACHE
|
||||||
|
Env->ThrowError("FFmpegSource: Invalid audio cache compression selected");
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (IsMatroska)
|
||||||
|
return new FFMatroskaSource(Source, VTrack, ATrack, Timecodes, VCache, VCacheFile, ACacheFile, ACCompression, PPString, PPQuality, Env);
|
||||||
|
else
|
||||||
|
return new FFmpegSource(Source, VTrack, ATrack, Timecodes, VCache, VCacheFile, ACacheFile, ACCompression, PPString, PPQuality, SeekMode, Env);
|
||||||
|
}
|
||||||
|
|
||||||
|
AVSValue __cdecl CreateFFPP(AVSValue Args, void* UserData, IScriptEnvironment* Env) {
|
||||||
|
return new FFPP(Args[0].AsClip(), Args[1].AsString(""), Args[2].AsInt(PP_QUALITY_MAX), Env);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* Env) {
|
||||||
|
Env->AddFunction("FFmpegSource", "[source]s[vtrack]i[atrack]i[timecodes]s[vcache]b[vcachefile]s[acachefile]s[accompression]i[pp]s[ppquality]i[seekmode]i", CreateFFmpegSource, 0);
|
||||||
|
Env->AddFunction("FFPP", "c[pp]s[ppquality]i", CreateFFPP, 0);
|
||||||
|
return "FFmpegSource";
|
||||||
|
};
|
||||||
|
|
|
@ -1,6 +1,24 @@
|
||||||
#include "stdiostream.h"
|
// Copyright (c) 2007 Fredrik Mellbin
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
#define CACHESIZE 65536
|
#include "stdiostream.h"
|
||||||
|
|
||||||
/* StdIoStream methods */
|
/* StdIoStream methods */
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,40 @@
|
||||||
|
// Copyright (c) 2007 Fredrik Mellbin
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
#ifndef STDIOSTREAM_H
|
#ifndef STDIOSTREAM_H
|
||||||
#define STDIOSTREAM_H
|
#define STDIOSTREAM_H
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <io.h>
|
||||||
#include "MatroskaParser.h"
|
#include "MatroskaParser.h"
|
||||||
|
|
||||||
|
#define CACHESIZE 65536
|
||||||
|
|
||||||
/************\
|
/************\
|
||||||
* Structures *
|
* Structures *
|
||||||
\************/
|
\************/
|
||||||
|
|
||||||
|
|
||||||
/* first we need to create an I/O object that the parser will use to read the
|
/* first we need to create an I/O object that the parser will use to read the
|
||||||
* source file
|
* source file
|
||||||
*/
|
*/
|
||||||
|
@ -20,8 +46,6 @@ struct StdIoStream {
|
||||||
|
|
||||||
typedef struct StdIoStream StdIoStream;
|
typedef struct StdIoStream StdIoStream;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/***********\
|
/***********\
|
||||||
* Functions *
|
* Functions *
|
||||||
\***********/
|
\***********/
|
||||||
|
|
Loading…
Reference in New Issue