Update libffms to 2.15

Originally committed to SVN as r5348.
This commit is contained in:
Thomas Goyne 2011-02-21 21:16:05 +00:00
parent 352741fa78
commit 2d15490233
22 changed files with 1901 additions and 1197 deletions

View File

@ -22,28 +22,32 @@
#define FFMS_H
// Version format: major - minor - micro - bump
#define FFMS_VERSION ((2 << 24) | (13 << 16)| (0 << 8) | 1)
#define FFMS_VERSION ((2 << 24) | (14 << 16) | (2 << 8) | 0)
#include <stdint.h>
#ifdef __cplusplus
# define EXTERN_C extern "C"
# define FFMS_EXTERN_C extern "C"
# define FFMS_CLASS_TYPE class
#else
# define EXTERN_C
# define FFMS_EXTERN_C
# define FFMS_CLASS_TYPE struct
#endif
#ifdef _WIN32
# define FFMS_CC __stdcall
# ifdef FFMS_EXPORTS
# define FFMS_API(ret) EXTERN_C __declspec(dllexport) ret FFMS_CC
# ifdef _MSC_VER
# ifdef FFMS_EXPORTS
# define FFMS_API(ret) FFMS_EXTERN_C __declspec(dllexport) ret FFMS_CC
# else
# define FFMS_API(ret) FFMS_EXTERN_C __declspec(dllimport) ret FFMS_CC
# endif
# else
# define FFMS_API(ret) EXTERN_C __declspec(dllimport) ret FFMS_CC
# define FFMS_API(ret) FFMS_EXTERN_C ret FFMS_CC
# endif
#else
# define FFMS_CC
# define FFMS_API(ret) EXTERN_C ret FFMS_CC
# define FFMS_API(ret) FFMS_EXTERN_C ret FFMS_CC
#endif
typedef struct {
@ -64,33 +68,34 @@ enum FFMS_Errors {
FFMS_ERROR_SUCCESS = 0,
// Main types - where the error occurred
FFMS_ERROR_INDEX = 1,
FFMS_ERROR_INDEXING,
FFMS_ERROR_POSTPROCESSING,
FFMS_ERROR_SCALING,
FFMS_ERROR_DECODING,
FFMS_ERROR_SEEKING,
FFMS_ERROR_PARSER,
FFMS_ERROR_TRACK,
FFMS_ERROR_WAVE_WRITER,
FFMS_ERROR_CANCELLED,
FFMS_ERROR_INDEX = 1, // index file handling
FFMS_ERROR_INDEXING, // indexing
FFMS_ERROR_POSTPROCESSING, // video postprocessing (libpostproc)
FFMS_ERROR_SCALING, // image scaling (libswscale)
FFMS_ERROR_DECODING, // audio/video decoding
FFMS_ERROR_SEEKING, // seeking
FFMS_ERROR_PARSER, // file parsing
FFMS_ERROR_TRACK, // track handling
FFMS_ERROR_WAVE_WRITER, // WAVE64 file writer
FFMS_ERROR_CANCELLED, // operation aborted
// Subtypes - what caused the error
FFMS_ERROR_UNKNOWN = 20,
FFMS_ERROR_UNSUPPORTED,
FFMS_ERROR_FILE_READ,
FFMS_ERROR_FILE_WRITE,
FFMS_ERROR_NO_FILE,
FFMS_ERROR_VERSION,
FFMS_ERROR_ALLOCATION_FAILED,
FFMS_ERROR_INVALID_ARGUMENT,
FFMS_ERROR_CODEC,
FFMS_ERROR_NOT_AVAILABLE,
FFMS_ERROR_FILE_MISMATCH,
FFMS_ERROR_USER
FFMS_ERROR_UNKNOWN = 20, // unknown error
FFMS_ERROR_UNSUPPORTED, // format or operation is not supported with this binary
FFMS_ERROR_FILE_READ, // cannot read from file
FFMS_ERROR_FILE_WRITE, // cannot write to file
FFMS_ERROR_NO_FILE, // no such file or directory
FFMS_ERROR_VERSION, // wrong version
FFMS_ERROR_ALLOCATION_FAILED, // out of memory
FFMS_ERROR_INVALID_ARGUMENT, // invalid or nonsensical argument
FFMS_ERROR_CODEC, // decoder error
FFMS_ERROR_NOT_AVAILABLE, // requested mode or operation unavailable in this binary
FFMS_ERROR_FILE_MISMATCH, // provided index does not match the file
FFMS_ERROR_USER // problem exists between keyboard and chair
};
enum FFMS_Sources {
FFMS_SOURCE_DEFAULT = 0x00,
FFMS_SOURCE_LAVF = 0x01,
FFMS_SOURCE_MATROSKA = 0x02,
FFMS_SOURCE_HAALIMPEG = 0x04,
@ -102,7 +107,8 @@ enum FFMS_CPUFeatures {
FFMS_CPU_CAPS_MMX2 = 0x02,
FFMS_CPU_CAPS_3DNOW = 0x04,
FFMS_CPU_CAPS_ALTIVEC = 0x08,
FFMS_CPU_CAPS_BFIN = 0x10
FFMS_CPU_CAPS_BFIN = 0x10,
FFMS_CPU_CAPS_SSE2 = 0x20
};
enum FFMS_SeekMode {
@ -123,18 +129,18 @@ enum FFMS_IndexErrorHandling {
enum FFMS_TrackType {
FFMS_TYPE_UNKNOWN = -1,
FFMS_TYPE_VIDEO,
FFMS_TYPE_AUDIO,
FFMS_TYPE_DATA,
FFMS_TYPE_SUBTITLE,
FFMS_TYPE_ATTACHMENT
FFMS_TYPE_AUDIO,
FFMS_TYPE_DATA,
FFMS_TYPE_SUBTITLE,
FFMS_TYPE_ATTACHMENT
};
enum FFMS_SampleFormat {
FFMS_FMT_U8 = 0,
FFMS_FMT_S16,
FFMS_FMT_S32,
FFMS_FMT_FLT,
FFMS_FMT_DBL
FFMS_FMT_U8 = 0,
FFMS_FMT_S16,
FFMS_FMT_S32,
FFMS_FMT_FLT,
FFMS_FMT_DBL
};
enum FFMS_AudioChannel {
@ -174,6 +180,12 @@ enum FFMS_Resizers {
FFMS_RESIZER_SPLINE = 0x0400
};
enum FFMS_AudioDelayModes {
FFMS_DELAY_NO_SHIFT = -3,
FFMS_DELAY_TIME_ZERO = -2,
FFMS_DELAY_FIRST_VIDEO_TRACK = -1
};
typedef struct {
uint8_t *Data[4];
int Linesize[4];
@ -236,11 +248,11 @@ typedef int (FFMS_CC *TAudioNameCallback)(const char *SourceFile, int Track, con
// Most functions return 0 on success
// Functions without error message output can be assumed to never fail in a graceful way
FFMS_API(void) FFMS_Init(int CPUFeatures);
FFMS_API(void) FFMS_Init(int CPUFeatures, int UseUTF8Paths);
FFMS_API(int) FFMS_GetLogLevel();
FFMS_API(void) FFMS_SetLogLevel(int Level);
FFMS_API(FFMS_VideoSource *) FFMS_CreateVideoSource(const char *SourceFile, int Track, FFMS_Index *Index, int Threads, int SeekMode, FFMS_ErrorInfo *ErrorInfo);
FFMS_API(FFMS_AudioSource *) FFMS_CreateAudioSource(const char *SourceFile, int Track, FFMS_Index *Index, FFMS_ErrorInfo *ErrorInfo);
FFMS_API(FFMS_AudioSource *) FFMS_CreateAudioSource(const char *SourceFile, int Track, FFMS_Index *Index, int DelayMode, FFMS_ErrorInfo *ErrorInfo);
FFMS_API(void) FFMS_DestroyVideoSource(FFMS_VideoSource *V);
FFMS_API(void) FFMS_DestroyAudioSource(FFMS_AudioSource *A);
FFMS_API(const FFMS_VideoProperties *) FFMS_GetVideoProperties(FFMS_VideoSource *V);
@ -253,6 +265,8 @@ FFMS_API(void) FFMS_ResetOutputFormatV(FFMS_VideoSource *V);
FFMS_API(int) FFMS_SetPP(FFMS_VideoSource *V, const char *PP, FFMS_ErrorInfo *ErrorInfo);
FFMS_API(void) FFMS_ResetPP(FFMS_VideoSource *V);
FFMS_API(void) FFMS_DestroyIndex(FFMS_Index *Index);
FFMS_API(int) FFMS_GetSourceType(FFMS_Index *Index);
FFMS_API(int) FFMS_GetSourceTypeI(FFMS_Indexer *Indexer);
FFMS_API(int) FFMS_GetFirstTrackOfType(FFMS_Index *Index, int TrackType, FFMS_ErrorInfo *ErrorInfo);
FFMS_API(int) FFMS_GetFirstIndexedTrackOfType(FFMS_Index *Index, int TrackType, FFMS_ErrorInfo *ErrorInfo);
FFMS_API(int) FFMS_GetNumTracks(FFMS_Index *Index);
@ -260,6 +274,7 @@ FFMS_API(int) FFMS_GetNumTracksI(FFMS_Indexer *Indexer);
FFMS_API(int) FFMS_GetTrackType(FFMS_Track *T);
FFMS_API(int) FFMS_GetTrackTypeI(FFMS_Indexer *Indexer, int Track);
FFMS_API(const char *) FFMS_GetCodecNameI(FFMS_Indexer *Indexer, int Track);
FFMS_API(const char *) FFMS_GetFormatNameI(FFMS_Indexer *Indexer);
FFMS_API(int) FFMS_GetNumFrames(FFMS_Track *T);
FFMS_API(const FFMS_FrameInfo *) FFMS_GetFrameInfo(FFMS_Track *T, int Frame);
FFMS_API(FFMS_Track *) FFMS_GetTrackFromIndex(FFMS_Index *Index, int Track);
@ -270,6 +285,7 @@ FFMS_API(int) FFMS_WriteTimecodes(FFMS_Track *T, const char *TimecodeFile, FFMS_
FFMS_API(FFMS_Index *) FFMS_MakeIndex(const char *SourceFile, int IndexMask, int DumpMask, TAudioNameCallback ANC, void *ANCPrivate, int ErrorHandling, TIndexCallback IC, void *ICPrivate, FFMS_ErrorInfo *ErrorInfo);
FFMS_API(int) FFMS_DefaultAudioFilename(const char *SourceFile, int Track, const FFMS_AudioProperties *AP, char *FileName, int FNSize, void *Private);
FFMS_API(FFMS_Indexer *) FFMS_CreateIndexer(const char *SourceFile, FFMS_ErrorInfo *ErrorInfo);
FFMS_API(FFMS_Indexer *) FFMS_CreateIndexerWithDemuxer(const char *SourceFile, int Demuxer, FFMS_ErrorInfo *ErrorInfo);
FFMS_API(FFMS_Index *) FFMS_DoIndexing(FFMS_Indexer *Indexer, int IndexMask, int DumpMask, TAudioNameCallback ANC, void *ANCPrivate, int ErrorHandling, TIndexCallback IC, void *ICPrivate, FFMS_ErrorInfo *ErrorInfo);
FFMS_API(void) FFMS_CancelIndexing(FFMS_Indexer *Indexer);
FFMS_API(FFMS_Index *) FFMS_ReadIndex(const char *IndexFile, FFMS_ErrorInfo *ErrorInfo);
@ -279,4 +295,5 @@ FFMS_API(int) FFMS_GetPixFmt(const char *Name);
FFMS_API(int) FFMS_GetPresentSources();
FFMS_API(int) FFMS_GetEnabledSources();
#endif

View File

@ -45,17 +45,41 @@
# if (LIBAVCODEC_VERSION_INT) >= (AV_VERSION_INT(52,29,0))
# define FFMS_HAVE_FFMPEG_COLORSPACE_INFO
# else
# define AVCOL_RANGE_JPEG 2
# ifdef _MSC_VER
# pragma message("WARNING: Your FFmpeg is too old to support reporting colorspace and luma range information. The corresponding fields of FFMS_VideoProperties will be set to 0. Please update FFmpeg to get rid of this warning.")
# else
# warning "Your FFmpeg is too old to support reporting colorspace and luma range information. The corresponding fields of FFMS_VideoProperties will be set to 0. Please update FFmpeg to get rid of this warning."
# endif
# endif
# if (LIBAVCODEC_VERSION_INT) < (AV_VERSION_INT(52,30,2))
# define AV_PKT_FLAG_KEY PKT_FLAG_KEY
# endif
# if (LIBAVCODEC_VERSION_INT) >= (AV_VERSION_INT(52,94,3)) // there are ~3 revisions where this will break but fixing that is :effort:
# undef SampleFormat
# else
# define AVSampleFormat SampleFormat
# define av_get_bits_per_sample_fmt av_get_bits_per_sample_format
# define AV_SAMPLE_FMT_U8 SAMPLE_FMT_U8
# define AV_SAMPLE_FMT_S16 SAMPLE_FMT_S16
# define AV_SAMPLE_FMT_S32 SAMPLE_FMT_S32
# define AV_SAMPLE_FMT_FLT SAMPLE_FMT_FLT
# define AV_SAMPLE_FMT_DBL SAMPLE_FMT_DBL
# endif
#endif
#ifndef AV_PKT_FLAG_KEY
# define AV_PKT_FLAG_KEY PKT_FLAG_KEY
#ifdef LIBAVUTIL_VERSION_INT
# if (LIBAVUTIL_VERSION_INT) < (AV_VERSION_INT(50, 8, 0))
# define av_get_pix_fmt avcodec_get_pix_fmt
# endif
#endif
#ifdef LIBSWSCALE_VERSION_INT
# if (LIBSWSCALE_VERSION_INT) < (AV_VERSION_INT(0,8,0))
# define FFMS_SWS_CONST_PARAM
# else
# define FFMS_SWS_CONST_PARAM const
# endif
#endif
#endif // FFMSCOMPAT_H

View File

@ -1,4 +1,4 @@
// Copyright (c) 2007-2009 Fredrik Mellbin
// Copyright (c) 2010 Thomas Goyne <tgoyne@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@ -20,113 +20,285 @@
#include "audiosource.h"
extern "C" {
#include <libavutil/mem.h>
}
#include <algorithm>
#include <cassert>
/* Audio Cache */
TAudioBlock::TAudioBlock(int64_t Start, int64_t Samples, uint8_t *SrcData, size_t SrcBytes) {
this->Start = Start;
this->Samples = Samples;
Data = new uint8_t[SrcBytes];
memcpy(Data, SrcData, SrcBytes);
}
TAudioBlock::~TAudioBlock() {
delete[] Data;
}
TAudioCache::TAudioCache() {
MaxCacheBlocks = 0;
BytesPerSample = 0;
}
TAudioCache::~TAudioCache() {
for (TAudioCache::iterator it=begin(); it != end(); it++)
delete *it;
}
void TAudioCache::Initialize(int BytesPerSample, int MaxCacheBlocks) {
this->BytesPerSample = BytesPerSample;
this->MaxCacheBlocks = MaxCacheBlocks;
}
void TAudioCache::CacheBlock(int64_t Start, int64_t Samples, uint8_t *SrcData) {
if (BytesPerSample > 0) {
for (TAudioCache::iterator it=begin(); it != end(); it++) {
if ((*it)->Start == Start) {
delete *it;
erase(it);
break;
}
}
push_front(new TAudioBlock(Start, Samples, SrcData, static_cast<size_t>(Samples * BytesPerSample)));
if (static_cast<int>(size()) >= MaxCacheBlocks) {
delete back();
pop_back();
}
}
}
bool TAudioCache::AudioBlockComp(TAudioBlock *A, TAudioBlock *B) {
return A->Start < B->Start;
}
int64_t TAudioCache::FillRequest(int64_t Start, int64_t Samples, uint8_t *Dst) {
// May be better to move used blocks to the front
std::list<TAudioBlock *> UsedBlocks;
for (TAudioCache::iterator it=begin(); it != end(); it++) {
int64_t SrcOffset = FFMAX(0, Start - (*it)->Start);
int64_t DstOffset = FFMAX(0, (*it)->Start - Start);
int64_t CopySamples = FFMIN((*it)->Samples - SrcOffset, Samples - DstOffset);
if (CopySamples > 0) {
memcpy(Dst + DstOffset * BytesPerSample, (*it)->Data + SrcOffset * BytesPerSample, static_cast<size_t>(CopySamples * BytesPerSample));
UsedBlocks.push_back(*it);
}
}
UsedBlocks.sort(AudioBlockComp);
int64_t Ret = Start;
for (std::list<TAudioBlock *>::iterator it = UsedBlocks.begin(); it != UsedBlocks.end(); it++) {
if (it == UsedBlocks.begin() || Ret == (*it)->Start)
Ret = (*it)->Start + (*it)->Samples;
else
break;
}
return FFMIN(Ret, Start + Samples);
}
/* FFMS_AudioSource base class */
FFMS_AudioSource::FFMS_AudioSource(const char *SourceFile, FFMS_Index *Index, int Track) : CurrentSample(0) {
if (Track < 0 || Track >= static_cast<int>(Index->size()))
FFMS_AudioSource::FFMS_AudioSource(const char *SourceFile, FFMS_Index &Index, int Track)
: Delay(0)
, MaxCacheBlocks(50)
, BytesPerSample(0)
, Decoded(0)
, CurrentSample(-1)
, PacketNumber(0)
, CurrentFrame(NULL)
, TrackNumber(Track)
, SeekOffset(0)
, DecodingBuffer(AVCODEC_MAX_AUDIO_FRAME_SIZE * 10)
{
if (Track < 0 || Track >= static_cast<int>(Index.size()))
throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT,
"Out of bounds track index selected");
if (Index->at(Track).TT != FFMS_TYPE_AUDIO)
if (Index[Track].TT != FFMS_TYPE_AUDIO)
throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT,
"Not an audio track");
if (Index[Track].size() == 0)
if (Index[Track].empty())
throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT,
"Audio track contains no audio frames");
if (!Index->CompareFileSignature(SourceFile))
Frames = Index[Track];
if (!Index.CompareFileSignature(SourceFile))
throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_FILE_MISMATCH,
"The index does not match the source file");
}
void FFMS_AudioSource::Init(FFMS_Index &Index, int DelayMode) {
// The first packet after a seek is often decoded incorrectly, which
// makes it impossible to ever correctly seek back to the beginning, so
// store the first block now
DecodingBuffer = (uint8_t *) av_malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE * 10 * sizeof(*DecodingBuffer));
if (!DecodingBuffer)
throw std::bad_alloc();
// In addition, anything with the same PTS as the first packet can't be
// distinguished from the first packet and so can't be seeked to, so
// store those as well
// Some of LAVF's splitters don't like to seek to the beginning of the
// file (ts and?), so cache a few blocks even if PTSes are unique
// Packet 7 is the last packet I've had be unseekable to, so cache up to
// 10 for a bit of an extra buffer
CacheIterator end = Cache.end();
while (PacketNumber < Frames.size() &&
((Frames[0].PTS != ffms_av_nopts_value && Frames[PacketNumber].PTS == Frames[0].PTS) ||
Cache.size() < 10)) {
DecodeNextBlock();
if (Decoded)
CacheBlock(end, CurrentSample, Decoded, &DecodingBuffer[0]);
}
// Store the iterator to the last element of the cache which is used for
// correctness rather than speed, so that when looking for one to delete
// we know how much to skip
CacheNoDelete = Cache.end();
--CacheNoDelete;
// Read properties of the audio which may not be available until the first
// frame has been decoded
FillAP(AP, CodecContext, Frames);
if (AP.SampleRate <= 0 || AP.BitsPerSample <= 0)
throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC,
"Codec returned zero size audio");
if (DelayMode < FFMS_DELAY_NO_SHIFT)
throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT,
"Bad audio delay compensation mode");
if (DelayMode == FFMS_DELAY_NO_SHIFT) return;
if (DelayMode > (signed)Index.size())
throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT,
"Out of bounds track index selected for audio delay compensation");
if (DelayMode >= 0 && Index[DelayMode].TT != FFMS_TYPE_VIDEO)
throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT,
"Audio delay compensation must be relative to a video track");
double AdjustRelative = 0;
if (DelayMode != FFMS_DELAY_TIME_ZERO) {
if (DelayMode == FFMS_DELAY_FIRST_VIDEO_TRACK) {
for (size_t i = 0; i < Index.size(); ++i) {
if (Index[i].TT == FFMS_TYPE_VIDEO) {
DelayMode = i;
break;
}
}
}
if (DelayMode >= 0) {
const FFMS_Track &VTrack = Index[DelayMode];
AdjustRelative = VTrack[0].PTS * VTrack.TB.Num / (double)VTrack.TB.Den;
}
}
Delay = static_cast<int64_t>((Frames[0].PTS * Frames.TB.Num / (double)Frames.TB.Den - AdjustRelative) * AP.SampleRate / 1000. + .5);
AP.NumSamples += Delay;
}
FFMS_AudioSource::~FFMS_AudioSource() {
av_free(DecodingBuffer);
void FFMS_AudioSource::CacheBlock(CacheIterator &pos, int64_t Start, size_t Samples, uint8_t *SrcData) {
Cache.insert(pos, AudioBlock(Start, Samples, SrcData, Samples * BytesPerSample));
if (Cache.size() >= MaxCacheBlocks) {
// Kill the oldest one
CacheIterator min = CacheNoDelete;
// Never drop the first one as the first packet decoded after a seek
// is often decoded incorrectly and we can't seek to before the first one
++min;
for (CacheIterator it = min; it != Cache.end(); ++it)
if (it->Age < min->Age) min = it;
if (min == pos) ++pos;
Cache.erase(min);
}
}
void FFMS_AudioSource::GetAudioCheck(int64_t Start, int64_t Count) {
if (Start < 0 || Start + Count > AP.NumSamples)
void FFMS_AudioSource::DecodeNextBlock() {
if (BytesPerSample == 0) BytesPerSample = (av_get_bits_per_sample_fmt(CodecContext->sample_fmt) * CodecContext->channels) / 8;
CurrentFrame = &Frames[PacketNumber];
AVPacket Packet;
if (!ReadPacket(&Packet))
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_UNKNOWN, "ReadPacket unexpectedly failed to read a packet");
// ReadPacket may have changed the packet number
CurrentFrame = &Frames[PacketNumber];
CurrentSample = CurrentFrame->SampleStart;
++PacketNumber;
uint8_t *Buf = &DecodingBuffer[0];
uint8_t *Data = Packet.data;
while (Packet.size > 0) {
int TempOutputBufSize = AVCODEC_MAX_AUDIO_FRAME_SIZE * 10 - (Buf - &DecodingBuffer[0]);
int Ret = avcodec_decode_audio3(CodecContext, (int16_t *)Buf, &TempOutputBufSize, &Packet);
// Should only ever happen if the user chose to ignore decoding errors
// during indexing, so continue to just ignore decoding errors
if (Ret < 0) break;
if (Ret > 0) {
Packet.size -= Ret;
Packet.data += Ret;
Buf += TempOutputBufSize;
}
}
Packet.data = Data;
FreePacket(&Packet);
Decoded = (Buf - &DecodingBuffer[0]) / BytesPerSample;
if (Decoded == 0) {
// zero sample packets aren't included in the index so we didn't
// actually move to the next packet
--PacketNumber;
}
}
static bool SampleStartComp(const TFrameInfo &a, const TFrameInfo &b) {
return a.SampleStart < b.SampleStart;
}
void FFMS_AudioSource::GetAudio(void *Buf, int64_t Start, int64_t Count) {
if (Start < 0 || Start + Count > AP.NumSamples || Count < 0)
throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_INVALID_ARGUMENT,
"Out of bounds audio samples requested");
uint8_t *Dst = static_cast<uint8_t*>(Buf);
// Apply audio delay (if any) and fill any samples before the start time with zero
Start -= Delay;
if (Start < 0) {
size_t Bytes = static_cast<size_t>(BytesPerSample * FFMIN(-Start, Count));
memset(Dst, 0, Bytes);
Count += Start;
// Entire request was before the start of the audio
if (Count <= 0) return;
Start = 0;
Dst += Bytes;
}
CacheIterator it = Cache.begin();
while (Count > 0) {
// Find first useful cache block
while (it != Cache.end() && it->Start + it->Samples <= Start) ++it;
// Cache has the next block we want
if (it != Cache.end() && it->Start <= Start) {
int64_t SrcOffset = FFMAX(0, Start - it->Start);
int64_t DstOffset = FFMAX(0, it->Start - Start);
int64_t CopySamples = FFMIN(it->Samples - SrcOffset, Count - DstOffset);
size_t Bytes = static_cast<size_t>(CopySamples * BytesPerSample);
memcpy(Dst + DstOffset * BytesPerSample, &it->Data[SrcOffset * BytesPerSample], Bytes);
Start += CopySamples;
Count -= CopySamples;
Dst += Bytes;
++it;
}
// Decode another block
else {
if (Start < CurrentSample && SeekOffset == -1)
throw FFMS_Exception(FFMS_ERROR_SEEKING, FFMS_ERROR_CODEC, "Audio stream is not seekable");
if (SeekOffset >= 0 && (Start < CurrentSample || Start > CurrentSample + Decoded * 5)) {
TFrameInfo f;
f.SampleStart = Start;
int NewPacketNumber = std::distance(Frames.begin(), std::lower_bound(Frames.begin(), Frames.end(), f, SampleStartComp));
NewPacketNumber = FFMAX(0, NewPacketNumber - SeekOffset - 15);
while (NewPacketNumber > 0 && !Frames[NewPacketNumber].KeyFrame) --NewPacketNumber;
// Only seek forward if it'll actually result in moving forward
if (Start < CurrentSample || static_cast<size_t>(NewPacketNumber) > PacketNumber) {
PacketNumber = NewPacketNumber;
Decoded = 0;
CurrentSample = -1;
avcodec_flush_buffers(CodecContext);
Seek();
}
}
// Decode everything between the last keyframe and the block we want
while (CurrentSample + Decoded <= Start) DecodeNextBlock();
if (CurrentSample > Start)
throw FFMS_Exception(FFMS_ERROR_SEEKING, FFMS_ERROR_CODEC, "Seeking is severely broken");
CacheBlock(it, CurrentSample, Decoded, &DecodingBuffer[0]);
size_t FirstSample = static_cast<size_t>(Start - CurrentSample);
size_t Samples = static_cast<size_t>(Decoded - FirstSample);
size_t Bytes = FFMIN(Samples, static_cast<size_t>(Count)) * BytesPerSample;
memcpy(Dst, &DecodingBuffer[FirstSample * BytesPerSample], Bytes);
Start += Samples;
Count -= Samples;
Dst += Bytes;
}
}
}
size_t GetSeekablePacketNumber(FFMS_Track const& Frames, size_t PacketNumber) {
// Packets don't always have unique PTSes, so we may not be able to
// uniquely identify the packet we want. This function attempts to find
// a PTS we can seek to which will let us figure out which packet we're
// on before we get to the packet we actually wanted
// MatroskaAudioSource doesn't need this, as it seeks by byte offset
// rather than PTS. LAVF theoretically can seek by byte offset, but we
// don't use it as not all demuxers support it and it's broken in some of
// those that claim to support it
// However much we might wish to, we can't seek to before packet zero
if (PacketNumber == 0) return PacketNumber;
// Desired packet's PTS is unique, so don't do anything
if (Frames[PacketNumber].PTS != Frames[PacketNumber - 1].PTS &&
(PacketNumber + 1 == Frames.size() || Frames[PacketNumber].PTS != Frames[PacketNumber + 1].PTS))
return PacketNumber;
// When decoding, we only reliably know what packet we're at when the
// newly parsed packet has a different PTS from the previous one. As such,
// we walk backwards until we hit a different PTS and then seek to there,
// so that we can then decode until we hit the PTS group we actually wanted
// (and thereby know that we're at the first packet in the group rather
// than whatever the splitter happened to choose)
// This doesn't work if our desired packet has the same PTS as the first
// packet, but this scenario should never come up anyway; we permanently
// cache the decoded results from those packets, so there's no need to ever
// seek to them
int64_t PTS = Frames[PacketNumber].PTS;
while (PacketNumber > 0 && PTS == Frames[PacketNumber].PTS)
--PacketNumber;
return PacketNumber;
}

View File

@ -28,7 +28,6 @@ extern "C" {
#include <vector>
#include <list>
#include <sstream>
#include <memory>
#include "indexing.h"
#include "utils.h"
@ -46,93 +45,118 @@ extern "C" {
# include "guids.h"
#endif
class TAudioBlock {
public:
int64_t Start;
int64_t Samples;
uint8_t *Data;
TAudioBlock(int64_t Start, int64_t Samples, uint8_t *SrcData, size_t SrcBytes);
~TAudioBlock();
};
class TAudioCache : private std::list<TAudioBlock *> {
private:
int MaxCacheBlocks;
int BytesPerSample;
static bool AudioBlockComp(TAudioBlock *A, TAudioBlock *B);
public:
TAudioCache();
~TAudioCache();
void Initialize(int BytesPerSample, int MaxCacheBlocks);
void CacheBlock(int64_t Start, int64_t Samples, uint8_t *SrcData);
int64_t FillRequest(int64_t Start, int64_t Samples, uint8_t *Dst);
};
class FFMS_AudioSource {
friend class FFSourceResources<FFMS_AudioSource>;
struct AudioBlock {
int64_t Age;
int64_t Start;
int64_t Samples;
std::vector<uint8_t> Data;
AudioBlock(int64_t Start, int64_t Samples, uint8_t *SrcData, size_t SrcBytes)
: Start(Start)
, Samples(Samples)
, Data(SrcData, SrcData + SrcBytes)
{
static int64_t Now = 0;
Age = Now++;
}
};
typedef std::list<AudioBlock>::iterator CacheIterator;
// delay in samples to apply to the audio
int64_t Delay;
// cache of decoded audio blocks
std::list<AudioBlock> Cache;
// max size of the cache in blocks
size_t MaxCacheBlocks;
// pointer to last element of the cache which should never be deleted
CacheIterator CacheNoDelete;
// bytes per sample * number of channels
size_t BytesPerSample;
// Number of samples stored in the decoding buffer
size_t Decoded;
// Insert a block into the cache
void CacheBlock(CacheIterator &pos, int64_t Start, size_t Samples, uint8_t *SrcData);
// Called after seeking
virtual void Seek() { };
// Read the next packet from the file
virtual bool ReadPacket(AVPacket *) = 0;
virtual void FreePacket(AVPacket *) { }
protected:
TAudioCache AudioCache;
// First sample which is stored in the decoding buffer
int64_t CurrentSample;
uint8_t *DecodingBuffer;
// Next packet to be read
size_t PacketNumber;
// Current audio frame
TFrameInfo *CurrentFrame;
// Track which this corresponds to
int TrackNumber;
// Number of packets which the demuxer requires to know where it is
// If -1, seeking is assumed to be impossible
int SeekOffset;
// Buffer which audio is decoded into
AlignedBuffer<uint8_t> DecodingBuffer;
FFMS_Track Frames;
AVCodecContext *CodecContext;
int AudioTrack;
FFCodecContext CodecContext;
FFMS_AudioProperties AP;
virtual void Free(bool CloseCodec) = 0;
void DecodeNextBlock();
// Initialization which has to be done after the codec is opened
void Init(FFMS_Index &Index, int DelayMode);
FFMS_AudioSource(const char *SourceFile, FFMS_Index &Index, int Track);
public:
FFMS_AudioSource(const char *SourceFile, FFMS_Index *Index, int Track);
virtual ~FFMS_AudioSource();
virtual ~FFMS_AudioSource() { };
FFMS_Track *GetTrack() { return &Frames; }
const FFMS_AudioProperties& GetAudioProperties() { return AP; }
virtual void GetAudio(void *Buf, int64_t Start, int64_t Count) = 0;
void GetAudioCheck(int64_t Start, int64_t Count);
const FFMS_AudioProperties& GetAudioProperties() const { return AP; }
void GetAudio(void *Buf, int64_t Start, int64_t Count);
};
class FFLAVFAudio : public FFMS_AudioSource {
private:
AVFormatContext *FormatContext;
FFSourceResources<FFMS_AudioSource> Res;
void DecodeNextAudioBlock(int64_t *Count);
void Free(bool CloseCodec);
bool ReadPacket(AVPacket *);
void FreePacket(AVPacket *);
void Seek();
public:
FFLAVFAudio(const char *SourceFile, int Track, FFMS_Index *Index);
void GetAudio(void *Buf, int64_t Start, int64_t Count);
FFLAVFAudio(const char *SourceFile, int Track, FFMS_Index &Index, int DelayMode);
~FFLAVFAudio();
};
class FFMatroskaAudio : public FFMS_AudioSource {
private:
MatroskaFile *MF;
MatroskaReaderContext MC;
CompressedStream *CS;
TrackInfo *TI;
std::auto_ptr<TrackCompressionContext> TCC;
char ErrorMessage[256];
FFSourceResources<FFMS_AudioSource> Res;
size_t PacketNumber;
void DecodeNextAudioBlock(int64_t *Count);
void Free(bool CloseCodec);
bool ReadPacket(AVPacket *);
public:
FFMatroskaAudio(const char *SourceFile, int Track, FFMS_Index *Index);
void GetAudio(void *Buf, int64_t Start, int64_t Count);
FFMatroskaAudio(const char *SourceFile, int Track, FFMS_Index &Index, int DelayMode);
~FFMatroskaAudio();
};
#ifdef HAALISOURCE
class FFHaaliAudio : public FFMS_AudioSource {
private:
CComPtr<IMMContainer> pMMC;
std::vector<uint8_t> CodecPrivate;
FFSourceResources<FFMS_AudioSource> Res;
CComPtr<IMMFrame> pMMF;
bool ReadPacket(AVPacket *);
void Seek();
void DecodeNextAudioBlock(int64_t *AFirstStartTime, int64_t *Count);
void Free(bool CloseCodec);
public:
FFHaaliAudio(const char *SourceFile, int Track, FFMS_Index *Index, enum FFMS_Sources SourceMode);
void GetAudio(void *Buf, int64_t Start, int64_t Count);
FFHaaliAudio(const char *SourceFile, int Track, FFMS_Index &Index, enum FFMS_Sources SourceMode, int DelayMode);
};
#endif // HAALISOURCE
size_t GetSeekablePacketNumber(FFMS_Track const& Frames, size_t PacketNumber);
#endif

View File

@ -25,52 +25,73 @@
#include "audiosource.h"
#include "indexing.h"
extern "C" {
#include <libavutil/pixdesc.h>
}
#ifdef FFMS_WIN_DEBUG
# include <windows.h>
#endif
static bool FFmpegInited = false;
bool HasHaaliMPEG = false;
bool HasHaaliOGG = false;
int CPUFeatures = 0;
bool GlobalUseUTF8Paths = false;
#ifdef FFMS_WIN_DEBUG
extern "C" int av_log_level;
void av_log_windebug_callback(void* ptr, int level, const char* fmt, va_list vl) {
static int print_prefix=1;
static int count;
static char line[1024], prev[1024];
AVClass* avc= ptr ? *(AVClass**)ptr : NULL;
if(level>av_log_level)
return;
#undef fprintf
if(print_prefix && avc) {
snprintf(line, sizeof(line), "[%s @ %p]", avc->item_name(ptr), ptr);
}else
line[0]=0;
static int print_prefix=1;
static int count;
static char line[1024] = {0}, prev[1024] = {0};
AVClass* avc = ptr ? *(AVClass**)ptr : NULL;
if(level > av_log_level)
return;
vsnprintf(line + strlen(line), sizeof(line) - strlen(line), fmt, vl);
int written = 0;
if(print_prefix && avc) {
written = snprintf(line, sizeof(line), "[%s @ %p]", avc->item_name(ptr), ptr);
}
print_prefix= line[strlen(line)-1] == '\n';
if(print_prefix && !strcmp(line, prev)){
count++;
return;
}
if(count>0){
written += vsnprintf(line + written, sizeof(line) - written, fmt, vl);
print_prefix = line[written-1] == '\n';
line[sizeof(line) - 1] = 0;
if(print_prefix && !strcmp(line, prev)){
count++;
return;
}
if(count > 0){
std::stringstream ss;
ss << " Last message repeated " << count << " times\n";
OutputDebugStringA(ss.str().c_str());
count=0;
}
count = 0;
}
OutputDebugStringA(line);
strcpy(prev, line);
strcpy(prev, line);
}
#endif
FFMS_API(void) FFMS_Init(int CPUFeatures) {
FFMS_API(void) FFMS_Init(int CPUFeatures, int UseUTF8Paths) {
if (!FFmpegInited) {
av_register_all();
#ifdef _WIN32
if (UseUTF8Paths) {
ffms_patch_lavf_file_open();
GlobalUseUTF8Paths = true;
}
else {
GlobalUseUTF8Paths = false;
}
#else
GlobalUseUTF8Paths = false;
#endif
#ifdef FFMS_WIN_DEBUG
av_log_set_callback(av_log_windebug_callback);
av_log_set_level(AV_LOG_INFO);
@ -98,6 +119,8 @@ FFMS_API(void) FFMS_SetLogLevel(int Level) {
}
FFMS_API(FFMS_VideoSource *) FFMS_CreateVideoSource(const char *SourceFile, int Track, FFMS_Index *Index, int Threads, int SeekMode, FFMS_ErrorInfo *ErrorInfo) {
if (Threads < 1)
Threads = 1;
try {
switch (Index->Decoder) {
case FFMS_SOURCE_LAVF:
@ -123,21 +146,21 @@ FFMS_API(FFMS_VideoSource *) FFMS_CreateVideoSource(const char *SourceFile, int
}
}
FFMS_API(FFMS_AudioSource *) FFMS_CreateAudioSource(const char *SourceFile, int Track, FFMS_Index *Index, FFMS_ErrorInfo *ErrorInfo) {
FFMS_API(FFMS_AudioSource *) FFMS_CreateAudioSource(const char *SourceFile, int Track, FFMS_Index *Index, int DelayMode, FFMS_ErrorInfo *ErrorInfo) {
try {
switch (Index->Decoder) {
case FFMS_SOURCE_LAVF:
return new FFLAVFAudio(SourceFile, Track, Index);
return new FFLAVFAudio(SourceFile, Track, *Index, DelayMode);
case FFMS_SOURCE_MATROSKA:
return new FFMatroskaAudio(SourceFile, Track, Index);
return new FFMatroskaAudio(SourceFile, Track, *Index, DelayMode);
#ifdef HAALISOURCE
case FFMS_SOURCE_HAALIMPEG:
if (HasHaaliMPEG)
return new FFHaaliAudio(SourceFile, Track, Index, FFMS_SOURCE_HAALIMPEG);
return new FFHaaliAudio(SourceFile, Track, *Index, FFMS_SOURCE_HAALIMPEG, DelayMode);
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_NOT_AVAILABLE, "Haali MPEG/TS source unavailable");
case FFMS_SOURCE_HAALIOGG:
if (HasHaaliOGG)
return new FFHaaliAudio(SourceFile, Track, Index, FFMS_SOURCE_HAALIOGG);
return new FFHaaliAudio(SourceFile, Track, *Index, FFMS_SOURCE_HAALIOGG, DelayMode);
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_NOT_AVAILABLE, "Haali OGG/OGM source unavailable");
#endif
default:
@ -227,6 +250,10 @@ FFMS_API(void) FFMS_DestroyIndex(FFMS_Index *Index) {
delete Index;
}
FFMS_API(int) FFMS_GetSourceType(FFMS_Index *Index) {
return Index->Decoder;
}
FFMS_API(int) FFMS_GetFirstTrackOfType(FFMS_Index *Index, int TrackType, FFMS_ErrorInfo *ErrorInfo) {
ClearErrorInfo(ErrorInfo);
for (int i = 0; i < static_cast<int>(Index->size()); i++)
@ -351,9 +378,13 @@ FFMS_API(int) FFMS_DefaultAudioFilename(const char *SourceFile, int Track, const
}
FFMS_API(FFMS_Indexer *) FFMS_CreateIndexer(const char *SourceFile, FFMS_ErrorInfo *ErrorInfo) {
return FFMS_CreateIndexerWithDemuxer(SourceFile, FFMS_SOURCE_DEFAULT, ErrorInfo);
}
FFMS_API(FFMS_Indexer *) FFMS_CreateIndexerWithDemuxer(const char *SourceFile, int Demuxer, FFMS_ErrorInfo *ErrorInfo) {
ClearErrorInfo(ErrorInfo);
try {
return FFMS_Indexer::CreateIndexer(SourceFile);
return FFMS_Indexer::CreateIndexer(SourceFile, static_cast<FFMS_Sources>(Demuxer));
} catch (FFMS_Exception &e) {
e.CopyOut(ErrorInfo);
return NULL;
@ -419,9 +450,10 @@ FFMS_API(int) FFMS_WriteIndex(const char *IndexFile, FFMS_Index *Index, FFMS_Err
}
FFMS_API(int) FFMS_GetPixFmt(const char *Name) {
return avcodec_get_pix_fmt(Name);
return av_get_pix_fmt(Name);
}
FFMS_API(int) FFMS_GetPresentSources() {
int Sources = FFMS_SOURCE_LAVF | FFMS_SOURCE_MATROSKA;
#ifdef HAALISOURCE
@ -440,3 +472,11 @@ FFMS_API(int) FFMS_GetEnabledSources() {
Sources |= FFMS_SOURCE_HAALIOGG;
return Sources;
}
FFMS_API(const char *) FFMS_GetFormatNameI(FFMS_Indexer *Indexer) {
return Indexer->GetFormatName();
}
FFMS_API(int) FFMS_GetSourceTypeI(FFMS_Indexer *Indexer) {
return Indexer->GetSourceType();
}

View File

@ -19,20 +19,29 @@
// THE SOFTWARE.
#include "indexing.h"
#include <iostream>
#include <fstream>
#include <algorithm>
#include <fstream>
#include <iostream>
#include <limits>
extern "C" {
#include <libavutil/mem.h>
#include <libavutil/sha1.h>
#include <zlib.h>
}
#undef max
#define INDEXID 0x53920873
extern bool HasHaaliMPEG;
extern bool HasHaaliOGG;
#ifndef FFMS_USE_POSTPROC
unsigned postproc_version() { return 0; } // ugly workaround to avoid lots of ifdeffing
#endif // FFMS_USE_POSTPROC
struct IndexHeader {
uint32_t Id;
uint32_t Version;
@ -48,17 +57,20 @@ struct IndexHeader {
};
struct TrackHeader {
uint32_t TT;
uint32_t Frames;
int64_t Num;
int64_t Den;
uint32_t TT;
uint32_t Frames;
int64_t Num;
int64_t Den;
uint32_t UseDTS;
};
SharedVideoContext::SharedVideoContext(bool FreeCodecContext) {
CodecContext = NULL;
Parser = NULL;
CS = NULL;
BitStreamFilter = NULL;
this->FreeCodecContext = FreeCodecContext;
TCC = NULL;
}
SharedVideoContext::~SharedVideoContext() {
@ -67,19 +79,17 @@ SharedVideoContext::~SharedVideoContext() {
if (FreeCodecContext)
av_freep(&CodecContext);
}
if (Parser)
av_parser_close(Parser);
if (CS)
cs_Destroy(CS);
av_parser_close(Parser);
if (BitStreamFilter)
av_bitstream_filter_close(BitStreamFilter);
delete TCC;
}
SharedAudioContext::SharedAudioContext(bool FreeCodecContext) {
W64Writer = NULL;
CodecContext = NULL;
CurrentSample = 0;
CS = NULL;
TCC = NULL;
this->FreeCodecContext = FreeCodecContext;
}
@ -90,9 +100,7 @@ SharedAudioContext::~SharedAudioContext() {
if (FreeCodecContext)
av_freep(&CodecContext);
}
if (CS)
cs_Destroy(CS);
delete TCC;
}
TFrameInfo::TFrameInfo() {
@ -113,22 +121,20 @@ TFrameInfo TFrameInfo::VideoFrameInfo(int64_t PTS, int RepeatPict, bool KeyFrame
return TFrameInfo(PTS, 0, 0, RepeatPict, KeyFrame, FilePos, FrameSize);
}
TFrameInfo TFrameInfo::AudioFrameInfo(int64_t PTS, int64_t SampleStart, unsigned int SampleCount, bool KeyFrame, int64_t FilePos, unsigned int FrameSize) {
return TFrameInfo(PTS, SampleStart, SampleCount, 0, KeyFrame, FilePos, FrameSize);
TFrameInfo TFrameInfo::AudioFrameInfo(int64_t PTS, int64_t SampleStart, int64_t SampleCount, bool KeyFrame, int64_t FilePos, unsigned int FrameSize) {
return TFrameInfo(PTS, SampleStart, static_cast<unsigned int>(SampleCount), 0, KeyFrame, FilePos, FrameSize);
}
void FFMS_Track::WriteTimecodes(const char *TimecodeFile) {
ffms_fstream Timecodes(TimecodeFile, std::ios::out | std::ios::trunc);
if (!Timecodes.is_open()) {
std::ostringstream buf;
buf << "Failed to open '" << TimecodeFile << "' for writing";
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
}
if (!Timecodes.is_open())
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ,
std::string("Failed to open '") + TimecodeFile + "' for writing");
Timecodes << "# timecode format v2\n";
for (iterator Cur=begin(); Cur!=end(); Cur++)
for (iterator Cur = begin(); Cur != end(); ++Cur)
Timecodes << std::fixed << ((Cur->PTS * TB.Num) / (double)TB.Den) << "\n";
}
@ -139,108 +145,91 @@ int FFMS_Track::FrameFromPTS(int64_t PTS) {
return -1;
}
int FFMS_Track::ClosestFrameFromPTS(int64_t PTS) {
int Frame = 0;
int64_t BestDiff = 0xFFFFFFFFFFFFFFLL; // big number
for (int i = 0; i < static_cast<int>(size()); i++) {
int64_t CurrentDiff = FFABS(at(i).PTS - PTS);
if (CurrentDiff < BestDiff) {
BestDiff = CurrentDiff;
Frame = i;
}
}
return Frame;
}
int FFMS_Track::FindClosestVideoKeyFrame(int Frame) {
Frame = FFMIN(FFMAX(Frame, 0), static_cast<int>(size()) - 1);
for (int i = Frame; i > 0; i--)
if (at(i).KeyFrame)
return i;
return 0;
}
int FFMS_Track::FindClosestAudioKeyFrame(int64_t Sample) {
for (size_t i = 0; i < size(); i++) {
if (at(i).SampleStart == Sample && at(i).KeyFrame)
return i;
else if (at(i).SampleStart > Sample && at(i).KeyFrame)
return i - 1;
}
return size() - 1;
}
FFMS_Track::FFMS_Track() {
this->TT = FFMS_TYPE_UNKNOWN;
this->TB.Num = 0;
this->TB.Den = 0;
}
FFMS_Track::FFMS_Track(int64_t Num, int64_t Den, FFMS_TrackType TT) {
this->TT = TT;
this->TB.Num = Num;
this->TB.Den = Den;
}
void FFMS_Index::CalculateFileSignature(const char *Filename, int64_t *Filesize, uint8_t Digest[20]) {
FILE *SFile = ffms_fopen(Filename,"rb");
if (SFile == NULL) {
std::ostringstream buf;
buf << "Failed to open '" << Filename << "' for hashing";
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
}
const int BlockSize = 1024*1024;
std::vector<uint8_t> FileBuffer(BlockSize);
std::vector<uint8_t> ctxmem(av_sha1_size);
AVSHA1 *ctx = (AVSHA1 *)&ctxmem[0];
av_sha1_init(ctx);
memset(&FileBuffer[0], 0, BlockSize);
fread(&FileBuffer[0], 1, BlockSize, SFile);
if (ferror(SFile) && !feof(SFile)) {
av_sha1_final(ctx, Digest);
fclose(SFile);
std::ostringstream buf;
buf << "Failed to read '" << Filename << "' for hashing";
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
}
av_sha1_update(ctx, &FileBuffer[0], BlockSize);
fseeko(SFile, -BlockSize, SEEK_END);
memset(&FileBuffer[0], 0, BlockSize);
fread(&FileBuffer[0], 1, BlockSize, SFile);
if (ferror(SFile) && !feof(SFile)) {
av_sha1_final(ctx, Digest);
fclose(SFile);
std::ostringstream buf;
buf << "Failed to seek with offset " << BlockSize << " from file end in '" << Filename << "' for hashing";
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
}
av_sha1_update(ctx, &FileBuffer[0], BlockSize);
fseeko(SFile, 0, SEEK_END);
if (ferror(SFile)) {
av_sha1_final(ctx, Digest);
fclose(SFile);
std::ostringstream buf;
buf << "Failed to seek to end of '" << Filename << "' for hashing";
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
}
*Filesize = ftello(SFile);
fclose(SFile);
av_sha1_final(ctx, Digest);
}
static bool PTSComparison(TFrameInfo FI1, TFrameInfo FI2) {
return FI1.PTS < FI2.PTS;
}
int FFMS_Track::ClosestFrameFromPTS(int64_t PTS) {
TFrameInfo F;
F.PTS = PTS;
iterator Pos = std::lower_bound(begin(), end(), F, PTSComparison);
int Frame = std::distance(begin(), Pos);
if (Pos == begin() || FFABS(Pos->PTS - PTS) <= FFABS((Pos - 1)->PTS - PTS))
return Frame;
return Frame - 1;
}
int FFMS_Track::FindClosestVideoKeyFrame(int Frame) {
Frame = FFMIN(FFMAX(Frame, 0), static_cast<int>(size()) - 1);
for (; Frame > 0 && !at(Frame).KeyFrame; Frame--) ;
for (; Frame > 0 && !at(at(Frame).OriginalPos).KeyFrame; Frame--) ;
return Frame;
}
FFMS_Track::FFMS_Track() {
this->TT = FFMS_TYPE_UNKNOWN;
this->TB.Num = 0;
this->TB.Den = 0;
this->UseDTS = false;
}
FFMS_Track::FFMS_Track(int64_t Num, int64_t Den, FFMS_TrackType TT, bool UseDTS) {
this->TT = TT;
this->TB.Num = Num;
this->TB.Den = Den;
this->UseDTS = UseDTS;
}
void FFMS_Index::CalculateFileSignature(const char *Filename, int64_t *Filesize, uint8_t Digest[20]) {
FILE *SFile = ffms_fopen(Filename,"rb");
if (!SFile)
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ,
std::string("Failed to open '") + Filename + "' for hashing");
std::vector<uint8_t> FileBuffer(1024*1024, 0);
std::vector<uint8_t> ctxmem(av_sha1_size);
AVSHA1 *ctx = (AVSHA1 *)(&ctxmem[0]);
av_sha1_init(ctx);
try {
fread(&FileBuffer[0], 1, FileBuffer.size(), SFile);
if (ferror(SFile) && !feof(SFile))
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ,
std::string("Failed to read '") + Filename + "' for hashing");
av_sha1_update(ctx, &FileBuffer[0], FileBuffer.size());
fseeko(SFile, -(int)FileBuffer.size(), SEEK_END);
std::fill(FileBuffer.begin(), FileBuffer.end(), 0);
fread(&FileBuffer[0], 1, FileBuffer.size(), SFile);
if (ferror(SFile) && !feof(SFile)) {
std::ostringstream buf;
buf << "Failed to seek with offset " << FileBuffer.size() << " from file end in '" << Filename << "' for hashing";
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
}
av_sha1_update(ctx, &FileBuffer[0], FileBuffer.size());
fseeko(SFile, 0, SEEK_END);
if (ferror(SFile))
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ,
std::string("Failed to seek to end of '") + Filename + "' for hashing");
*Filesize = ftello(SFile);
}
catch (...) {
fclose(SFile);
av_sha1_final(ctx, Digest);
throw;
}
fclose(SFile);
av_sha1_final(ctx, Digest);
}
void FFMS_Index::Sort() {
for (FFMS_Index::iterator Cur=begin(); Cur!=end(); Cur++) {
for (FFMS_Index::iterator Cur = begin(); Cur != end(); ++Cur) {
if (Cur->size() > 2 && Cur->front().PTS >= Cur->back().PTS) Cur->pop_back();
for (size_t i = 0; i < Cur->size(); i++)
Cur->at(i).OriginalPos = i;
@ -265,13 +254,42 @@ bool FFMS_Index::CompareFileSignature(const char *Filename) {
return (CFilesize == Filesize && !memcmp(CDigest, Digest, sizeof(Digest)));
}
#define CHUNK 65536
static unsigned int z_def(ffms_fstream *IndexStream, z_stream *stream, void *in, size_t in_sz, int finish) {
unsigned int total = 0, have;
int ret;
char out[CHUNK];
if (!finish && (in_sz == 0 || in == NULL)) return 0;
stream->next_in = (Bytef*) in;
stream->avail_in = in_sz;
do {
do {
stream->avail_out = CHUNK;
stream->next_out = (Bytef*) out;
ret = deflate(stream, finish ? Z_FINISH : Z_NO_FLUSH);
have = CHUNK - stream->avail_out;
if (have) IndexStream->write(out, have);
total += have;
} while (stream->avail_out == 0);
} while (finish && ret != Z_STREAM_END);
if (finish) deflateEnd(stream);
return total;
}
void FFMS_Index::WriteIndex(const char *IndexFile) {
ffms_fstream IndexStream(IndexFile, std::ios::out | std::ios::binary | std::ios::trunc);
if (!IndexStream.is_open()) {
std::ostringstream buf;
buf << "Failed to open '" << IndexFile << "' for writing";
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
if (!IndexStream.is_open())
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ,
std::string("Failed to open '") + IndexFile + "' for writing");
z_stream stream;
memset(&stream, 0, sizeof(z_stream));
if (deflateInit(&stream, 9) != Z_OK) {
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, "Failed to initialize zlib");
}
// Write the index file header
@ -288,51 +306,99 @@ void FFMS_Index::WriteIndex(const char *IndexFile) {
IH.FileSize = Filesize;
memcpy(IH.FileSignature, Digest, sizeof(Digest));
IndexStream.write(reinterpret_cast<char *>(&IH), sizeof(IH));
for (unsigned int i = 0; i < IH.Tracks; i++) {
TrackHeader TH;
TH.TT = at(i).TT;
TH.Frames = at(i).size();
TH.Num = at(i).TB.Num;;
TH.Den = at(i).TB.Den;
z_def(&IndexStream, &stream, &IH, sizeof(IndexHeader), 0);
IndexStream.write(reinterpret_cast<char *>(&TH), sizeof(TH));
IndexStream.write(reinterpret_cast<char *>(FFMS_GET_VECTOR_PTR(at(i))), TH.Frames * sizeof(TFrameInfo));
for (unsigned int i = 0; i < IH.Tracks; i++) {
FFMS_Track &ctrack = at(i);
TrackHeader TH;
TH.TT = ctrack.TT;
TH.Frames = ctrack.size();
TH.Num = ctrack.TB.Num;;
TH.Den = ctrack.TB.Den;
TH.UseDTS = ctrack.UseDTS;
FFMS_Track temptrack;
temptrack.resize(TH.Frames);
if (TH.Frames)
temptrack[0] = ctrack[0];
for (size_t j = 1; j < ctrack.size(); j++) {
temptrack[j] = ctrack[j];
temptrack[j].FilePos = ctrack[j].FilePos - ctrack[j - 1].FilePos;
temptrack[j].OriginalPos = ctrack[j].OriginalPos - ctrack[j - 1].OriginalPos;
temptrack[j].PTS = ctrack[j].PTS - ctrack[j - 1].PTS;
temptrack[j].SampleStart = ctrack[j].SampleStart - ctrack[j - 1].SampleStart;
}
z_def(&IndexStream, &stream, &TH, sizeof(TrackHeader), 0);
if (TH.Frames)
z_def(&IndexStream, &stream, FFMS_GET_VECTOR_PTR(temptrack), TH.Frames * sizeof(TFrameInfo), 0);
}
z_def(&IndexStream, &stream, NULL, 0, 1);
}
static unsigned int z_inf(ffms_fstream *Index, z_stream *stream, void *in, size_t in_sz, void *out, size_t out_sz) {
if (out_sz == 0 || out == 0) return 0;
stream->next_out = (Bytef*) out;
stream->avail_out = out_sz;
do {
if (stream->avail_in) memmove(in, stream->next_in, stream->avail_in);
Index->read(((char*)in) + stream->avail_in, in_sz - stream->avail_in);
stream->next_in = (Bytef*) in;
stream->avail_in += Index->gcount();
switch (inflate(stream, Z_SYNC_FLUSH)) {
case Z_NEED_DICT:
inflateEnd(stream);
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, "Failed to read data: Dictionary error.");
case Z_DATA_ERROR:
inflateEnd(stream);
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, "Failed to read data: Data error.");
case Z_MEM_ERROR:
inflateEnd(stream);
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, "Failed to read data: Memory error.");
case Z_STREAM_END:
inflateEnd(stream);
return out_sz - stream->avail_out;
}
} while (stream->avail_out);
return out_sz;
}
void FFMS_Index::ReadIndex(const char *IndexFile) {
ffms_fstream Index(IndexFile, std::ios::in | std::ios::binary);
if (!Index.is_open()) {
std::ostringstream buf;
buf << "Failed to open '" << IndexFile << "' for reading";
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
}
if (!Index.is_open())
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ,
std::string("Failed to open '") + IndexFile + "' for reading");
z_stream stream;
memset(&stream, 0, sizeof(z_stream));
unsigned char in[CHUNK];
if (inflateInit(&stream) != Z_OK)
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ,
"Failed to initialize zlib");
// Read the index file header
IndexHeader IH;
Index.read(reinterpret_cast<char *>(&IH), sizeof(IH));
if (IH.Id != INDEXID) {
std::ostringstream buf;
buf << "'" << IndexFile << "' is not a valid index file";
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
}
z_inf(&Index, &stream, &in, CHUNK, &IH, sizeof(IndexHeader));
if (IH.Version != FFMS_VERSION) {
std::ostringstream buf;
buf << "'" << IndexFile << "' is not the expected index version";
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
}
if (IH.Id != INDEXID)
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ,
std::string("'") + IndexFile + "' is not a valid index file");
if (IH.Version != FFMS_VERSION)
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ,
std::string("'") + IndexFile + "' is the expected index version");
if (IH.LAVUVersion != avutil_version() || IH.LAVFVersion != avformat_version() ||
IH.LAVCVersion != avcodec_version() || IH.LSWSVersion != swscale_version() ||
IH.LPPVersion != postproc_version()) {
std::ostringstream buf;
buf << "A different FFmpeg build was used to create '" << IndexFile << "'";
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
}
IH.LPPVersion != postproc_version())
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ,
std::string("A different FFmpeg build was used to create '") + IndexFile + "'");
if (!(IH.Decoder & FFMS_GetEnabledSources()))
throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_NOT_AVAILABLE,
@ -345,24 +411,33 @@ void FFMS_Index::ReadIndex(const char *IndexFile) {
try {
for (unsigned int i = 0; i < IH.Tracks; i++) {
TrackHeader TH;
Index.read(reinterpret_cast<char *>(&TH), sizeof(TH));
push_back(FFMS_Track(TH.Num, TH.Den, static_cast<FFMS_TrackType>(TH.TT)));
z_inf(&Index, &stream, &in, CHUNK, &TH, sizeof(TrackHeader));
push_back(FFMS_Track(TH.Num, TH.Den, static_cast<FFMS_TrackType>(TH.TT), TH.UseDTS != 0));
FFMS_Track &ctrack = at(i);
if (TH.Frames) {
at(i).resize(TH.Frames);
Index.read(reinterpret_cast<char *>(FFMS_GET_VECTOR_PTR(at(i))), TH.Frames * sizeof(TFrameInfo));
ctrack.resize(TH.Frames);
z_inf(&Index, &stream, &in, CHUNK, FFMS_GET_VECTOR_PTR(ctrack), TH.Frames * sizeof(TFrameInfo));
}
for (size_t j = 1; j < ctrack.size(); j++) {
ctrack[j].FilePos = ctrack[j].FilePos + ctrack[j - 1].FilePos;
ctrack[j].OriginalPos = ctrack[j].OriginalPos + ctrack[j - 1].OriginalPos;
ctrack[j].PTS = ctrack[j].PTS + ctrack[j - 1].PTS;
ctrack[j].SampleStart = ctrack[j].SampleStart + ctrack[j - 1].SampleStart;
}
}
} catch (...) {
std::ostringstream buf;
buf << "Unknown error while reading index information in '" << IndexFile << "'";
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
}
catch (FFMS_Exception const&) {
throw;
}
catch (...) {
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ,
std::string("Unknown error while reading index information in '") + IndexFile + "'");
}
}
FFMS_Index::FFMS_Index() {
// this comment documents nothing
}
FFMS_Index::FFMS_Index(int64_t Filesize, uint8_t Digest[20]) {
@ -387,7 +462,7 @@ void FFMS_Indexer::SetErrorHandling(int ErrorHandling) {
}
void FFMS_Indexer::SetProgressCallback(TIndexCallback IC, void *ICPrivate) {
this->IC = IC;
this->IC = IC;
this->ICPrivate = ICPrivate;
}
@ -396,38 +471,67 @@ void FFMS_Indexer::SetAudioNameCallback(TAudioNameCallback ANC, void *ANCPrivate
this->ANCPrivate = ANCPrivate;
}
FFMS_Indexer *FFMS_Indexer::CreateIndexer(const char *Filename) {
FFMS_Indexer *FFMS_Indexer::CreateIndexer(const char *Filename, enum FFMS_Sources Demuxer) {
AVFormatContext *FormatContext = NULL;
if (av_open_input_file(&FormatContext, Filename, NULL, 0, NULL) != 0) {
std::ostringstream buf;
buf << "Can't open '" << Filename << "'";
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
}
if (av_open_input_file(&FormatContext, Filename, NULL, 0, NULL) != 0)
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ,
std::string("Can't open '") + Filename + "'");
// Do matroska indexing instead?
if (!strcmp(FormatContext->iformat->name, "matroska")) {
av_close_input_file(FormatContext);
return new FFMatroskaIndexer(Filename);
}
// Demuxer was not forced, probe for the best one to use
if (Demuxer == FFMS_SOURCE_DEFAULT) {
// Do matroska indexing instead?
if (!strncmp(FormatContext->iformat->name, "matroska", 8)) {
av_close_input_file(FormatContext);
return new FFMatroskaIndexer(Filename);
}
#ifdef HAALISOURCE
// Do haali ts indexing instead?
if (HasHaaliMPEG && (!strcmp(FormatContext->iformat->name, "mpeg") || !strcmp(FormatContext->iformat->name, "mpegts"))) {
av_close_input_file(FormatContext);
return new FFHaaliIndexer(Filename, FFMS_SOURCE_HAALIMPEG);
}
// Do haali ts indexing instead?
if (HasHaaliMPEG && (!strcmp(FormatContext->iformat->name, "mpeg") || !strcmp(FormatContext->iformat->name, "mpegts"))) {
av_close_input_file(FormatContext);
return new FFHaaliIndexer(Filename, FFMS_SOURCE_HAALIMPEG);
}
if (HasHaaliOGG && !strcmp(FormatContext->iformat->name, "ogg")) {
av_close_input_file(FormatContext);
return new FFHaaliIndexer(Filename, FFMS_SOURCE_HAALIOGG);
}
if (HasHaaliOGG && !strcmp(FormatContext->iformat->name, "ogg")) {
av_close_input_file(FormatContext);
return new FFHaaliIndexer(Filename, FFMS_SOURCE_HAALIOGG);
}
#endif
return new FFLAVFIndexer(Filename, FormatContext);
return new FFLAVFIndexer(Filename, FormatContext);
}
// someone forced a demuxer, use it
if (Demuxer != FFMS_SOURCE_LAVF)
av_close_input_file(FormatContext);
#if !defined(HAALISOURCE)
if (Demuxer == FFMS_SOURCE_HAALIOGG || Demuxer == FFMS_SOURCE_HAALIMPEG) {
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_NOT_AVAILABLE, "Your binary was not compiled with support for Haali's DirectShow parsers");
}
#endif // !defined(HAALISOURCE)
switch (Demuxer) {
case FFMS_SOURCE_LAVF:
return new FFLAVFIndexer(Filename, FormatContext);
#ifdef HAALISOURCE
case FFMS_SOURCE_HAALIOGG:
if (!HasHaaliOGG)
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_NOT_AVAILABLE, "Haali's Ogg parser is not available");
return new FFHaaliIndexer(Filename, FFMS_SOURCE_HAALIOGG);
case FFMS_SOURCE_HAALIMPEG:
if (!HasHaaliMPEG)
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_NOT_AVAILABLE, "Haali's MPEG PS/TS parser is not available");
return new FFHaaliIndexer(Filename, FFMS_SOURCE_HAALIMPEG);
#endif
case FFMS_SOURCE_MATROSKA:
return new FFMatroskaIndexer(Filename);
default:
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_INVALID_ARGUMENT, "Invalid demuxer requested");
}
}
FFMS_Indexer::FFMS_Indexer(const char *Filename) {
FFMS_Indexer::FFMS_Indexer(const char *Filename) : DecodingBuffer(AVCODEC_MAX_AUDIO_FRAME_SIZE * 10) {
IndexMask = 0;
DumpMask = 0;
ErrorHandling = FFMS_IEH_CLEAR_TRACK;
@ -435,36 +539,99 @@ FFMS_Indexer::FFMS_Indexer(const char *Filename) {
ICPrivate = NULL;
ANC = NULL;
ANCPrivate = NULL;
DecodingBuffer = (int16_t *) av_malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE * 5 * sizeof(*DecodingBuffer));
if (!DecodingBuffer)
throw std::bad_alloc();
FFMS_Index::CalculateFileSignature(Filename, &Filesize, Digest);
}
FFMS_Indexer::~FFMS_Indexer() {
av_free(DecodingBuffer);
}
void FFMS_Indexer::WriteAudio(SharedAudioContext &AudioContext, FFMS_Index *Index, int Track, int DBSize) {
// Delay writer creation until after an audio frame has been decoded. This ensures that all parameters are known when writing the headers.
if (DBSize > 0) {
if (!AudioContext.W64Writer) {
FFMS_AudioProperties AP;
FillAP(AP, AudioContext.CodecContext, (*Index)[Track]);
int FNSize = (*ANC)(SourceFile, Track, &AP, NULL, 0, ANCPrivate);
std::vector<char> WName(FNSize);
(*ANC)(SourceFile, Track, &AP, &WName[0], FNSize, ANCPrivate);
std::string WN(&WName[0]);
try {
AudioContext.W64Writer = new Wave64Writer(WN.c_str(), av_get_bits_per_sample_format(AudioContext.CodecContext->sample_fmt),
AudioContext.CodecContext->channels, AudioContext.CodecContext->sample_rate, (AudioContext.CodecContext->sample_fmt == SAMPLE_FMT_FLT) || (AudioContext.CodecContext->sample_fmt == SAMPLE_FMT_DBL));
} catch (...) {
throw FFMS_Exception(FFMS_ERROR_WAVE_WRITER, FFMS_ERROR_FILE_WRITE,
"Failed to write wave data");
}
if (DBSize <= 0) return;
if (!AudioContext.W64Writer) {
FFMS_AudioProperties AP;
FillAP(AP, AudioContext.CodecContext, (*Index)[Track]);
int FNSize = (*ANC)(SourceFile, Track, &AP, NULL, 0, ANCPrivate);
if (FNSize <= 0) {
DumpMask = DumpMask & ~(1 << Track);
return;
}
AudioContext.W64Writer->WriteData(DecodingBuffer, DBSize);
std::vector<char> WName(FNSize);
(*ANC)(SourceFile, Track, &AP, &WName[0], FNSize, ANCPrivate);
std::string WN(&WName[0]);
try {
AudioContext.W64Writer =
new Wave64Writer(WN.c_str(),
av_get_bits_per_sample_fmt(AudioContext.CodecContext->sample_fmt),
AudioContext.CodecContext->channels,
AudioContext.CodecContext->sample_rate,
(AudioContext.CodecContext->sample_fmt == AV_SAMPLE_FMT_FLT) || (AudioContext.CodecContext->sample_fmt == AV_SAMPLE_FMT_DBL));
} catch (...) {
throw FFMS_Exception(FFMS_ERROR_WAVE_WRITER, FFMS_ERROR_FILE_WRITE,
"Failed to write wave data");
}
}
AudioContext.W64Writer->WriteData(&DecodingBuffer[0], DBSize);
}
int64_t FFMS_Indexer::IndexAudioPacket(int Track, AVPacket *Packet, SharedAudioContext &Context, FFMS_Index &TrackIndices) {
AVCodecContext *CodecContext = Context.CodecContext;
int64_t StartSample = Context.CurrentSample;
int Read = 0;
while (Packet->size > 0) {
int dbsize = AVCODEC_MAX_AUDIO_FRAME_SIZE*10;
int Ret = avcodec_decode_audio3(CodecContext, (int16_t *)&DecodingBuffer[0], &dbsize, Packet);
if (Ret < 0) {
if (ErrorHandling == FFMS_IEH_ABORT) {
throw FFMS_Exception(FFMS_ERROR_CODEC, FFMS_ERROR_DECODING, "Audio decoding error");
} else if (ErrorHandling == FFMS_IEH_CLEAR_TRACK) {
TrackIndices[Track].clear();
IndexMask &= ~(1 << Track);
} else if (ErrorHandling == FFMS_IEH_STOP_TRACK) {
IndexMask &= ~(1 << Track);
}
break;
}
Packet->size -= Ret;
Packet->data += Ret;
Read += Ret;
CheckAudioProperties(Track, CodecContext);
if (dbsize > 0)
Context.CurrentSample += (dbsize * 8) / (av_get_bits_per_sample_fmt(CodecContext->sample_fmt) * CodecContext->channels);
if (DumpMask & (1 << Track))
WriteAudio(Context, &TrackIndices, Track, dbsize);
}
Packet->size += Read;
Packet->data -= Read;
return Context.CurrentSample - StartSample;
}
void FFMS_Indexer::CheckAudioProperties(int Track, AVCodecContext *Context) {
std::map<int, FFMS_AudioProperties>::iterator it = LastAudioProperties.find(Track);
if (it == LastAudioProperties.end()) {
FFMS_AudioProperties &AP = LastAudioProperties[Track];
AP.SampleRate = Context->sample_rate;
AP.SampleFormat = Context->sample_fmt;
AP.Channels = Context->channels;
}
else if (it->second.SampleRate != Context->sample_rate ||
it->second.SampleFormat != Context->sample_fmt ||
it->second.Channels != Context->channels) {
std::ostringstream buf;
buf <<
"Audio format change detected. This is currently unsupported."
<< " Channels: " << it->second.Channels << " -> " << Context->channels << ";"
<< " Sample rate: " << it->second.SampleRate << " -> " << Context->sample_rate << ";"
<< " Sample format: " << GetLAVCSampleFormatName((AVSampleFormat)it->second.SampleFormat) << " -> "
<< GetLAVCSampleFormatName(Context->sample_fmt);
throw FFMS_Exception(FFMS_ERROR_UNSUPPORTED, FFMS_ERROR_DECODING, buf.str());
}
}

View File

@ -19,8 +19,9 @@
// THE SOFTWARE.
#ifndef INDEXING_H
#define INDEXING_H
#define INDEXING_H
#include <map>
#include <memory>
#include "utils.h"
#include "wave64writer.h"
@ -38,14 +39,14 @@
#endif
class SharedVideoContext {
private:
bool FreeCodecContext;
public:
AVCodecContext *CodecContext;
AVCodecParserContext *Parser;
CompressedStream *CS;
AVBitStreamFilterContext *BitStreamFilter;
TrackCompressionContext *TCC;
SharedVideoContext(bool FreeCodecContext);
~SharedVideoContext();
@ -58,7 +59,7 @@ public:
AVCodecContext *CodecContext;
Wave64Writer *W64Writer;
int64_t CurrentSample;
CompressedStream *CS;
TrackCompressionContext *TCC;
SharedAudioContext(bool FreeCodecContext);
~SharedAudioContext();
@ -75,7 +76,7 @@ public:
TFrameInfo();
static TFrameInfo VideoFrameInfo(int64_t PTS, int RepeatPict, bool KeyFrame, int64_t FilePos = 0, unsigned int FrameSize = 0);
static TFrameInfo AudioFrameInfo(int64_t PTS, int64_t SampleStart, unsigned int SampleCount, bool KeyFrame, int64_t FilePos = 0, unsigned int FrameSize = 0);
static TFrameInfo AudioFrameInfo(int64_t PTS, int64_t SampleStart, int64_t SampleCount, bool KeyFrame, int64_t FilePos = 0, unsigned int FrameSize = 0);
private:
TFrameInfo(int64_t PTS, int64_t SampleStart, unsigned int SampleCount, int RepeatPict, bool KeyFrame, int64_t FilePos, unsigned int FrameSize);
};
@ -84,15 +85,15 @@ class FFMS_Track : public std::vector<TFrameInfo> {
public:
FFMS_TrackType TT;
FFMS_TrackTimeBase TB;
bool UseDTS;
int FindClosestVideoKeyFrame(int Frame);
int FindClosestAudioKeyFrame(int64_t Sample);
int FrameFromPTS(int64_t PTS);
int ClosestFrameFromPTS(int64_t PTS);
void WriteTimecodes(const char *TimecodeFile);
FFMS_Track();
FFMS_Track(int64_t Num, int64_t Den, FFMS_TrackType TT);
FFMS_Track(int64_t Num, int64_t Den, FFMS_TrackType TT, bool UseDTS = false);
};
class FFMS_Index : public std::vector<FFMS_Track> {
@ -113,6 +114,7 @@ public:
};
class FFMS_Indexer {
std::map<int, FFMS_AudioProperties> LastAudioProperties;
protected:
int IndexMask;
int DumpMask;
@ -122,14 +124,18 @@ protected:
TAudioNameCallback ANC;
void *ANCPrivate;
const char *SourceFile;
int16_t *DecodingBuffer;
AlignedBuffer<uint8_t> DecodingBuffer;
FFMS_Sources Demuxer;
const char *FormatName;
int64_t Filesize;
uint8_t Digest[20];
void WriteAudio(SharedAudioContext &AudioContext, FFMS_Index *Index, int Track, int DBSize);
void CheckAudioProperties(int Track, AVCodecContext *Context);
int64_t IndexAudioPacket(int Track, AVPacket *Packet, SharedAudioContext &Context, FFMS_Index &TrackIndices);
public:
static FFMS_Indexer *CreateIndexer(const char *Filename);
static FFMS_Indexer *CreateIndexer(const char *Filename, enum FFMS_Sources Demuxer = FFMS_SOURCE_DEFAULT);
FFMS_Indexer(const char *Filename);
virtual ~FFMS_Indexer();
void SetIndexMask(int IndexMask);
@ -141,11 +147,13 @@ public:
virtual int GetNumberOfTracks() = 0;
virtual FFMS_TrackType GetTrackType(int Track) = 0;
virtual const char *GetTrackCodec(int Track) = 0;
virtual FFMS_Sources GetSourceType() = 0;
virtual const char *GetFormatName() = 0;
};
class FFLAVFIndexer : public FFMS_Indexer {
private:
AVFormatContext *FormatContext;
void ReadTS(const AVPacket &Packet, int64_t &TS, bool &UseDTS);
public:
FFLAVFIndexer(const char *Filename, AVFormatContext *FormatContext);
~FFLAVFIndexer();
@ -153,6 +161,8 @@ public:
int GetNumberOfTracks();
FFMS_TrackType GetTrackType(int Track);
const char *GetTrackCodec(int Track);
const char *GetFormatName();
FFMS_Sources GetSourceType();
};
class FFMatroskaIndexer : public FFMS_Indexer {
@ -167,6 +177,8 @@ public:
int GetNumberOfTracks();
FFMS_TrackType GetTrackType(int Track);
const char *GetTrackCodec(int Track);
const char *GetFormatName();
FFMS_Sources GetSourceType();
};
#ifdef HAALISOURCE
@ -177,9 +189,6 @@ private:
CComPtr<IMMContainer> pMMC;
int NumTracks;
FFMS_TrackType TrackType[32];
AVCodec *Codec[32];
std::vector<uint8_t> CodecPrivate[32];
int CodecPrivateSize[32];
CComQIPtr<IPropertyBag> PropertyBags[32];
int64_t Duration;
public:
@ -188,6 +197,8 @@ public:
int GetNumberOfTracks();
FFMS_TrackType GetTrackType(int Track);
const char *GetTrackCodec(int Track);
const char *GetFormatName();
FFMS_Sources GetSourceType();
};
#endif // HAALISOURCE

View File

@ -1,4 +1,4 @@
// Copyright (c) 2007-2009 Fredrik Mellbin
// Copyright (c) 2011 Thomas Goyne <tgoyne@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@ -19,175 +19,69 @@
// THE SOFTWARE.
#include "audiosource.h"
#include <cassert>
void FFLAVFAudio::Free(bool CloseCodec) {
if (CloseCodec)
avcodec_close(CodecContext);
if (FormatContext)
av_close_input_file(FormatContext);
}
FFLAVFAudio::FFLAVFAudio(const char *SourceFile, int Track, FFMS_Index *Index)
: Res(FFSourceResources<FFMS_AudioSource>(this)), FFMS_AudioSource(SourceFile, Index, Track){
FormatContext = NULL;
AVCodec *Codec = NULL;
AudioTrack = Track;
Frames = (*Index)[AudioTrack];
FFLAVFAudio::FFLAVFAudio(const char *SourceFile, int Track, FFMS_Index &Index, int DelayMode)
: FFMS_AudioSource(SourceFile, Index, Track)
, FormatContext(NULL)
{
LAVFOpenFile(SourceFile, FormatContext);
CodecContext = FormatContext->streams[AudioTrack]->codec;
CodecContext.reset(FormatContext->streams[TrackNumber]->codec);
assert(CodecContext);
Codec = avcodec_find_decoder(CodecContext->codec_id);
if (Codec == NULL)
throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC,
"Audio codec not found");
AVCodec *Codec = avcodec_find_decoder(CodecContext->codec_id);
try {
if (!Codec)
throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC,
"Audio codec not found");
if (avcodec_open(CodecContext, Codec) < 0)
throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC,
"Could not open audio codec");
Res.CloseCodec(true);
// Always try to decode a frame to make sure all required parameters are known
int64_t Dummy;
DecodeNextAudioBlock(&Dummy);
if (av_seek_frame(FormatContext, AudioTrack, Frames[0].PTS, AVSEEK_FLAG_BACKWARD) < 0)
av_seek_frame(FormatContext, AudioTrack, Frames[0].PTS, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_ANY);
avcodec_flush_buffers(CodecContext);
FillAP(AP, CodecContext, Frames);
if (AP.SampleRate <= 0 || AP.BitsPerSample <= 0)
throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC,
"Codec returned zero size audio");
AudioCache.Initialize((AP.Channels * AP.BitsPerSample) / 8, 50);
}
void FFLAVFAudio::DecodeNextAudioBlock(int64_t *Count) {
const size_t SizeConst = (av_get_bits_per_sample_format(CodecContext->sample_fmt) * CodecContext->channels) / 8;
int Ret = -1;
*Count = 0;
uint8_t *Buf = DecodingBuffer;
AVPacket Packet, TempPacket;
InitNullPacket(Packet);
InitNullPacket(TempPacket);
while (av_read_frame(FormatContext, &Packet) >= 0) {
if (Packet.stream_index == AudioTrack) {
TempPacket.data = Packet.data;
TempPacket.size = Packet.size;
while (TempPacket.size > 0) {
int TempOutputBufSize = AVCODEC_MAX_AUDIO_FRAME_SIZE * 10;
Ret = avcodec_decode_audio3(CodecContext, (int16_t *)Buf, &TempOutputBufSize, &TempPacket);
if (Ret < 0) {// throw error or something?
av_free_packet(&Packet);
goto Done;
}
if (Ret > 0) {
TempPacket.size -= Ret;
TempPacket.data += Ret;
Buf += TempOutputBufSize;
if (SizeConst)
*Count += TempOutputBufSize / SizeConst;
}
}
av_free_packet(&Packet);
goto Done;
}
av_free_packet(&Packet);
if (avcodec_open(CodecContext, Codec) < 0)
throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC,
"Could not open audio codec");
}
catch (...) {
av_close_input_file(FormatContext);
throw;
}
Done:;
if (Frames.back().PTS == Frames.front().PTS)
SeekOffset = -1;
else
SeekOffset = 10;
Init(Index, DelayMode);
}
void FFLAVFAudio::GetAudio(void *Buf, int64_t Start, int64_t Count) {
GetAudioCheck(Start, Count);
FFLAVFAudio::~FFLAVFAudio() {
av_close_input_file(FormatContext);
}
const int64_t SizeConst = (av_get_bits_per_sample_format(CodecContext->sample_fmt) * CodecContext->channels) / 8;
memset(Buf, 0, static_cast<size_t>(SizeConst * Count));
void FFLAVFAudio::Seek() {
size_t TargetPacket = GetSeekablePacketNumber(Frames, PacketNumber);
unsigned int PreDecBlocks = 0;
uint8_t *DstBuf = static_cast<uint8_t *>(Buf);
if (av_seek_frame(FormatContext, TrackNumber, Frames[TargetPacket].PTS, AVSEEK_FLAG_BACKWARD) < 0)
av_seek_frame(FormatContext, TrackNumber, Frames[TargetPacket].PTS, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_ANY);
// Fill with everything in the cache
int64_t CacheEnd = AudioCache.FillRequest(Start, Count, DstBuf);
// Was everything in the cache?
if (CacheEnd == Start + Count)
return;
size_t CurrentAudioBlock;
if (CurrentSample != CacheEnd) {
PreDecBlocks = 15;
CurrentAudioBlock = FFMAX((int64_t)Frames.FindClosestAudioKeyFrame(CacheEnd) - PreDecBlocks - 20, (int64_t)0);
if (CurrentAudioBlock <= PreDecBlocks) {
CurrentAudioBlock = 0;
PreDecBlocks = 0;
}
// Did the seeking fail?
if (av_seek_frame(FormatContext, AudioTrack, Frames[CurrentAudioBlock].PTS, AVSEEK_FLAG_BACKWARD) < 0)
av_seek_frame(FormatContext, AudioTrack, Frames[CurrentAudioBlock].PTS, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_ANY);
avcodec_flush_buffers(CodecContext);
// Pretend we got to the first audio frame when PreDecBlocks = 0
if (PreDecBlocks > 0) {
AVPacket Packet;
InitNullPacket(Packet);
// Establish where we actually are
// Trigger on packet PTS difference since groups can otherwise be indistinguishable
int64_t LastPTS = - 1;
while (av_read_frame(FormatContext, &Packet) >= 0) {
if (Packet.stream_index == AudioTrack) {
if (LastPTS < 0) {
LastPTS = Packet.dts;
} else if (LastPTS != Packet.dts) {
for (size_t i = 0; i < Frames.size(); i++)
if (Frames[i].PTS == Packet.dts) {
// The current match was consumed
CurrentAudioBlock = i + 1;
break;
}
av_free_packet(&Packet);
break;
}
}
av_free_packet(&Packet);
}
}
} else {
CurrentAudioBlock = Frames.FindClosestAudioKeyFrame(CurrentSample);
if (TargetPacket != PacketNumber) {
// Decode until the PTS changes so we know where we are
int64_t LastPTS = Frames[PacketNumber].PTS;
while (LastPTS == Frames[PacketNumber].PTS) DecodeNextBlock();
}
int64_t DecodeCount;
do {
DecodeNextAudioBlock(&DecodeCount);
// Cache the block if enough blocks before it have been decoded to avoid garbage
if (PreDecBlocks == 0) {
AudioCache.CacheBlock(Frames[CurrentAudioBlock].SampleStart, DecodeCount, DecodingBuffer);
CacheEnd = AudioCache.FillRequest(CacheEnd, Start + Count - CacheEnd, DstBuf + (CacheEnd - Start) * SizeConst);
} else {
PreDecBlocks--;
}
CurrentAudioBlock++;
if (CurrentAudioBlock < Frames.size())
CurrentSample = Frames[CurrentAudioBlock].SampleStart;
} while (Start + Count - CacheEnd > 0 && CurrentAudioBlock < Frames.size());
}
bool FFLAVFAudio::ReadPacket(AVPacket *Packet) {
InitNullPacket(*Packet);
while (av_read_frame(FormatContext, Packet) >= 0) {
if (Packet->stream_index == TrackNumber) {
while (PacketNumber > 0 && Frames[PacketNumber].PTS > Packet->pts) --PacketNumber;
while (Frames[PacketNumber].PTS < Packet->pts) ++PacketNumber;
return true;
}
av_free_packet(Packet);
}
return false;
}
void FFLAVFAudio::FreePacket(AVPacket *Packet) {
av_free_packet(Packet);
}

View File

@ -45,27 +45,27 @@ FFMS_Index *FFLAVFIndexer::DoIndexing() {
TrackIndices->Decoder = FFMS_SOURCE_LAVF;
for (unsigned int i = 0; i < FormatContext->nb_streams; i++) {
TrackIndices->push_back(FFMS_Track((int64_t)FormatContext->streams[i]->time_base.num * 1000,
TrackIndices->push_back(FFMS_Track((int64_t)FormatContext->streams[i]->time_base.num * 1000,
FormatContext->streams[i]->time_base.den,
static_cast<FFMS_TrackType>(FormatContext->streams[i]->codec->codec_type)));
if (static_cast<FFMS_TrackType>(FormatContext->streams[i]->codec->codec_type) == FFMS_TYPE_VIDEO &&
(VideoContexts[i].Parser = av_parser_init(FormatContext->streams[i]->codec->codec_id))) {
if (FormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO) {
AVCodec *VideoCodec = avcodec_find_decoder(FormatContext->streams[i]->codec->codec_id);
if (VideoCodec == NULL)
if (!VideoCodec)
throw FFMS_Exception(FFMS_ERROR_CODEC, FFMS_ERROR_UNSUPPORTED,
"Video codec not found");
if (avcodec_open(FormatContext->streams[i]->codec, VideoCodec) < 0)
if (avcodec_open(FormatContext->streams[i]->codec, VideoCodec) < 0)
throw FFMS_Exception(FFMS_ERROR_CODEC, FFMS_ERROR_DECODING,
"Could not open video codec");
VideoContexts[i].CodecContext = FormatContext->streams[i]->codec;
VideoContexts[i].Parser->flags = PARSER_FLAG_COMPLETE_FRAMES;
VideoContexts[i].Parser = av_parser_init(FormatContext->streams[i]->codec->codec_id);
if (VideoContexts[i].Parser)
VideoContexts[i].Parser->flags = PARSER_FLAG_COMPLETE_FRAMES;
IndexMask |= 1 << i;
}
if (IndexMask & (1 << i) && FormatContext->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO) {
else if (IndexMask & (1 << i) && FormatContext->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO) {
AVCodecContext *AudioCodecContext = FormatContext->streams[i]->codec;
AVCodec *AudioCodec = avcodec_find_decoder(AudioCodecContext->codec_id);
@ -83,71 +83,51 @@ FFMS_Index *FFLAVFIndexer::DoIndexing() {
}
}
//
AVPacket Packet, TempPacket;
AVPacket Packet;
InitNullPacket(Packet);
InitNullPacket(TempPacket);
std::vector<int64_t> LastValidTS;
LastValidTS.resize(FormatContext->nb_streams, ffms_av_nopts_value);
while (av_read_frame(FormatContext, &Packet) >= 0) {
// Update progress
if (IC) {
// FormatContext->pb can apparently be NULL when opening images.
if (IC && FormatContext->pb) {
if ((*IC)(FormatContext->pb->pos, FormatContext->file_size, ICPrivate))
throw FFMS_Exception(FFMS_ERROR_CANCELLED, FFMS_ERROR_USER,
"Cancelled by user");
}
if (!(IndexMask & (1 << Packet.stream_index))) {
av_free_packet(&Packet);
continue;
}
int Track = Packet.stream_index;
bool KeyFrame = !!(Packet.flags & AV_PKT_FLAG_KEY);
ReadTS(Packet, LastValidTS[Track], (*TrackIndices)[Track].UseDTS);
if (FormatContext->streams[Track]->codec->codec_type == CODEC_TYPE_VIDEO) {
if (LastValidTS[Track] == ffms_av_nopts_value)
throw FFMS_Exception(FFMS_ERROR_INDEXING, FFMS_ERROR_PARSER,
"Invalid initial pts and dts");
// Only create index entries for video for now to save space
if (FormatContext->streams[Packet.stream_index]->codec->codec_type == CODEC_TYPE_VIDEO) {
uint8_t *OB;
int OBSize;
int RepeatPict = -1;
if (VideoContexts[Packet.stream_index].Parser) {
av_parser_parse2(VideoContexts[Packet.stream_index].Parser, VideoContexts[Packet.stream_index].CodecContext, &OB, &OBSize, Packet.data, Packet.size, Packet.pts, Packet.dts, Packet.pos);
RepeatPict = VideoContexts[Packet.stream_index].Parser->repeat_pict;
if (VideoContexts[Track].Parser) {
uint8_t *OB;
int OBSize;
av_parser_parse2(VideoContexts[Track].Parser, VideoContexts[Track].CodecContext, &OB, &OBSize, Packet.data, Packet.size, Packet.pts, Packet.dts, Packet.pos);
RepeatPict = VideoContexts[Track].Parser->repeat_pict;
}
(*TrackIndices)[Packet.stream_index].push_back(TFrameInfo::VideoFrameInfo(Packet.dts, RepeatPict, (Packet.flags & AV_PKT_FLAG_KEY) ? 1 : 0));
} else if (FormatContext->streams[Packet.stream_index]->codec->codec_type == CODEC_TYPE_AUDIO && (IndexMask & (1 << Packet.stream_index))) {
int64_t StartSample = AudioContexts[Packet.stream_index].CurrentSample;
AVCodecContext *AudioCodecContext = FormatContext->streams[Packet.stream_index]->codec;
TempPacket.data = Packet.data;
TempPacket.size = Packet.size;
TempPacket.flags = Packet.flags;
(*TrackIndices)[Track].push_back(TFrameInfo::VideoFrameInfo(LastValidTS[Track], RepeatPict, KeyFrame, Packet.pos));
}
else if (FormatContext->streams[Track]->codec->codec_type == CODEC_TYPE_AUDIO) {
int64_t StartSample = AudioContexts[Track].CurrentSample;
int64_t SampleCount = IndexAudioPacket(Track, &Packet, AudioContexts[Track], *TrackIndices);
while (TempPacket.size > 0) {
int dbsize = AVCODEC_MAX_AUDIO_FRAME_SIZE*10;
int Ret = avcodec_decode_audio3(AudioCodecContext, &DecodingBuffer[0], &dbsize, &TempPacket);
if (Ret < 0) {
if (ErrorHandling == FFMS_IEH_ABORT) {
throw FFMS_Exception(FFMS_ERROR_CODEC, FFMS_ERROR_DECODING,
"Audio decoding error");
} else if (ErrorHandling == FFMS_IEH_CLEAR_TRACK) {
(*TrackIndices)[Packet.stream_index].clear();
IndexMask &= ~(1 << Packet.stream_index);
break;
} else if (ErrorHandling == FFMS_IEH_STOP_TRACK) {
IndexMask &= ~(1 << Packet.stream_index);
break;
} else if (ErrorHandling == FFMS_IEH_IGNORE) {
break;
}
}
if (Ret > 0) {
TempPacket.size -= Ret;
TempPacket.data += Ret;
}
if (dbsize > 0)
AudioContexts[Packet.stream_index].CurrentSample += (dbsize * 8) / (av_get_bits_per_sample_format(AudioCodecContext->sample_fmt) * AudioCodecContext->channels);
if (DumpMask & (1 << Packet.stream_index))
WriteAudio(AudioContexts[Packet.stream_index], TrackIndices.get(), Packet.stream_index, dbsize);
}
(*TrackIndices)[Packet.stream_index].push_back(TFrameInfo::AudioFrameInfo(Packet.dts, StartSample,
static_cast<unsigned int>(AudioContexts[Packet.stream_index].CurrentSample - StartSample), (Packet.flags & AV_PKT_FLAG_KEY) ? 1 : 0));
if (SampleCount != 0)
(*TrackIndices)[Track].push_back(TFrameInfo::AudioFrameInfo(LastValidTS[Track],
StartSample, SampleCount, KeyFrame, Packet.pos));
}
av_free_packet(&Packet);
@ -157,6 +137,15 @@ FFMS_Index *FFLAVFIndexer::DoIndexing() {
return TrackIndices.release();
}
void FFLAVFIndexer::ReadTS(const AVPacket &Packet, int64_t &TS, bool &UseDTS) {
if (!UseDTS && Packet.pts != ffms_av_nopts_value)
TS = Packet.pts;
if (TS == ffms_av_nopts_value)
UseDTS = true;
if (UseDTS && Packet.dts != ffms_av_nopts_value)
TS = Packet.dts;
}
int FFLAVFIndexer::GetNumberOfTracks() {
return FormatContext->nb_streams;
}
@ -165,6 +154,15 @@ FFMS_TrackType FFLAVFIndexer::GetTrackType(int Track) {
return static_cast<FFMS_TrackType>(FormatContext->streams[Track]->codec->codec_type);
}
const char *FFLAVFIndexer::GetTrackCodec(int Track) {
return (avcodec_find_decoder(FormatContext->streams[Track]->codec->codec_id))->name;
const char *FFLAVFIndexer::GetTrackCodec(int Track) {
AVCodec *codec = avcodec_find_decoder(FormatContext->streams[Track]->codec->codec_id);
return codec ? codec->name : NULL;
}
const char *FFLAVFIndexer::GetFormatName() {
return this->FormatContext->iformat->name;
}
FFMS_Sources FFLAVFIndexer::GetSourceType() {
return FFMS_SOURCE_LAVF;
}

View File

@ -45,7 +45,8 @@ FFLAVFVideo::FFLAVFVideo(const char *SourceFile, int Track, FFMS_Index *Index,
"Video track is unseekable");
CodecContext = FormatContext->streams[VideoTrack]->codec;
CodecContext->thread_count = Threads;
if (avcodec_thread_init(CodecContext, Threads))
CodecContext->thread_count = 1;
Codec = avcodec_find_decoder(CodecContext->codec_id);
if (Codec == NULL)
@ -82,6 +83,15 @@ FFLAVFVideo::FFLAVFVideo(const char *SourceFile, int Track, FFMS_Index *Index,
VP.ColorSpace = 0;
VP.ColorRange = 0;
#endif
// these pixfmt's are deprecated but still used
if (
CodecContext->pix_fmt == PIX_FMT_YUVJ420P
|| CodecContext->pix_fmt == PIX_FMT_YUVJ422P
|| CodecContext->pix_fmt == PIX_FMT_YUVJ444P
)
VP.ColorRange = AVCOL_RANGE_JPEG;
VP.FirstTime = ((Frames.front().PTS * Frames.TB.Num) / (double)Frames.TB.Den) / 1000;
VP.LastTime = ((Frames.back().PTS * Frames.TB.Num) / (double)Frames.TB.Den) / 1000;
@ -95,12 +105,20 @@ FFLAVFVideo::FFLAVFVideo(const char *SourceFile, int Track, FFMS_Index *Index,
VP.FPSNumerator = 30;
}
// Adjust framerate to match the duration of the first frame
// Calculate the average framerate
if (Frames.size() >= 2) {
unsigned int PTSDiff = (unsigned int)FFMAX(Frames[1].PTS - Frames[0].PTS, 1);
VP.FPSDenominator *= PTSDiff;
double PTSDiff = (double)(Frames.back().PTS - Frames.front().PTS);
double TD = (double)(Frames.TB.Den);
double TN = (double)(Frames.TB.Num);
VP.FPSDenominator = (unsigned int)(((double)1000000) / (double)((VP.NumFrames - 1) / ((PTSDiff * TN/TD) / (double)1000)));
VP.FPSNumerator = 1000000;
}
// attempt to correct framerate to the proper NTSC fraction, if applicable
CorrectNTSCRationalFramerate(&VP.FPSNumerator, &VP.FPSDenominator);
// correct the timebase, if necessary
CorrectTimebase(&VP, &Frames.TB);
// Cannot "output" to PPFrame without doing all other initialization
// This is the additional mess required for seekmode=-1 to work in a reasonable way
OutputFrame(DecodeFrame);
@ -116,22 +134,31 @@ void FFLAVFVideo::DecodeNextFrame(int64_t *AStartTime) {
int FrameFinished = 0;
*AStartTime = -1;
if (InitialDecode == -1) {
if (DelayCounter > CodecContext->has_b_frames) {
DelayCounter--;
goto Done;
} else {
InitialDecode = 0;
}
}
while (av_read_frame(FormatContext, &Packet) >= 0) {
if (Packet.stream_index == VideoTrack) {
if (*AStartTime < 0)
*AStartTime = Packet.dts;
if (*AStartTime < 0) {
if (Frames.UseDTS)
*AStartTime = Packet.dts;
else
*AStartTime = Packet.pts;
}
avcodec_decode_video2(CodecContext, DecodeFrame, &FrameFinished, &Packet);
if (CodecContext->codec_id == CODEC_ID_MPEG4) {
if (IsPackedFrame(Packet)) {
MPEG4Counter++;
} else if (IsNVOP(Packet) && MPEG4Counter && FrameFinished) {
MPEG4Counter--;
} else if (IsNVOP(Packet) && !MPEG4Counter && !FrameFinished) {
av_free_packet(&Packet);
goto Done;
}
if (!FrameFinished)
DelayCounter++;
if (DelayCounter > CodecContext->has_b_frames && !InitialDecode) {
av_free_packet(&Packet);
goto Done;
}
}
@ -152,7 +179,8 @@ void FFLAVFVideo::DecodeNextFrame(int64_t *AStartTime) {
goto Error;
Error:
Done:;
Done:
if (InitialDecode == 1) InitialDecode = -1;
}
FFMS_Frame *FFLAVFVideo::GetFrame(int n) {
@ -173,6 +201,8 @@ FFMS_Frame *FFLAVFVideo::GetFrame(int n) {
av_seek_frame(FormatContext, VideoTrack, Frames[0].PTS, AVSEEK_FLAG_BACKWARD);
avcodec_flush_buffers(CodecContext);
CurrentFrame = 0;
DelayCounter = 0;
InitialDecode = 1;
}
} else {
// 10 frames is used as a margin to prevent excessive seeking since the predicted best keyframe isn't always selected by avformat
@ -183,6 +213,8 @@ ReSeek:
AVSEEK_FLAG_BACKWARD);
avcodec_flush_buffers(CodecContext);
HasSeeked = true;
DelayCounter = 0;
InitialDecode = 1;
}
}
} else if (n < CurrentFrame) {
@ -192,11 +224,14 @@ ReSeek:
do {
int64_t StartTime;
if (CurrentFrame+CodecContext->has_b_frames >= n)
CodecContext->skip_frame = AVDISCARD_DEFAULT;
else
CodecContext->skip_frame = AVDISCARD_NONREF;
DecodeNextFrame(&StartTime);
if (HasSeeked) {
HasSeeked = false;
MPEG4Counter = 0;
// Is the seek destination time known? Does it belong to a frame?
if (StartTime < 0 || (CurrentFrame = Frames.FrameFromPTS(StartTime)) < 0) {

View File

@ -1,4 +1,4 @@
// Copyright (c) 2007-2009 Fredrik Mellbin
// Copyright (c) 2011 Thomas Goyne <tgoyne@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@ -19,168 +19,58 @@
// THE SOFTWARE.
#include "audiosource.h"
#include <cassert>
void FFMatroskaAudio::Free(bool CloseCodec) {
if (CS)
cs_Destroy(CS);
if (MC.ST.fp) {
mkv_Close(MF);
fclose(MC.ST.fp);
}
if (CloseCodec)
avcodec_close(CodecContext);
av_freep(&CodecContext);
}
FFMatroskaAudio::FFMatroskaAudio(const char *SourceFile, int Track, FFMS_Index *Index)
: Res(FFSourceResources<FFMS_AudioSource>(this)), FFMS_AudioSource(SourceFile, Index, Track) {
CodecContext = NULL;
AVCodec *Codec = NULL;
TrackInfo *TI = NULL;
CS = NULL;
PacketNumber = 0;
Frames = (*Index)[Track];
MC.ST.fp = ffms_fopen(SourceFile, "rb");
if (MC.ST.fp == NULL) {
std::ostringstream buf;
buf << "Can't open '" << SourceFile << "': " << strerror(errno);
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
}
FFMatroskaAudio::FFMatroskaAudio(const char *SourceFile, int Track, FFMS_Index &Index, int DelayMode)
: FFMS_AudioSource(SourceFile, Index, Track)
, TI(NULL)
{
if (!(MC.ST.fp = ffms_fopen(SourceFile, "rb")))
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ,
std::string("Can't open '") + SourceFile + "': " + strerror(errno));
setvbuf(MC.ST.fp, NULL, _IOFBF, CACHESIZE);
MF = mkv_OpenEx(&MC.ST.base, 0, 0, ErrorMessage, sizeof(ErrorMessage));
if (MF == NULL) {
fclose(MC.ST.fp);
std::ostringstream buf;
buf << "Can't parse Matroska file: " << ErrorMessage;
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
}
if (!(MF = mkv_OpenEx(&MC.ST.base, 0, 0, ErrorMessage, sizeof(ErrorMessage))))
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ,
std::string("Can't parse Matroska file: ") + ErrorMessage);
TI = mkv_GetTrackInfo(MF, Track);
assert(TI);
if (TI->CompEnabled) {
CS = cs_Create(MF, Track, ErrorMessage, sizeof(ErrorMessage));
if (CS == NULL) {
Free(false);
std::ostringstream buf;
buf << "Can't create decompressor: " << ErrorMessage;
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
}
if (TI->CompEnabled)
TCC.reset(new TrackCompressionContext(MF, TI, Track));
CodecContext.reset(avcodec_alloc_context(), DeleteMatroskaCodecContext);
assert(CodecContext);
AVCodec *Codec = avcodec_find_decoder(MatroskaToFFCodecID(TI->CodecID, TI->CodecPrivate, 0, TI->AV.Audio.BitDepth));
if (!Codec) {
mkv_Close(MF);
throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC, "Audio codec not found");
}
CodecContext = avcodec_alloc_context();
Codec = avcodec_find_decoder(MatroskaToFFCodecID(TI->CodecID, TI->CodecPrivate, 0, TI->AV.Audio.BitDepth));
if (Codec == NULL)
throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC,
"Audio codec not found");
InitializeCodecContextFromMatroskaTrackInfo(TI, CodecContext);
if (avcodec_open(CodecContext, Codec) < 0)
throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC,
"Could not open audio codec");
Res.CloseCodec(true);
// Always try to decode a frame to make sure all required parameters are known
int64_t Dummy;
DecodeNextAudioBlock(&Dummy);
avcodec_flush_buffers(CodecContext);
FillAP(AP, CodecContext, Frames);
if (AP.SampleRate <= 0 || AP.BitsPerSample <= 0)
throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC,
"Codec returned zero size audio");
AudioCache.Initialize((AP.Channels * AP.BitsPerSample) / 8, 50);
}
void FFMatroskaAudio::GetAudio(void *Buf, int64_t Start, int64_t Count) {
GetAudioCheck(Start, Count);
const int64_t SizeConst = (av_get_bits_per_sample_format(CodecContext->sample_fmt) * CodecContext->channels) / 8;
memset(Buf, 0, static_cast<size_t>(SizeConst * Count));
unsigned int PreDecBlocks = 0;
uint8_t *DstBuf = static_cast<uint8_t *>(Buf);
// Fill with everything in the cache
int64_t CacheEnd = AudioCache.FillRequest(Start, Count, DstBuf);
// Was everything in the cache?
if (CacheEnd == Start + Count)
return;
if (CurrentSample != CacheEnd) {
PreDecBlocks = 15;
PacketNumber = FFMAX((int64_t)Frames.FindClosestAudioKeyFrame(CacheEnd) - PreDecBlocks, (int64_t)0);
if (PacketNumber <= PreDecBlocks) {
PacketNumber = 0;
PreDecBlocks = 0;
}
avcodec_flush_buffers(CodecContext);
}
int64_t DecodeCount;
do {
const TFrameInfo &FI = Frames[Frames[PacketNumber].OriginalPos];
DecodeNextAudioBlock(&DecodeCount);
// Cache the block if enough blocks before it have been decoded to avoid garbage
if (PreDecBlocks == 0) {
AudioCache.CacheBlock(FI.SampleStart, DecodeCount, DecodingBuffer);
CacheEnd = AudioCache.FillRequest(CacheEnd, Start + Count - CacheEnd, DstBuf + (CacheEnd - Start) * SizeConst);
} else {
PreDecBlocks--;
}
} while (Start + Count - CacheEnd > 0 && PacketNumber < Frames.size());
}
void FFMatroskaAudio::DecodeNextAudioBlock(int64_t *Count) {
const size_t SizeConst = (av_get_bits_per_sample_format(CodecContext->sample_fmt) * CodecContext->channels) / 8;
int Ret = -1;
*Count = 0;
uint8_t *Buf = DecodingBuffer;
AVPacket TempPacket;
InitNullPacket(TempPacket);
const TFrameInfo &FI = Frames[Frames[PacketNumber].OriginalPos];
unsigned int FrameSize = FI.FrameSize;
CurrentSample = FI.SampleStart + FI.SampleCount;
PacketNumber++;
ReadFrame(FI.FilePos, FrameSize, CS, MC);
TempPacket.data = MC.Buffer;
TempPacket.size = FrameSize;
if (FI.KeyFrame)
TempPacket.flags = AV_PKT_FLAG_KEY;
else
TempPacket.flags = 0;
while (TempPacket.size > 0) {
int TempOutputBufSize = AVCODEC_MAX_AUDIO_FRAME_SIZE;
Ret = avcodec_decode_audio3(CodecContext, (int16_t *)Buf, &TempOutputBufSize, &TempPacket);
if (Ret < 0) // throw error or something?
goto Done;
if (Ret > 0) {
TempPacket.size -= Ret;
TempPacket.data += Ret;
Buf += TempOutputBufSize;
if (SizeConst)
*Count += TempOutputBufSize / SizeConst;
}
if (avcodec_open(CodecContext, Codec) < 0) {
mkv_Close(MF);
throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC, "Could not open audio codec");
}
Done:;
Init(Index, DelayMode);
}
FFMatroskaAudio::~FFMatroskaAudio() {
TCC.reset(); // cs_Destroy() must be called before mkv_Close()
mkv_Close(MF);
}
bool FFMatroskaAudio::ReadPacket(AVPacket *Packet) {
ReadFrame(CurrentFrame->FilePos, CurrentFrame->FrameSize, TCC.get(), MC);
InitNullPacket(*Packet);
Packet->data = MC.Buffer;
Packet->size = CurrentFrame->FrameSize + ((TCC.get() && TCC->CompressionMethod == COMP_PREPEND) ? TCC->CompressedPrivateDataSize : 0);
Packet->flags = CurrentFrame->KeyFrame ? AV_PKT_FLAG_KEY : 0;
return true;
}

View File

@ -42,7 +42,6 @@ FFMatroskaIndexer::FFMatroskaIndexer(const char *Filename) : FFMS_Indexer(Filena
MF = mkv_OpenEx(&MC.ST.base, 0, 0, ErrorMessage, sizeof(ErrorMessage));
if (MF == NULL) {
fclose(MC.ST.fp);
std::ostringstream buf;
buf << "Can't parse Matroska file: " << ErrorMessage;
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
@ -56,11 +55,9 @@ FFMatroskaIndexer::FFMatroskaIndexer(const char *Filename) : FFMS_Indexer(Filena
FFMatroskaIndexer::~FFMatroskaIndexer() {
mkv_Close(MF);
fclose(MC.ST.fp);
}
FFMS_Index *FFMatroskaIndexer::DoIndexing() {
char ErrorMessage[256];
std::vector<SharedAudioContext> AudioContexts(mkv_GetNumTracks(MF), SharedAudioContext(true));
std::vector<SharedVideoContext> VideoContexts(mkv_GetNumTracks(MF), SharedVideoContext(true));
@ -71,68 +68,43 @@ FFMS_Index *FFMatroskaIndexer::DoIndexing() {
TrackInfo *TI = mkv_GetTrackInfo(MF, i);
TrackIndices->push_back(FFMS_Track(mkv_TruncFloat(mkv_GetTrackInfo(MF, i)->TimecodeScale), 1000000, HaaliTrackTypeToFFTrackType(mkv_GetTrackInfo(MF, i)->Type)));
if (HaaliTrackTypeToFFTrackType(TI->Type) == FFMS_TYPE_VIDEO && Codec[i] && (VideoContexts[i].Parser = av_parser_init(Codec[i]->id))) {
if (!Codec[i]) continue;
AVCodecContext *CodecContext = avcodec_alloc_context();
AVCodecContext *CodecContext = avcodec_alloc_context();
InitializeCodecContextFromMatroskaTrackInfo(TI, CodecContext);
InitializeCodecContextFromMatroskaTrackInfo(TI, CodecContext);
try {
if (TI->Type == TT_VIDEO && (VideoContexts[i].Parser = av_parser_init(Codec[i]->id))) {
if (avcodec_open(CodecContext, Codec[i]) < 0)
throw FFMS_Exception(FFMS_ERROR_CODEC, FFMS_ERROR_DECODING,
"Could not open video codec");
if (avcodec_open(CodecContext, Codec[i]) < 0) {
if (TI->CompEnabled)
VideoContexts[i].TCC = new TrackCompressionContext(MF, TI, i);
VideoContexts[i].CodecContext = CodecContext;
VideoContexts[i].Parser->flags = PARSER_FLAG_COMPLETE_FRAMES;
}
else if (IndexMask & (1 << i) && TI->Type == TT_AUDIO) {
if (avcodec_open(CodecContext, Codec[i]) < 0)
throw FFMS_Exception(FFMS_ERROR_CODEC, FFMS_ERROR_DECODING,
"Could not open audio codec");
if (TI->CompEnabled)
AudioContexts[i].TCC = new TrackCompressionContext(MF, TI, i);
AudioContexts[i].CodecContext = CodecContext;
} else {
IndexMask &= ~(1 << i);
av_freep(&CodecContext);
throw FFMS_Exception(FFMS_ERROR_CODEC, FFMS_ERROR_DECODING,
"Could not open video codec");
}
if (TI->CompEnabled) {
VideoContexts[i].CS = cs_Create(MF, i, ErrorMessage, sizeof(ErrorMessage));
if (VideoContexts[i].CS == NULL) {
std::ostringstream buf;
buf << "Can't create decompressor: " << ErrorMessage;
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
}
}
VideoContexts[i].CodecContext = CodecContext;
VideoContexts[i].Parser->flags = PARSER_FLAG_COMPLETE_FRAMES;
}
if (IndexMask & (1 << i) && TI->Type == TT_AUDIO) {
AVCodecContext *AudioCodecContext = avcodec_alloc_context();
InitializeCodecContextFromMatroskaTrackInfo(TI, AudioCodecContext);
AudioContexts[i].CodecContext = AudioCodecContext;
if (TI->CompEnabled) {
AudioContexts[i].CS = cs_Create(MF, i, ErrorMessage, sizeof(ErrorMessage));
if (AudioContexts[i].CS == NULL) {
av_freep(&AudioCodecContext);
AudioContexts[i].CodecContext = NULL;
std::ostringstream buf;
buf << "Can't create decompressor: " << ErrorMessage;
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
}
}
AVCodec *AudioCodec = Codec[i];
if (AudioCodec == NULL) {
av_freep(&AudioCodecContext);
AudioContexts[i].CodecContext = NULL;
throw FFMS_Exception(FFMS_ERROR_CODEC, FFMS_ERROR_UNSUPPORTED,
"Audio codec not found");
}
if (avcodec_open(AudioCodecContext, AudioCodec) < 0) {
av_freep(&AudioCodecContext);
AudioContexts[i].CodecContext = NULL;
throw FFMS_Exception(FFMS_ERROR_CODEC, FFMS_ERROR_DECODING,
"Could not open audio codec");
}
} else {
IndexMask &= ~(1 << i);
catch (...) {
av_freep(&CodecContext);
throw;
}
}
//
ulonglong StartTime, EndTime, FilePos;
unsigned int Track, FrameFlags, FrameSize;
AVPacket TempPacket;
@ -140,13 +112,9 @@ FFMS_Index *FFMatroskaIndexer::DoIndexing() {
while (mkv_ReadFrame(MF, 0, &Track, &StartTime, &EndTime, &FilePos, &FrameSize, &FrameFlags) == 0) {
// Update progress
if (IC) {
if ((*IC)(ftello(MC.ST.fp), Filesize, ICPrivate))
throw FFMS_Exception(FFMS_ERROR_CANCELLED, FFMS_ERROR_USER,
"Cancelled by user");
}
// Only create index entries for video for now to save space
if (IC && (*IC)(ftello(MC.ST.fp), Filesize, ICPrivate))
throw FFMS_Exception(FFMS_ERROR_CANCELLED, FFMS_ERROR_USER, "Cancelled by user");
if (mkv_GetTrackInfo(MF, Track)->Type == TT_VIDEO) {
uint8_t *OB;
int OBSize;
@ -159,50 +127,19 @@ FFMS_Index *FFMatroskaIndexer::DoIndexing() {
(*TrackIndices)[Track].push_back(TFrameInfo::VideoFrameInfo(StartTime, RepeatPict, (FrameFlags & FRAME_KF) != 0, FilePos, FrameSize));
} else if (mkv_GetTrackInfo(MF, Track)->Type == TT_AUDIO && (IndexMask & (1 << Track))) {
int64_t StartSample = AudioContexts[Track].CurrentSample;
TrackCompressionContext *TCC = AudioContexts[Track].TCC;
unsigned int CompressedFrameSize = FrameSize;
AVCodecContext *AudioCodecContext = AudioContexts[Track].CodecContext;
ReadFrame(FilePos, FrameSize, AudioContexts[Track].CS, MC);
ReadFrame(FilePos, FrameSize, TCC, MC);
TempPacket.data = MC.Buffer;
TempPacket.size = FrameSize;
if ((FrameFlags & FRAME_KF) != 0)
TempPacket.flags = AV_PKT_FLAG_KEY;
else
TempPacket.flags = 0;
TempPacket.size = (TCC && TCC->CompressionMethod == COMP_PREPEND) ? FrameSize + TCC->CompressedPrivateDataSize : FrameSize;
TempPacket.flags = FrameFlags & FRAME_KF ? AV_PKT_FLAG_KEY : 0;
while (TempPacket.size > 0) {
int dbsize = AVCODEC_MAX_AUDIO_FRAME_SIZE*10;
int Ret = avcodec_decode_audio3(AudioCodecContext, DecodingBuffer, &dbsize, &TempPacket);
if (Ret < 0) {
if (ErrorHandling == FFMS_IEH_ABORT) {
throw FFMS_Exception(FFMS_ERROR_CODEC, FFMS_ERROR_DECODING,
"Audio decoding error");
} else if (ErrorHandling == FFMS_IEH_CLEAR_TRACK) {
(*TrackIndices)[Track].clear();
IndexMask &= ~(1 << Track);
break;
} else if (ErrorHandling == FFMS_IEH_STOP_TRACK) {
IndexMask &= ~(1 << Track);
break;
} else if (ErrorHandling == FFMS_IEH_IGNORE) {
break;
}
}
int64_t StartSample = AudioContexts[Track].CurrentSample;
int64_t SampleCount = IndexAudioPacket(Track, &TempPacket, AudioContexts[Track], *TrackIndices);
if (Ret > 0) {
TempPacket.size -= Ret;
TempPacket.data += Ret;
}
if (dbsize > 0)
AudioContexts[Track].CurrentSample += (dbsize * 8) / (av_get_bits_per_sample_format(AudioCodecContext->sample_fmt) * AudioCodecContext->channels);
if (DumpMask & (1 << Track))
WriteAudio(AudioContexts[Track], TrackIndices.get(), Track, dbsize);
}
(*TrackIndices)[Track].push_back(TFrameInfo::AudioFrameInfo(StartTime, StartSample,
static_cast<unsigned int>(AudioContexts[Track].CurrentSample - StartSample), (FrameFlags & FRAME_KF) != 0, FilePos, CompressedFrameSize));
if (SampleCount != 0)
(*TrackIndices)[Track].push_back(TFrameInfo::AudioFrameInfo(StartTime, StartSample,
SampleCount, (FrameFlags & FRAME_KF) != 0, FilePos, CompressedFrameSize));
}
}
@ -219,8 +156,14 @@ FFMS_TrackType FFMatroskaIndexer::GetTrackType(int Track) {
}
const char *FFMatroskaIndexer::GetTrackCodec(int Track) {
if (Codec[Track])
return Codec[Track]->name;
else
return "Unsupported codec/Unknown codec name";
return Codec[Track] ? Codec[Track]->name : NULL;
}
const char *FFMatroskaIndexer::GetFormatName() {
return "matroska";
}
FFMS_Sources FFMatroskaIndexer::GetSourceType() {
return FFMS_SOURCE_MATROSKA;
}

View File

@ -1,32 +1,38 @@
/*
* Copyright (c) 2004-2009 Mike Matsnev. All Rights Reserved.
* Copyright (c) 2004-2008 Mike Matsnev. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice immediately at the beginning of the file, without modification,
* this list of conditions, and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Absolutely no warranty of function or purpose is made by the author
* Mike Matsnev.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice immediately at the beginning of the file, without modification,
* this list of conditions, and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Absolutely no warranty of function or purpose is made by the author
* Mike Matsnev.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* $Id: MatroskaParser.c,v 1.75 2010/08/14 08:38:41 mike Exp $
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
@ -34,11 +40,19 @@
#include <setjmp.h>
#ifdef _WIN32
#ifdef _MSC_VER
// MS names some functions differently
#define alloca _alloca
#define inline __inline
#else /* _MSC_VER */
// support for building with MinGW on Windows
#include <malloc.h>
#endif /* _MSC_VER */
#include <tchar.h>
#endif /* _WIN32 */
#ifdef HAVE_ALLOCA_H
#include <alloca.h>
#endif
#ifndef EVCBUG
@ -56,6 +70,7 @@
#define EBML_MAX_SIZE_LENGTH 8
#define MATROSKA_VERSION 2
#define MATROSKA_DOCTYPE "matroska"
#define WEBM_DOCTYPE "webm"
#define MAX_STRING_LEN 1023
#define QSEGSIZE 512
@ -707,12 +722,22 @@ static inline ulonglong readVLUInt(MatroskaFile *mf) {
return readVLUIntImp(mf,NULL);
}
static ulonglong readSize(MatroskaFile *mf) {
static ulonglong readSizeUnspec(MatroskaFile *mf) {
int m;
ulonglong v = readVLUIntImp(mf,&m);
// see if it's unspecified
if (v == (MAXU64 >> (57-m*7)))
return MAXU64;
return v;
}
static ulonglong readSize(MatroskaFile *mf) {
ulonglong v = readSizeUnspec(mf);
// see if it's unspecified
if (v == MAXU64)
errorjmp(mf,"Unspecified element size is not supported here.");
return v;
@ -860,7 +885,7 @@ static void readLangCC(MatroskaFile *mf, ulonglong len, char lcc[4]) {
///////////////////////////////////////////////////////////////////////////
// file parser
#define FOREACH(f,tl) \
#define FOREACH2(f,tl,clid) \
{ \
ulonglong tmplen = (tl); \
{ \
@ -869,14 +894,18 @@ static void readLangCC(MatroskaFile *mf, ulonglong len, char lcc[4]) {
int id; \
for (;;) { \
cur = filepos(mf); \
if (cur == start + tmplen) \
if (tmplen != MAXU64 && cur == start + tmplen) \
break; \
id = readID(f); \
if (id==EOF) \
errorjmp(mf,"Unexpected EOF while reading EBML container"); \
len = readSize(mf); \
len = id == clid ? readSizeUnspec(mf) : readSize(mf); \
switch (id) {
#define FOREACH(f,tl) FOREACH2(f,tl,EOF)
#define RESTART() (tmplen=len),(start=cur)
#define ENDFOR1(f) \
default: \
skipbytes(f,len); \
@ -939,7 +968,7 @@ static void parseEBML(MatroskaFile *mf,ulonglong toplen) {
break;
case 0x4282: // DocType
readString(mf,len,buf,sizeof(buf));
if (strcmp(buf,MATROSKA_DOCTYPE))
if (strcmp(buf,MATROSKA_DOCTYPE) != 0 && strcmp(buf,WEBM_DOCTYPE) != 0)
errorjmp(mf,"Unsupported DocType: %s",buf);
break;
case 0x4287: // DocTypeVersion
@ -1074,31 +1103,57 @@ static void parseSegmentInfo(MatroskaFile *mf,ulonglong toplen) {
}
static void parseFirstCluster(MatroskaFile *mf,ulonglong toplen) {
ulonglong end = filepos(mf) + toplen;
int seenTimecode = 0, seenBlock = 0;
longlong tc;
ulonglong clstart = filepos(mf);
mf->seen.Cluster = 1;
mf->firstTimecode = 0;
FOREACH(mf,toplen)
FOREACH2(mf,toplen,0x1f43b675)
case 0xe7: // Timecode
mf->firstTimecode += readUInt(mf,(unsigned)len);
tc = readUInt(mf,(unsigned)len);
if (!seenTimecode) {
seenTimecode = 1;
mf->firstTimecode += tc;
}
if (seenBlock) {
out:
if (toplen != MAXU64)
skipbytes(mf,clstart + toplen - filepos(mf));
else if (len != MAXU64)
skipbytes(mf,cur + len - filepos(mf));
return;
}
break;
case 0xa3: // BlockEx
readVLUInt(mf); // track number
mf->firstTimecode += readSInt(mf, 2);
tc = readSInt(mf, 2);
if (!seenBlock) {
seenBlock = 1;
mf->firstTimecode += tc;
}
skipbytes(mf,end - filepos(mf));
return;
if (seenTimecode)
goto out;
break;
case 0xa0: // BlockGroup
FOREACH(mf,len)
case 0xa1: // Block
readVLUInt(mf); // track number
mf->firstTimecode += readSInt(mf,2);
tc = readSInt(mf,2);
if (!seenBlock) {
seenBlock = 1;
mf->firstTimecode += tc;
}
skipbytes(mf,end - filepos(mf));
return;
if (seenTimecode)
goto out;
ENDFOR(mf);
break;
case 0x1f43b675:
return;
ENDFOR(mf);
}
@ -1148,14 +1203,10 @@ static void parseVideoInfo(MatroskaFile *mf,ulonglong toplen,struct TrackInfo *t
break;
case 0x54b2: // DisplayUnit
v = readUInt(mf,(unsigned)len);
if (v>2)
errorjmp(mf,"Invalid DisplayUnit: %d",(int)v);
ti->AV.Video.DisplayUnit = (unsigned char)v;
break;
case 0x54b3: // AspectRatioType
v = readUInt(mf,(unsigned)len);
if (v>2)
errorjmp(mf,"Invalid AspectRatioType: %d",(int)v);
ti->AV.Video.AspectRatioType = (unsigned char)v;
break;
case 0x54aa: // PixelCropBottom
@ -1431,7 +1482,7 @@ static void parseTrackEntry(MatroskaFile *mf,ulonglong toplen) {
errorjmp(mf, "inflateInit failed");
zs.next_in = (Bytef *)cp;
zs.avail_in = cplen;
zs.avail_in = (uInt)cplen;
do {
zs.next_out = tmp;
@ -1449,7 +1500,7 @@ static void parseTrackEntry(MatroskaFile *mf,ulonglong toplen) {
inflateReset(&zs);
zs.next_in = (Bytef *)cp;
zs.avail_in = cplen;
zs.avail_in = (uInt)cplen;
zs.next_out = ncp;
zs.avail_out = ncplen;
@ -1988,7 +2039,7 @@ static void parseSegment(MatroskaFile *mf,ulonglong toplen) {
mf->flags &= ~MPF_ERROR;
else {
// we want to read data until we find a seekhead or a trackinfo
FOREACH(mf,toplen)
FOREACH2(mf,toplen,0x1f43b675)
case 0x114d9b74: // SeekHead
if (mf->flags & MKVF_AVOID_SEEKS) {
skipbytes(mf,len);
@ -2019,9 +2070,10 @@ static void parseSegment(MatroskaFile *mf,ulonglong toplen) {
case 0x1f43b675: // Cluster
if (!mf->pCluster)
mf->pCluster = cur;
if (mf->seen.Cluster)
skipbytes(mf,len);
else
if (mf->seen.Cluster) {
if (len != MAXU64)
skipbytes(mf,len);
} else
parseFirstCluster(mf,len);
break;
case 0x1654ae6b: // Tracks
@ -2347,8 +2399,8 @@ static int readMoreBlocks(MatroskaFile *mf) {
if (cid == EOF)
goto ex;
if (cid == 0x1f43b675) {
toplen = readSize(mf);
if (toplen < MAXCLUSTER) {
toplen = readSizeUnspec(mf);
if (toplen < MAXCLUSTER || toplen == MAXU64) {
// reset error flags
mf->flags &= ~MPF_ERROR;
ret = RBRESYNC;
@ -2373,12 +2425,15 @@ static int readMoreBlocks(MatroskaFile *mf) {
ret = EOF;
break;
}
toplen = readSize(mf);
toplen = cid == 0x1f43b675 ? readSizeUnspec(mf) : readSize(mf);
if (cid == 0x1f43b675) { // Cluster
unsigned char have_timecode = 0;
FOREACH(mf,toplen)
FOREACH2(mf,toplen,0x1f43b675)
case 0x1f43b675:
RESTART();
break;
case 0xe7: // Timecode
mf->tcCluster = readUInt(mf,(unsigned)len);
have_timecode = 1;
@ -2518,7 +2573,10 @@ static void reindex(MatroskaFile *mf) {
if (id != 0x1f43b675) // shouldn't happen
continue;
size = readVLUInt(mf);
size = readSizeUnspec(mf);
if (size == MAXU64)
break;
if (size >= MAXCLUSTER || size < 1024)
continue;
@ -2651,7 +2709,6 @@ static void parseFile(MatroskaFile *mf) {
ulonglong len = filepos(mf), adjust;
unsigned i;
int id = readID(mf);
int m;
if (id==EOF)
errorjmp(mf,"Unexpected EOF at start of file");
@ -2672,12 +2729,11 @@ static void parseFile(MatroskaFile *mf) {
if (id==EOF)
errorjmp(mf,"No segments found in the file");
segment:
len = readVLUIntImp(mf,&m);
// see if it's unspecified
if (len == (MAXU64 >> (57-m*7)))
len = MAXU64;
len = readSizeUnspec(mf);
if (id == 0x18538067) // Segment
break;
if (len == MAXU64)
errorjmp(mf,"No segments found in the file");
skipbytes(mf,len);
}

View File

@ -1,30 +1,32 @@
/*
* Copyright (c) 2004-2009 Mike Matsnev. All Rights Reserved.
* Copyright (c) 2004-2008 Mike Matsnev. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice immediately at the beginning of the file, without modification,
* this list of conditions, and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Absolutely no warranty of function or purpose is made by the author
* Mike Matsnev.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice immediately at the beginning of the file, without modification,
* this list of conditions, and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Absolutely no warranty of function or purpose is made by the author
* Mike Matsnev.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* $Id: MatroskaParser.h,v 1.22 2008/04/29 21:03:09 mike Exp $
*
*/
#ifndef MATROSKA_PARSER_H

View File

@ -23,11 +23,10 @@
void FFMatroskaVideo::Free(bool CloseCodec) {
if (CS)
cs_Destroy(CS);
if (TCC)
delete TCC;
if (MC.ST.fp) {
mkv_Close(MF);
fclose(MC.ST.fp);
}
if (CloseCodec)
avcodec_close(CodecContext);
@ -41,7 +40,7 @@ FFMatroskaVideo::FFMatroskaVideo(const char *SourceFile, int Track,
AVCodec *Codec = NULL;
CodecContext = NULL;
TrackInfo *TI = NULL;
CS = NULL;
TCC = NULL;
PacketNumber = 0;
VideoTrack = Track;
Frames = (*Index)[VideoTrack];
@ -57,7 +56,6 @@ FFMatroskaVideo::FFMatroskaVideo(const char *SourceFile, int Track,
MF = mkv_OpenEx(&MC.ST.base, 0, 0, ErrorMessage, sizeof(ErrorMessage));
if (MF == NULL) {
fclose(MC.ST.fp);
std::ostringstream buf;
buf << "Can't parse Matroska file: " << ErrorMessage;
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
@ -65,18 +63,12 @@ FFMatroskaVideo::FFMatroskaVideo(const char *SourceFile, int Track,
TI = mkv_GetTrackInfo(MF, VideoTrack);
if (TI->CompEnabled) {
CS = cs_Create(MF, VideoTrack, ErrorMessage, sizeof(ErrorMessage));
if (CS == NULL) {
Free(false);
std::ostringstream buf;
buf << "Can't create decompressor: " << ErrorMessage;
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
}
}
if (TI->CompEnabled)
TCC = new TrackCompressionContext(MF, TI, VideoTrack);
CodecContext = avcodec_alloc_context();
CodecContext->thread_count = Threads;
if (avcodec_thread_init(CodecContext, Threads))
CodecContext->thread_count = 1;
Codec = avcodec_find_decoder(MatroskaToFFCodecID(TI->CodecID, TI->CodecPrivate));
if (Codec == NULL)
@ -113,6 +105,14 @@ FFMatroskaVideo::FFMatroskaVideo(const char *SourceFile, int Track,
VP.ColorSpace = 0;
VP.ColorRange = 0;
#endif
// these pixfmt's are deprecated but still used
if (
CodecContext->pix_fmt == PIX_FMT_YUVJ420P
|| CodecContext->pix_fmt == PIX_FMT_YUVJ422P
|| CodecContext->pix_fmt == PIX_FMT_YUVJ444P
)
VP.ColorRange = AVCOL_RANGE_JPEG;
VP.FirstTime = ((Frames.front().PTS * Frames.TB.Num) / (double)Frames.TB.Den) / 1000;
VP.LastTime = ((Frames.back().PTS * Frames.TB.Num) / (double)Frames.TB.Den) / 1000;
@ -127,6 +127,11 @@ FFMatroskaVideo::FFMatroskaVideo(const char *SourceFile, int Track,
VP.FPSNumerator = 1000000;
}
// attempt to correct framerate to the proper NTSC fraction, if applicable
CorrectNTSCRationalFramerate(&VP.FPSNumerator, &VP.FPSDenominator);
// correct the timebase, if necessary
CorrectTimebase(&VP, &Frames.TB);
// Output the already decoded frame so it isn't wasted
OutputFrame(DecodeFrame);
@ -147,6 +152,15 @@ void FFMatroskaVideo::DecodeNextFrame() {
InitNullPacket(Packet);
unsigned int FrameSize;
if (InitialDecode == -1) {
if (DelayCounter > CodecContext->has_b_frames) {
DelayCounter--;
goto Done;
} else {
InitialDecode = 0;
}
}
while (PacketNumber < Frames.size()) {
// The additional indirection is because the packets are stored in
@ -154,10 +168,10 @@ void FFMatroskaVideo::DecodeNextFrame() {
// in the other sources where less is done manually
const TFrameInfo &FI = Frames[Frames[PacketNumber].OriginalPos];
FrameSize = FI.FrameSize;
ReadFrame(FI.FilePos, FrameSize, CS, MC);
ReadFrame(FI.FilePos, FrameSize, TCC, MC);
Packet.data = MC.Buffer;
Packet.size = FrameSize;
Packet.size = (TCC && TCC->CompressionMethod == COMP_PREPEND) ? FrameSize + TCC->CompressedPrivateDataSize : FrameSize;
if (FI.KeyFrame)
Packet.flags = AV_PKT_FLAG_KEY;
else
@ -167,15 +181,10 @@ void FFMatroskaVideo::DecodeNextFrame() {
avcodec_decode_video2(CodecContext, DecodeFrame, &FrameFinished, &Packet);
if (CodecContext->codec_id == CODEC_ID_MPEG4) {
if (IsPackedFrame(Packet)) {
MPEG4Counter++;
} else if (IsNVOP(Packet) && MPEG4Counter && FrameFinished) {
MPEG4Counter--;
} else if (IsNVOP(Packet) && !MPEG4Counter && !FrameFinished) {
goto Done;
}
}
if (!FrameFinished)
DelayCounter++;
if (DelayCounter > CodecContext->has_b_frames && !InitialDecode)
goto Done;
if (FrameFinished)
goto Done;
@ -192,7 +201,8 @@ void FFMatroskaVideo::DecodeNextFrame() {
goto Error;
Error:
Done:;
Done:
if (InitialDecode == 1) InitialDecode = -1;
}
FFMS_Frame *FFMatroskaVideo::GetFrame(int n) {
@ -203,13 +213,18 @@ FFMS_Frame *FFMatroskaVideo::GetFrame(int n) {
int ClosestKF = Frames.FindClosestVideoKeyFrame(n);
if (CurrentFrame > n || ClosestKF > CurrentFrame + 10) {
MPEG4Counter = 0;
DelayCounter = 0;
InitialDecode = 1;
PacketNumber = ClosestKF;
CurrentFrame = ClosestKF;
avcodec_flush_buffers(CodecContext);
}
do {
if (CurrentFrame+CodecContext->has_b_frames >= n)
CodecContext->skip_frame = AVDISCARD_DEFAULT;
else
CodecContext->skip_frame = AVDISCARD_NONREF;
DecodeNextFrame();
CurrentFrame++;
} while (CurrentFrame <= n);

View File

@ -21,6 +21,8 @@
#ifndef STDIOSTREAM_H
#define STDIOSTREAM_H
#undef __STRICT_ANSI__
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

View File

@ -20,15 +20,18 @@
#include <string.h>
#include <errno.h>
#include "utils.h"
#include "indexing.h"
#ifdef FFMS_USE_UTF8_PATHS
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#endif
# include <io.h>
# include <fcntl.h>
extern "C" {
# include "libavutil/avstring.h"
}
#endif // _WIN32
// Export the array but not its data type... fun...
@ -42,9 +45,15 @@ extern const AVCodecTag ff_codec_bmp_tags[];
extern const CodecTags ff_mkv_codec_tags[];
extern const AVCodecTag ff_codec_movvideo_tags[];
extern const AVCodecTag ff_codec_wav_tags[];
/* if you have this, we'll assume you have a new enough libavutil too */
#if LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(0, 12, 0)
# include <libavutil/opt.h>
#endif
}
extern int CPUFeatures;
extern bool GlobalUseUTF8Paths;
@ -65,7 +74,7 @@ int FFMS_Exception::CopyOut(FFMS_ErrorInfo *ErrorInfo) const {
if (ErrorInfo) {
ErrorInfo->ErrorType = _ErrorType;
ErrorInfo->SubType = _SubType;
if (ErrorInfo->BufferSize > 0) {
memset(ErrorInfo->Buffer, 0, ErrorInfo->BufferSize);
_Message.copy(ErrorInfo->Buffer, ErrorInfo->BufferSize - 1);
@ -75,8 +84,36 @@ int FFMS_Exception::CopyOut(FFMS_ErrorInfo *ErrorInfo) const {
return (_ErrorType << 16) | _SubType;
}
int GetSWSCPUFlags() {
int Flags = 0;
TrackCompressionContext::TrackCompressionContext(MatroskaFile *MF, TrackInfo *TI, unsigned int Track) {
CS = NULL;
CompressedPrivateData = NULL;
CompressedPrivateDataSize = 0;
CompressionMethod = TI->CompMethod;
if (CompressionMethod == COMP_ZLIB) {
char ErrorMessage[512];
CS = cs_Create(MF, Track, ErrorMessage, sizeof(ErrorMessage));
if (CS == NULL) {
std::ostringstream buf;
buf << "Can't create MKV track decompressor: " << ErrorMessage;
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
}
} else if (CompressionMethod == COMP_PREPEND) {
CompressedPrivateData = TI->CompMethodPrivate;
CompressedPrivateDataSize = TI->CompMethodPrivateSize;
} else {
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ,
"Can't create MKV track decompressor: unknown or unsupported compression method");
}
}
TrackCompressionContext::~TrackCompressionContext() {
if (CS)
cs_Destroy(CS);
}
int64_t GetSWSCPUFlags() {
int64_t Flags = 0;
if (CPUFeatures & FFMS_CPU_CAPS_MMX)
Flags |= SWS_CPU_CAPS_MMX;
@ -88,13 +125,65 @@ int GetSWSCPUFlags() {
Flags |= SWS_CPU_CAPS_ALTIVEC;
if (CPUFeatures & FFMS_CPU_CAPS_BFIN)
Flags |= SWS_CPU_CAPS_BFIN;
#ifdef SWS_CPU_CAPS_SSE2
if (CPUFeatures & FFMS_CPU_CAPS_SSE2)
Flags |= SWS_CPU_CAPS_SSE2;
#endif
return Flags;
}
static int handle_jpeg(PixelFormat *format)
{
switch (*format) {
case PIX_FMT_YUVJ420P: *format = PIX_FMT_YUV420P; return 1;
case PIX_FMT_YUVJ422P: *format = PIX_FMT_YUV422P; return 1;
case PIX_FMT_YUVJ444P: *format = PIX_FMT_YUV444P; return 1;
case PIX_FMT_YUVJ440P: *format = PIX_FMT_YUV440P; return 1;
default: return 0;
}
}
SwsContext *GetSwsContext(int SrcW, int SrcH, PixelFormat SrcFormat, int DstW, int DstH, PixelFormat DstFormat, int64_t Flags, int ColorSpace) {
#if LIBSWSCALE_VERSION_INT < AV_VERSION_INT(0, 12, 0)
return sws_getContext(SrcW, SrcH, SrcFormat, DstW, DstH, DstFormat, Flags, 0, 0, 0);
#else
SwsContext *Context = sws_alloc_context();
if (!Context) return 0;
if (ColorSpace == -1)
ColorSpace = (SrcW > 1024 || SrcH >= 600) ? SWS_CS_ITU709 : SWS_CS_DEFAULT;
int SrcRange = handle_jpeg(&SrcFormat);
int DstRange = handle_jpeg(&DstFormat);
av_set_int(Context, "sws_flags", Flags);
av_set_int(Context, "srcw", SrcW);
av_set_int(Context, "srch", SrcH);
av_set_int(Context, "dstw", DstW);
av_set_int(Context, "dsth", DstH);
av_set_int(Context, "src_range", SrcRange);
av_set_int(Context, "dst_range", DstRange);
av_set_int(Context, "src_format", SrcFormat);
av_set_int(Context, "dst_format", DstFormat);
sws_setColorspaceDetails(Context, sws_getCoefficients(ColorSpace), SrcRange, sws_getCoefficients(ColorSpace), DstRange, 0, 1<<16, 1<<16);
if(sws_init_context(Context, 0, 0) < 0){
sws_freeContext(Context);
return 0;
}
return Context;
#endif
}
int GetPPCPUFlags() {
int Flags = 0;
#ifdef FFMS_USE_POSTPROC
// not exactly a pretty solution but it'll never get called anyway
if (CPUFeatures & FFMS_CPU_CAPS_MMX)
Flags |= PP_CPU_CAPS_MMX;
if (CPUFeatures & FFMS_CPU_CAPS_MMX2)
@ -103,6 +192,7 @@ int GetPPCPUFlags() {
Flags |= PP_CPU_CAPS_3DNOW;
if (CPUFeatures & FFMS_CPU_CAPS_ALTIVEC)
Flags |= PP_CPU_CAPS_ALTIVEC;
#endif // FFMS_USE_POSTPROC
return Flags;
}
@ -126,8 +216,30 @@ FFMS_TrackType HaaliTrackTypeToFFTrackType(int TT) {
}
}
void ReadFrame(uint64_t FilePos, unsigned int &FrameSize, CompressedStream *CS, MatroskaReaderContext &Context) {
if (CS) {
const char *GetLAVCSampleFormatName(AVSampleFormat s) {
switch (s) {
case AV_SAMPLE_FMT_U8: return "8-bit unsigned integer";
case AV_SAMPLE_FMT_S16: return "16-bit signed integer";
case AV_SAMPLE_FMT_S32: return "32-bit signed integer";
case AV_SAMPLE_FMT_FLT: return "Single-precision floating point";
case AV_SAMPLE_FMT_DBL: return "Double-precision floating point";
default: return "Unknown";
}
}
template<class T> static void safe_aligned_reallocz(T *&ptr, size_t old_size, size_t new_size) {
void *newalloc = av_mallocz(new_size);
if (newalloc) {
memcpy(newalloc, ptr, FFMIN(old_size, new_size));
}
av_free(ptr);
ptr = static_cast<T*>(newalloc);
}
void ReadFrame(uint64_t FilePos, unsigned int &FrameSize, TrackCompressionContext *TCC, MatroskaReaderContext &Context) {
memset(Context.Buffer, 0, Context.BufferSize); // necessary to avoid lavc hurfing a durf with some mpeg4 video streams
if (TCC && TCC->CS) {
CompressedStream *CS = TCC->CS;
unsigned int DecompressedFrameSize = 0;
cs_NextFrame(CS, FilePos, FrameSize);
@ -142,17 +254,16 @@ void ReadFrame(uint64_t FilePos, unsigned int &FrameSize, CompressedStream *CS,
if (ReadBytes == 0) {
FrameSize = DecompressedFrameSize;
memset(Context.Buffer + DecompressedFrameSize, 0,
Context.BufferSize + FF_INPUT_BUFFER_PADDING_SIZE - DecompressedFrameSize);
return;
}
if (Context.BufferSize < DecompressedFrameSize + ReadBytes) {
Context.BufferSize = DecompressedFrameSize + ReadBytes;
Context.Buffer = (uint8_t *)realloc(Context.Buffer, Context.BufferSize + FF_INPUT_BUFFER_PADDING_SIZE);
if (Context.BufferSize < DecompressedFrameSize + ReadBytes + FF_INPUT_BUFFER_PADDING_SIZE) {
size_t NewSize = (DecompressedFrameSize + ReadBytes) * 2;
safe_aligned_reallocz(Context.Buffer, Context.BufferSize, NewSize);
if (Context.Buffer == NULL)
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_ALLOCATION_FAILED,
"Out of memory");
Context.BufferSize = NewSize;
}
memcpy(Context.Buffer + DecompressedFrameSize, Context.CSBuffer, ReadBytes);
@ -165,15 +276,37 @@ void ReadFrame(uint64_t FilePos, unsigned int &FrameSize, CompressedStream *CS,
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_SEEKING, buf.str());
}
if (Context.BufferSize < FrameSize) {
Context.BufferSize = FrameSize;
Context.Buffer = (uint8_t *)realloc(Context.Buffer, Context.BufferSize + 16);
if (TCC && TCC->CompressionMethod == COMP_PREPEND) {
unsigned ReqBufsize = FrameSize + TCC->CompressedPrivateDataSize + FF_INPUT_BUFFER_PADDING_SIZE;
if (Context.BufferSize < ReqBufsize) {
size_t NewSize = ReqBufsize * 2;
safe_aligned_reallocz(Context.Buffer, Context.BufferSize, NewSize);
if (Context.Buffer == NULL)
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_ALLOCATION_FAILED, "Out of memory");
Context.BufferSize = NewSize;
}
/* // maybe faster? maybe not?
for (int i=0; i < TCC->CompressedPrivateDataSize; i++)
*(Context.Buffer)++ = ((uint8_t *)TCC->CompressedPrivateData)[i];
*/
// screw it, memcpy and fuck the losers who use header compression
memcpy(Context.Buffer, TCC->CompressedPrivateData, TCC->CompressedPrivateDataSize);
}
else if (Context.BufferSize < FrameSize + FF_INPUT_BUFFER_PADDING_SIZE) {
size_t NewSize = FrameSize * 2;
safe_aligned_reallocz(Context.Buffer, Context.BufferSize, NewSize);
if (Context.Buffer == NULL)
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_ALLOCATION_FAILED,
"Out of memory");
Context.BufferSize = NewSize;
}
size_t ReadBytes = fread(Context.Buffer, 1, FrameSize, Context.ST.fp);
uint8_t *TargetPtr = Context.Buffer;
if (TCC && TCC->CompressionMethod == COMP_PREPEND)
TargetPtr += TCC->CompressedPrivateDataSize;
size_t ReadBytes = fread(TargetPtr, 1, FrameSize, Context.ST.fp);
if (ReadBytes != FrameSize) {
if (ReadBytes == 0) {
if (feof(Context.ST.fp)) {
@ -202,32 +335,20 @@ void InitNullPacket(AVPacket &pkt) {
pkt.size = 0;
}
bool IsPackedFrame(AVPacket &pkt) {
for (int i = 0; i < pkt.size - 5; i++)
if (pkt.data[i] == 0x00 && pkt.data[i + 1] == 0x00 && pkt.data[i + 2] == 0x01 && pkt.data[i + 3] == 0xB6 && (pkt.data[i + 4] & 0x40))
for (i = i + 5; i < pkt.size - 5; i++)
if (pkt.data[i] == 0x00 && pkt.data[i + 1] == 0x00 && pkt.data[i + 2] == 0x01 && pkt.data[i + 3] == 0xB6 && (pkt.data[i + 4] & 0xC0) == 0x80)
return true;
return false;
}
bool IsNVOP(AVPacket &pkt) {
static const uint8_t MPEG4NVOP[] = { 0x00, 0x00, 0x01, 0xB6 };
return (pkt.size >= 4 && pkt.size <= 8) && !memcmp(pkt.data, MPEG4NVOP, 4);
}
void FillAP(FFMS_AudioProperties &AP, AVCodecContext *CTX, FFMS_Track &Frames) {
AP.SampleFormat = static_cast<FFMS_SampleFormat>(CTX->sample_fmt);
AP.BitsPerSample = av_get_bits_per_sample_format(CTX->sample_fmt);
if (CTX->sample_fmt == SAMPLE_FMT_S32 && CTX->bits_per_raw_sample)
AP.BitsPerSample = av_get_bits_per_sample_fmt(CTX->sample_fmt);
if (CTX->sample_fmt == AV_SAMPLE_FMT_S32 && CTX->bits_per_raw_sample)
AP.BitsPerSample = CTX->bits_per_raw_sample;
AP.Channels = CTX->channels;;
AP.ChannelLayout = CTX->channel_layout;
AP.SampleRate = CTX->sample_rate;
AP.NumSamples = (Frames.back()).SampleStart + (Frames.back()).SampleCount;
AP.FirstTime = ((Frames.front().PTS * Frames.TB.Num) / (double)Frames.TB.Den) / 1000;
AP.LastTime = ((Frames.back().PTS * Frames.TB.Num) / (double)Frames.TB.Den) / 1000;
if (Frames.size() > 0) {
AP.NumSamples = (Frames.back()).SampleStart + (Frames.back()).SampleCount;
AP.FirstTime = ((Frames.front().PTS * Frames.TB.Num) / (double)Frames.TB.Den) / 1000;
AP.LastTime = ((Frames.back().PTS * Frames.TB.Num) / (double)Frames.TB.Den) / 1000;
}
}
#ifdef HAALISOURCE
@ -271,7 +392,7 @@ CodecID MatroskaToFFCodecID(char *Codec, void *CodecPrivate, unsigned int FourCC
case 16: CID = CODEC_ID_PCM_S16LE; break;
case 24: CID = CODEC_ID_PCM_S24LE; break;
case 32: CID = CODEC_ID_PCM_S32LE; break;
}
}
break;
case CODEC_ID_PCM_S16BE:
switch (BitsPerSample) {
@ -279,10 +400,12 @@ CodecID MatroskaToFFCodecID(char *Codec, void *CodecPrivate, unsigned int FourCC
case 16: CID = CODEC_ID_PCM_S16BE; break;
case 24: CID = CODEC_ID_PCM_S24BE; break;
case 32: CID = CODEC_ID_PCM_S32BE; break;
}
}
break;
default:
break;
}
return CID;
}
}
@ -327,75 +450,139 @@ void InitializeCodecContextFromMatroskaTrackInfo(TrackInfo *TI, AVCodecContext *
#ifdef HAALISOURCE
void InitializeCodecContextFromHaaliInfo(CComQIPtr<IPropertyBag> pBag, AVCodecContext *CodecContext) {
if (pBag) {
CComVariant pV;
FFCodecContext InitializeCodecContextFromHaaliInfo(CComQIPtr<IPropertyBag> pBag) {
CComVariant pV;
if (FAILED(pBag->Read(L"Type", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4)))
return FFCodecContext();
unsigned int TT = pV.uintVal;
FFCodecContext CodecContext(avcodec_alloc_context(), DeleteHaaliCodecContext);
unsigned int FourCC = 0;
if (TT == TT_VIDEO) {
pV.Clear();
if (SUCCEEDED(pBag->Read(L"Video.PixelWidth", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4)))
CodecContext->coded_width = pV.uintVal;
pV.Clear();
if (SUCCEEDED(pBag->Read(L"Type", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4))) {
if (SUCCEEDED(pBag->Read(L"Video.PixelHeight", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4)))
CodecContext->coded_height = pV.uintVal;
unsigned int TT = pV.uintVal;
pV.Clear();
if (SUCCEEDED(pBag->Read(L"FOURCC", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4)))
FourCC = pV.uintVal;
if (TT == TT_VIDEO) {
pV.Clear();
if (SUCCEEDED(pBag->Read(L"Video.PixelWidth", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4)))
CodecContext->coded_width = pV.uintVal;
pV.Clear();
if (SUCCEEDED(pBag->Read(L"Video.PixelHeight", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4)))
CodecContext->coded_height = pV.uintVal;
} else if (TT == TT_AUDIO) {
pV.Clear();
if (SUCCEEDED(pBag->Read(L"Audio.SamplingFreq", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4)))
CodecContext->sample_rate = pV.uintVal;
pV.Clear();
if (SUCCEEDED(pBag->Read(L"Audio.BitDepth", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4)))
CodecContext->bits_per_coded_sample = pV.uintVal;
pV.Clear();
if (SUCCEEDED(pBag->Read(L"Audio.Channels", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4)))
CodecContext->channels = pV.uintVal;
}
FFMS_BITMAPINFOHEADER bih;
memset(&bih, 0, sizeof bih);
if (FourCC) {
// Reconstruct the missing codec private part for VC1
bih.biSize = sizeof bih;
bih.biCompression = FourCC;
bih.biBitCount = 24;
bih.biPlanes = 1;
bih.biHeight = CodecContext->coded_height;
}
}
pV.Clear();
if (SUCCEEDED(pBag->Read(L"CodecPrivate", &pV, NULL))) {
bih.biSize += vtSize(pV);
CodecContext->extradata = static_cast<uint8_t*>(av_malloc(bih.biSize));
// prepend BITMAPINFOHEADER if there's anything interesting in it (i.e. we're decoding VC1)
if (FourCC)
memcpy(CodecContext->extradata, &bih, sizeof bih);
vtCopy(pV, CodecContext->extradata + (FourCC ? sizeof bih : 0));
}
// use the BITMAPINFOHEADER only. not sure if this is correct or if it's ever going to be used...
else {
CodecContext->extradata = static_cast<uint8_t*>(av_malloc(bih.biSize));
memcpy(CodecContext->extradata, &bih, sizeof bih);
}
CodecContext->extradata_size = bih.biSize;
}
else if (TT == TT_AUDIO) {
pV.Clear();
if (SUCCEEDED(pBag->Read(L"CodecPrivate", &pV, NULL))) {
CodecContext->extradata_size = vtSize(pV);
CodecContext->extradata = static_cast<uint8_t*>(av_malloc(CodecContext->extradata_size));
vtCopy(pV, CodecContext->extradata);
}
pV.Clear();
if (SUCCEEDED(pBag->Read(L"Audio.SamplingFreq", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4)))
CodecContext->sample_rate = pV.uintVal;
pV.Clear();
if (SUCCEEDED(pBag->Read(L"Audio.BitDepth", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4)))
CodecContext->bits_per_coded_sample = pV.uintVal;
pV.Clear();
if (SUCCEEDED(pBag->Read(L"Audio.Channels", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_UI4)))
CodecContext->channels = pV.uintVal;
}
pV.Clear();
if (SUCCEEDED(pBag->Read(L"CodecID", &pV, NULL)) && SUCCEEDED(pV.ChangeType(VT_BSTR))) {
char CodecStr[2048];
wcstombs(CodecStr, pV.bstrVal, 2000);
CodecContext->codec = avcodec_find_decoder(MatroskaToFFCodecID(CodecStr, CodecContext->extradata, FourCC, CodecContext->bits_per_coded_sample));
}
return CodecContext;
}
#endif
// All this filename chikanery that follows is supposed to make sure both local
// codepage (used by avisynth etc) and UTF8 (potentially used by API users) strings
// work correctly on Win32.
// It's a really ugly hack, and I blame Microsoft for it.
#ifdef _WIN32
static wchar_t *dup_char_to_wchar(const char *s, unsigned int cp) {
wchar_t *w;
int l;
if (!(l = MultiByteToWideChar(cp, MB_ERR_INVALID_CHARS, s, -1, NULL, 0)))
return NULL;
if (!(w = (wchar_t *)malloc(l * sizeof(wchar_t))))
return NULL;
if (MultiByteToWideChar(cp, MB_ERR_INVALID_CHARS, s, -1 , w, l) <= 0) {
free(w);
w = NULL;
}
return w;
}
#endif
FILE *ffms_fopen(const char *filename, const char *mode) {
#ifdef FFMS_USE_UTF8_PATHS
// Hack: support utf8-in-char* filenames on windows
wchar_t filename_wide[MAX_PATH*2];
// 64 characters of mode string ought to be more than enough for everyone
wchar_t mode_wide[64];
if ((MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, filename, -1, filename_wide, MAX_PATH) > 0)
&& (MultiByteToWideChar(CP_ACP, NULL, mode, -1, mode_wide, 60) > 0))
return _wfopen(filename_wide, mode_wide);
#ifdef _WIN32
unsigned int codepage;
if (GlobalUseUTF8Paths)
codepage = CP_UTF8;
else
return fopen(filename, mode);
codepage = CP_ACP;
FILE *ret;
wchar_t *filename_wide = dup_char_to_wchar(filename, codepage);
wchar_t *mode_wide = dup_char_to_wchar(mode, codepage);
if (filename_wide && mode_wide)
ret = _wfopen(filename_wide, mode_wide);
else
ret = fopen(filename, mode);
free(filename_wide);
free(mode_wide);
return ret;
#else
return fopen(filename, mode);
#endif
#endif /* _WIN32 */
}
size_t ffms_mbstowcs (wchar_t *wcstr, const char *mbstr, size_t max) {
#ifdef FFMS_USE_UTF8_PATHS
// try utf8 first
int len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mbstr, -1, NULL, 0);
if (len > 0) {
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mbstr, -1, wcstr, max);
return static_cast<size_t>(len);
}
// failed, use local ANSI codepage
else {
len = MultiByteToWideChar(CP_ACP, NULL, mbstr, -1, wcstr, max);
return static_cast<size_t>(len);
}
#ifdef _WIN32
// this is only called by HaaliOpenFile anyway, so I think this is safe
return static_cast<size_t>(MultiByteToWideChar((GlobalUseUTF8Paths ? CP_UTF8 : CP_ACP), MB_ERR_INVALID_CHARS, mbstr, -1, wcstr, max));
#else
return mbstowcs(wcstr, mbstr, max);
#endif
@ -404,22 +591,82 @@ size_t ffms_mbstowcs (wchar_t *wcstr, const char *mbstr, size_t max) {
// ffms_fstream stuff
void ffms_fstream::open(const char *filename, std::ios_base::openmode mode) {
#ifdef FFMS_USE_UTF8_PATHS
// Hack: support utf8-in-char* filenames on windows
wchar_t filename_wide[MAX_PATH*2];
if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, filename, -1, filename_wide, MAX_PATH) > 0)
// Unlike MSVC, mingw's iostream library doesn't have an fstream overload
// that takes a wchar_t* filename, which means you can't open unicode
// filenames with it on Windows. gg.
#if defined(_WIN32) && !defined(__MINGW32__)
unsigned int codepage = GlobalUseUTF8Paths ? CP_UTF8 : CP_ACP;
wchar_t *filename_wide = dup_char_to_wchar(filename, codepage);
if (filename_wide)
std::fstream::open(filename_wide, mode);
else
std::fstream::open(filename, mode);
#else
free(filename_wide);
#else // defined(_WIN32) && !defined(__MINGW32__)
std::fstream::open(filename, mode);
#endif
#endif // defined(_WIN32) && !defined(__MINGW32__)
}
ffms_fstream::ffms_fstream(const char *filename, std::ios_base::openmode mode) {
open(filename, mode);
}
#ifdef _WIN32
int ffms_wchar_open(const char *fname, int oflags, int pmode) {
wchar_t *wfname = dup_char_to_wchar(fname, CP_UTF8);
if (wfname) {
int ret = _wopen(wfname, oflags, pmode);
free(wfname);
return ret;
}
return -1;
}
static int ffms_lavf_file_open(URLContext *h, const char *filename, int flags) {
int access;
int fd;
av_strstart(filename, "file:", &filename);
if (flags & URL_RDWR) {
access = _O_CREAT | _O_TRUNC | _O_RDWR;
} else if (flags & URL_WRONLY) {
access = _O_CREAT | _O_TRUNC | _O_WRONLY;
} else {
access = _O_RDONLY;
}
#ifdef _O_BINARY
access |= _O_BINARY;
#endif
fd = ffms_wchar_open(filename, access, 0666);
if (fd == -1)
return AVERROR(ENOENT);
h->priv_data = (void *) (intptr_t) fd;
return 0;
}
// Hijack lavf's file protocol handler's open function and use our own instead.
// Hack by nielsm.
void ffms_patch_lavf_file_open() {
URLProtocol *proto = av_protocol_next(NULL);
while (proto != NULL) {
if (strcmp("file", proto->name) == 0) {
break;
}
proto = proto->next;
}
if (proto != NULL) {
proto->url_open = &ffms_lavf_file_open;
}
}
#endif // _WIN32
// End of filename hackery.
#ifdef HAALISOURCE
CComPtr<IMMContainer> HaaliOpenFile(const char *SourceFile, enum FFMS_Sources SourceMode) {
@ -465,12 +712,10 @@ CComPtr<IMMContainer> HaaliOpenFile(const char *SourceFile, enum FFMS_Sources So
#endif
void LAVFOpenFile(const char *SourceFile, AVFormatContext *&FormatContext) {
if (av_open_input_file(&FormatContext, SourceFile, NULL, 0, NULL) != 0) {
std::ostringstream buf;
buf << "Couldn't open '" << SourceFile << "'";
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ, buf.str());
}
if (av_open_input_file(&FormatContext, SourceFile, NULL, 0, NULL) != 0)
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_FILE_READ,
std::string("Couldn't open '") + SourceFile + "'");
if (av_find_stream_info(FormatContext) < 0) {
av_close_input_file(FormatContext);
FormatContext = NULL;
@ -478,3 +723,39 @@ void LAVFOpenFile(const char *SourceFile, AVFormatContext *&FormatContext) {
"Couldn't find stream information");
}
}
// attempt to correct framerate to the proper NTSC fraction, if applicable
// code stolen from Perian
void CorrectNTSCRationalFramerate(int *Num, int *Den) {
AVRational TempFPS;
TempFPS.den = *Num; // not a typo
TempFPS.num = *Den; // still not a typo
av_reduce(&TempFPS.num, &TempFPS.den, TempFPS.num, TempFPS.den, INT_MAX);
if (TempFPS.num == 1) {
*Num = TempFPS.den;
*Den = TempFPS.num;
}
else {
double FTimebase = av_q2d(TempFPS);
double NearestNTSC = floor(FTimebase * 1001.0 + 0.5) / 1001.0;
const double SmallInterval = 1.0/120.0;
if (fabs(FTimebase - NearestNTSC) < SmallInterval) {
*Num = int((1001.0 / FTimebase) + 0.5);
*Den = 1001;
}
}
}
// correct the timebase if it is invalid
void CorrectTimebase(FFMS_VideoProperties *VP, FFMS_TrackTimeBase *TTimebase) {
double Timebase = (double)TTimebase->Num / TTimebase->Den;
double FPS = (double)VP->FPSNumerator / VP->FPSDenominator;
if ((1000/Timebase) / FPS < 1) {
TTimebase->Den = VP->FPSNumerator;
TTimebase->Num = (int64_t)VP->FPSDenominator * 1000;
}
}

View File

@ -30,10 +30,13 @@
extern "C" {
#include "stdiostream.h"
#include <libavutil/mem.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#ifdef FFMS_USE_POSTPROC
#include <libpostproc/postprocess.h>
#endif // FFMS_USE_POSTPROC
}
// must be included after ffmpeg headers
@ -57,17 +60,17 @@ const int64_t ffms_av_nopts_value = static_cast<int64_t>(1) << 63;
// used for matroska<->ffmpeg codec ID mapping to avoid Win32 dependency
typedef struct FFMS_BITMAPINFOHEADER {
uint32_t biSize;
int32_t biWidth;
int32_t biHeight;
uint16_t biPlanes;
uint16_t biBitCount;
uint32_t biCompression;
uint32_t biSizeImage;
int32_t biXPelsPerMeter;
int32_t biYPelsPerMeter;
uint32_t biClrUsed;
uint32_t biClrImportant;
uint32_t biSize;
int32_t biWidth;
int32_t biHeight;
uint16_t biPlanes;
uint16_t biBitCount;
uint32_t biCompression;
uint32_t biSizeImage;
int32_t biXPelsPerMeter;
int32_t biYPelsPerMeter;
uint32_t biClrUsed;
uint32_t biClrImportant;
} FFMS_BITMAPINFOHEADER;
class FFMS_Exception : public std::exception {
@ -110,8 +113,35 @@ public:
_Arg = Arg;
}
};
// auto_ptr-ish holder for AVCodecContexts with overridable deleter
class FFCodecContext {
AVCodecContext *CodecContext;
void (*Deleter)(AVCodecContext *);
public:
FFCodecContext() : CodecContext(0), Deleter(0) { }
FFCodecContext(FFCodecContext &r) : CodecContext(r.CodecContext), Deleter(r.Deleter) { r.CodecContext = 0; }
FFCodecContext(AVCodecContext *c, void (*d)(AVCodecContext *)) : CodecContext(c), Deleter(d) { }
FFCodecContext& operator=(FFCodecContext r) { reset(r.CodecContext, r.Deleter); r.CodecContext = 0; return *this; }
~FFCodecContext() { reset(); }
AVCodecContext* operator->() { return CodecContext; }
operator AVCodecContext*() { return CodecContext; }
void reset(AVCodecContext *c = 0, void (*d)(AVCodecContext *) = 0) {
if (CodecContext && Deleter) Deleter(CodecContext);
CodecContext = c;
Deleter = d;
}
};
struct MatroskaReaderContext {
inline void DeleteHaaliCodecContext(AVCodecContext *CodecContext) {
av_freep(&CodecContext->extradata);
av_freep(&CodecContext);
}
inline void DeleteMatroskaCodecContext(AVCodecContext *CodecContext) {
avcodec_close(CodecContext);
av_freep(&CodecContext);
}
class MatroskaReaderContext {
public:
StdIoStream ST;
uint8_t *Buffer;
@ -120,12 +150,16 @@ public:
MatroskaReaderContext() {
InitStdIoStream(&ST);
Buffer = NULL;
BufferSize = 0;
Buffer = static_cast<uint8_t *>(av_mallocz(16384)); // arbitrarily decided number
if (!Buffer)
throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_ALLOCATION_FAILED, "Out of memory");
BufferSize = 16384;
}
~MatroskaReaderContext() {
free(Buffer);
if (Buffer)
av_free(Buffer);
if (ST.fp) fclose(ST.fp);
}
};
@ -135,28 +169,67 @@ public:
ffms_fstream(const char *filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out);
};
int GetSWSCPUFlags();
template <typename T>
class AlignedBuffer {
T *buf;
public:
AlignedBuffer(size_t n = 1, bool zero = false) {
buf = (T*) av_malloc(sizeof(*buf) * n);
if (!buf) throw std::bad_alloc();
}
~AlignedBuffer() {
av_free(buf);
buf = 0;
}
const T &operator[] (size_t i) const { return buf[i]; }
T &operator[] (size_t i) { return buf[i]; }
};
class TrackCompressionContext {
public:
CompressedStream *CS;
unsigned CompressionMethod;
void *CompressedPrivateData;
unsigned CompressedPrivateDataSize;
TrackCompressionContext(MatroskaFile *MF, TrackInfo *TI, unsigned int Track);
~TrackCompressionContext();
};
int64_t GetSWSCPUFlags();
SwsContext *GetSwsContext(int SrcW, int SrcH, PixelFormat SrcFormat, int DstW, int DstH, PixelFormat DstFormat, int64_t Flags, int ColorSpace = -1);
int GetPPCPUFlags();
void ClearErrorInfo(FFMS_ErrorInfo *ErrorInfo);
FFMS_TrackType HaaliTrackTypeToFFTrackType(int TT);
void ReadFrame(uint64_t FilePos, unsigned int &FrameSize, CompressedStream *CS, MatroskaReaderContext &Context);
bool AudioFMTIsFloat(SampleFormat FMT);
void ReadFrame(uint64_t FilePos, unsigned int &FrameSize, TrackCompressionContext *TCC, MatroskaReaderContext &Context);
bool AudioFMTIsFloat(AVSampleFormat FMT);
void InitNullPacket(AVPacket &pkt);
bool IsPackedFrame(AVPacket &pkt);
bool IsNVOP(AVPacket &pkt);
void FillAP(FFMS_AudioProperties &AP, AVCodecContext *CTX, FFMS_Track &Frames);
#ifdef HAALISOURCE
unsigned vtSize(VARIANT &vt);
void vtCopy(VARIANT& vt,void *dest);
void InitializeCodecContextFromHaaliInfo(CComQIPtr<IPropertyBag> pBag, AVCodecContext *CodecContext);
FFCodecContext InitializeCodecContextFromHaaliInfo(CComQIPtr<IPropertyBag> pBag);
#endif
void InitializeCodecContextFromMatroskaTrackInfo(TrackInfo *TI, AVCodecContext *CodecContext);
CodecID MatroskaToFFCodecID(char *Codec, void *CodecPrivate, unsigned int FourCC = 0, unsigned int BitsPerSample = 0);
FILE *ffms_fopen(const char *filename, const char *mode);
size_t ffms_mbstowcs (wchar_t *wcstr, const char *mbstr, size_t max);
#ifdef _WIN32
void ffms_patch_lavf_file_open();
#endif // _WIN32
#ifdef HAALISOURCE
CComPtr<IMMContainer> HaaliOpenFile(const char *SourceFile, enum FFMS_Sources SourceMode);
#endif
#endif // HAALISOURCE
void LAVFOpenFile(const char *SourceFile, AVFormatContext *&FormatContext);
void CorrectNTSCRationalFramerate(int *Num, int *Den);
void CorrectTimebase(FFMS_VideoProperties *VP, FFMS_TrackTimeBase *TTimebase);
const char *GetLAVCSampleFormatName(AVSampleFormat s);
#endif

View File

@ -27,6 +27,8 @@ void FFMS_VideoSource::GetFrameCheck(int n) {
}
void FFMS_VideoSource::SetPP(const char *PP) {
#ifdef FFMS_USE_POSTPROC
if (PPMode)
pp_free_mode(PPMode);
PPMode = NULL;
@ -43,9 +45,14 @@ void FFMS_VideoSource::SetPP(const char *PP) {
ReAdjustPP(CodecContext->pix_fmt, CodecContext->width, CodecContext->height);
OutputFrame(DecodeFrame);
#else
throw FFMS_Exception(FFMS_ERROR_POSTPROCESSING, FFMS_ERROR_UNSUPPORTED,
"FFMS2 was not compiled with postprocessing support");
#endif /* FFMS_USE_POSTPROC */
}
void FFMS_VideoSource::ResetPP() {
#ifdef FFMS_USE_POSTPROC
if (PPContext)
pp_free_context(PPContext);
PPContext = NULL;
@ -54,10 +61,12 @@ void FFMS_VideoSource::ResetPP() {
pp_free_mode(PPMode);
PPMode = NULL;
#endif /* FFMS_USE_POSTPROC */
OutputFrame(DecodeFrame);
}
void FFMS_VideoSource::ReAdjustPP(PixelFormat VPixelFormat, int Width, int Height) {
#ifdef FFMS_USE_POSTPROC
if (PPContext)
pp_free_context(PPContext);
PPContext = NULL;
@ -68,10 +77,17 @@ void FFMS_VideoSource::ReAdjustPP(PixelFormat VPixelFormat, int Width, int Heigh
int Flags = GetPPCPUFlags();
switch (VPixelFormat) {
case PIX_FMT_YUV420P: Flags |= PP_FORMAT_420; break;
case PIX_FMT_YUV422P: Flags |= PP_FORMAT_422; break;
case PIX_FMT_YUV411P: Flags |= PP_FORMAT_411; break;
case PIX_FMT_YUV444P: Flags |= PP_FORMAT_444; break;
case PIX_FMT_YUV420P:
case PIX_FMT_YUVJ420P:
Flags |= PP_FORMAT_420; break;
case PIX_FMT_YUV422P:
case PIX_FMT_YUVJ422P:
Flags |= PP_FORMAT_422; break;
case PIX_FMT_YUV411P:
Flags |= PP_FORMAT_411; break;
case PIX_FMT_YUV444P:
case PIX_FMT_YUVJ444P:
Flags |= PP_FORMAT_444; break;
default:
ResetPP();
throw FFMS_Exception(FFMS_ERROR_POSTPROCESSING, FFMS_ERROR_UNSUPPORTED,
@ -82,8 +98,13 @@ void FFMS_VideoSource::ReAdjustPP(PixelFormat VPixelFormat, int Width, int Heigh
avpicture_free(&PPFrame);
avpicture_alloc(&PPFrame, VPixelFormat, Width, Height);
#else
return;
#endif /* FFMS_USE_POSTPROC */
}
static void CopyAVPictureFields(AVPicture &Picture, FFMS_Frame &Dst) {
for (int i = 0; i < 4; i++) {
Dst.Data[i] = Picture.data[i];
@ -99,17 +120,18 @@ FFMS_Frame *FFMS_VideoSource::OutputFrame(AVFrame *Frame) {
ReAdjustOutputFormat(TargetPixelFormats, TargetWidth, TargetHeight, TargetResizer);
}
#ifdef FFMS_USE_POSTPROC
if (PPMode) {
pp_postprocess(const_cast<const uint8_t **>(Frame->data), Frame->linesize, PPFrame.data, PPFrame.linesize, CodecContext->width, CodecContext->height, Frame->qscale_table, Frame->qstride, PPMode, PPContext, Frame->pict_type | (Frame->qscale_type ? PP_PICT_TYPE_QP2 : 0));
if (SWS) {
sws_scale(SWS, PPFrame.data, PPFrame.linesize, 0, CodecContext->height, SWSFrame.data, SWSFrame.linesize);
sws_scale(SWS, const_cast<FFMS_SWS_CONST_PARAM uint8_t **>(PPFrame.data), PPFrame.linesize, 0, CodecContext->height, SWSFrame.data, SWSFrame.linesize);
CopyAVPictureFields(SWSFrame, LocalFrame);
} else {
CopyAVPictureFields(PPFrame, LocalFrame);
}
} else {
if (SWS) {
sws_scale(SWS, Frame->data, Frame->linesize, 0, CodecContext->height, SWSFrame.data, SWSFrame.linesize);
sws_scale(SWS, const_cast<FFMS_SWS_CONST_PARAM uint8_t **>(Frame->data), Frame->linesize, 0, CodecContext->height, SWSFrame.data, SWSFrame.linesize);
CopyAVPictureFields(SWSFrame, LocalFrame);
} else {
// Special case to avoid ugly casts
@ -119,6 +141,18 @@ FFMS_Frame *FFMS_VideoSource::OutputFrame(AVFrame *Frame) {
}
}
}
#else // FFMS_USE_POSTPROC
if (SWS) {
sws_scale(SWS, const_cast<FFMS_SWS_CONST_PARAM uint8_t **>(Frame->data), Frame->linesize, 0, CodecContext->height, SWSFrame.data, SWSFrame.linesize);
CopyAVPictureFields(SWSFrame, LocalFrame);
} else {
// Special case to avoid ugly casts
for (int i = 0; i < 4; i++) {
LocalFrame.Data[i] = Frame->data[i];
LocalFrame.Linesize[i] = Frame->linesize[i];
}
}
#endif // FFMS_USE_POSTPROC
LocalFrame.EncodedWidth = CodecContext->width;
LocalFrame.EncodedHeight = CodecContext->height;
@ -148,7 +182,7 @@ FFMS_VideoSource::FFMS_VideoSource(const char *SourceFile, FFMS_Index *Index, in
throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT,
"Not a video track");
if (Index[Track].size() == 0)
if (Index->at(Track).size() == 0)
throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT,
"Video track contains no frames");
@ -157,12 +191,15 @@ FFMS_VideoSource::FFMS_VideoSource(const char *SourceFile, FFMS_Index *Index, in
"The index does not match the source file");
memset(&VP, 0, sizeof(VP));
#ifdef FFMS_USE_POSTPROC
PPContext = NULL;
PPMode = NULL;
#endif // FFMS_USE_POSTPROC
SWS = NULL;
LastFrameNum = 0;
CurrentFrame = 1;
MPEG4Counter = 0;
DelayCounter = 0;
InitialDecode = 1;
CodecContext = NULL;
LastFrameHeight = -1;
LastFrameWidth = -1;
@ -175,21 +212,26 @@ FFMS_VideoSource::FFMS_VideoSource(const char *SourceFile, FFMS_Index *Index, in
DecodeFrame = avcodec_alloc_frame();
// Dummy allocations so the unallocated case doesn't have to be handled later
#ifdef FFMS_USE_POSTPROC
avpicture_alloc(&PPFrame, PIX_FMT_GRAY8, 16, 16);
#endif // FFMS_USE_POSTPROC
avpicture_alloc(&SWSFrame, PIX_FMT_GRAY8, 16, 16);
}
FFMS_VideoSource::~FFMS_VideoSource() {
#ifdef FFMS_USE_POSTPROC
if (PPMode)
pp_free_mode(PPMode);
if (PPContext)
pp_free_context(PPContext);
avpicture_free(&PPFrame);
#endif // FFMS_USE_POSTPROC
if (SWS)
sws_freeContext(SWS);
avpicture_free(&PPFrame);
avpicture_free(&SWSFrame);
av_freep(&DecodeFrame);
}
@ -224,8 +266,10 @@ void FFMS_VideoSource::ReAdjustOutputFormat(int64_t TargetFormats, int Width, in
}
if (CodecContext->pix_fmt != OutputFormat || Width != CodecContext->width || Height != CodecContext->height) {
SWS = sws_getContext(CodecContext->width, CodecContext->height, CodecContext->pix_fmt, Width, Height,
OutputFormat, GetSWSCPUFlags() | Resizer, NULL, NULL, NULL);
int ColorSpace = CodecContext->colorspace;
if (ColorSpace == AVCOL_SPC_UNSPECIFIED) ColorSpace = -1;
SWS = GetSwsContext(CodecContext->width, CodecContext->height, CodecContext->pix_fmt, Width, Height,
OutputFormat, GetSWSCPUFlags() | Resizer, ColorSpace);
if (SWS == NULL) {
ResetOutputFormat();
throw FFMS_Exception(FFMS_ERROR_SCALING, FFMS_ERROR_INVALID_ARGUMENT,

View File

@ -25,7 +25,9 @@ extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#ifdef FFMS_USE_POSTPROC
#include <libpostproc/postprocess.h>
#endif // FFMS_USE_POSTPROC
}
// must be included after ffmpeg headers
@ -52,8 +54,10 @@ extern "C" {
class FFMS_VideoSource {
friend class FFSourceResources<FFMS_VideoSource>;
private:
#ifdef FFMS_USE_POSTPROC
pp_context_t *PPContext;
pp_mode_t *PPMode;
#endif // FFMS_USE_POSTPROC
SwsContext *SWS;
int LastFrameHeight;
int LastFrameWidth;
@ -73,7 +77,8 @@ protected:
FFMS_Track Frames;
int VideoTrack;
int CurrentFrame;
int MPEG4Counter;
int DelayCounter;
int InitialDecode;
AVCodecContext *CodecContext;
FFMS_VideoSource(const char *SourceFile, FFMS_Index *Index, int Track);
@ -112,7 +117,7 @@ class FFMatroskaVideo : public FFMS_VideoSource {
private:
MatroskaFile *MF;
MatroskaReaderContext MC;
CompressedStream *CS;
TrackCompressionContext *TCC;
char ErrorMessage[256];
FFSourceResources<FFMS_VideoSource> Res;
size_t PacketNumber;
@ -122,15 +127,14 @@ protected:
void Free(bool CloseCodec);
public:
FFMatroskaVideo(const char *SourceFile, int Track, FFMS_Index *Index, int Threads);
FFMS_Frame *GetFrame(int n);
FFMS_Frame *GetFrame(int n);
};
#ifdef HAALISOURCE
class FFHaaliVideo : public FFMS_VideoSource {
private:
FFCodecContext HCodecContext;
CComPtr<IMMContainer> pMMC;
std::vector<uint8_t> CodecPrivate;
AVBitStreamFilterContext *BitStreamFilter;
FFSourceResources<FFMS_VideoSource> Res;
@ -139,7 +143,7 @@ protected:
void Free(bool CloseCodec);
public:
FFHaaliVideo(const char *SourceFile, int Track, FFMS_Index *Index, int Threads, enum FFMS_Sources SourceMode);
FFMS_Frame *GetFrame(int n);
FFMS_Frame *GetFrame(int n);
};
#endif // HAALISOURCE

View File

@ -60,7 +60,11 @@ FFmpegSourceAudioProvider::FFmpegSourceAudioProvider(wxString filename) {
else if (res != RPC_E_CHANGED_MODE)
throw _T("FFmpegSource video provider: COM initialization failure");
#endif
#if FFMS_VERSION >= ((2 << 24) | (14 << 16) | (0 << 8) | 0)
FFMS_Init(0, 1);
#else
FFMS_Init(0);
#endif
ErrInfo.Buffer = FFMSErrMsg;
ErrInfo.BufferSize = sizeof(FFMSErrMsg);
@ -180,7 +184,11 @@ void FFmpegSourceAudioProvider::LoadAudio(wxString filename) {
// warn user?
}
#if FFMS_VERSION >= ((2 << 24) | (14 << 16) | (1 << 8) | 0)
AudioSource = FFMS_CreateAudioSource(FileNameShort.utf8_str(), TrackNumber, Index, FFMS_DELAY_FIRST_VIDEO_TRACK, &ErrInfo);
#else
AudioSource = FFMS_CreateAudioSource(FileNameShort.utf8_str(), TrackNumber, Index, &ErrInfo);
#endif
FFMS_DestroyIndex(Index);
Index = NULL;
if (!AudioSource) {

View File

@ -66,7 +66,11 @@ FFmpegSourceVideoProvider::FFmpegSourceVideoProvider(wxString filename) {
#endif
// initialize ffmpegsource
// FIXME: CPU detection?
#if FFMS_VERSION >= ((2 << 24) | (14 << 16) | (0 << 8) | 0)
FFMS_Init(0, 1);
#else
FFMS_Init(0);
#endif
// clean up variables
VideoSource = NULL;