# enable-libavresample.patch # # Adds libavresample support. Created by diffing Thomas Goyne's GIT repo # with official ffms SVN. # diff -ru ffmpegsource/configure.ac ffms2/configure.ac --- ffmpegsource/configure.ac 2013-02-27 16:53:39.230691825 +0100 +++ ffms2/configure.ac 2013-02-27 16:53:31.737713841 +0100 @@ -181,6 +181,25 @@ AC_MSG_RESULT([no]) ]) +AC_ARG_ENABLE(avresample, + AS_HELP_STRING([--enable-avresample], + [use libavresample for audio resampling])) +AS_IF([test x$enable_avresample != xno], [ + PKG_CHECK_MODULES(AVRESAMPLE, [libavresample >= 1.0.0], [enable_avresample=yes], [ + AS_IF([test x$enable_avresample = xyes], + [AC_MSG_ERROR([--enable-avresample was specified, but avresample 1.0.0+ could not be found.])]) + enable_avresample=no + ]) +]) + +AS_IF([test x$enable_avresample], + [libavresample="libavresample" + AC_DEFINE([WITH_AVRESAMPLE], [1], [Use avresample])]) + +AC_SUBST([AVRESAMPLE_CFLAGS]) +AC_SUBST([AVRESAMPLE_LIBS]) +AC_SUBST([libavresample]) + AC_MSG_CHECKING([whether -Wl,-Bsymbolic is needed]) if test "$enable_shared" = yes; then _LDFLAGS="$LDFLAGS" diff -ru ffmpegsource/ffms2.pc.in ffms2/ffms2.pc.in --- ffmpegsource/ffms2.pc.in 2013-02-27 16:53:38.924039701 +0100 +++ ffms2/ffms2.pc.in 2013-02-27 16:53:31.737713841 +0100 @@ -7,7 +7,7 @@ Name: ffms2 Description: The Fabulous FM Library 2 -Requires.private: libavformat libavcodec libswscale libavutil +Requires.private: libavformat libavcodec libswscale libavutil @libavresample@ Version: @FFMS_VERSION@ Libs.private: @ZLIB_LDFLAGS@ -lz Libs: -L${libdir} -lffms2 diff -ru ffmpegsource/include/ffmscompat.h ffms2/include/ffmscompat.h --- ffmpegsource/include/ffmscompat.h 2013-02-27 16:53:38.920706525 +0100 +++ ffms2/include/ffmscompat.h 2013-02-27 16:53:31.737713841 +0100 @@ -71,6 +71,15 @@ # define FFMS_CodecID AVCodecID # undef CodecID # endif +# if VERSION_CHECK(LIBAVCODEC_VERSION_INT, <, 54, 28, 0, 54, 59, 100) +# define avcodec_free_frame av_free +# endif +#endif + +#ifdef LIBAVUTIL_VERSION_INT +# if VERSION_CHECK(LIBAVUTIL_VERSION_INT, <, 51, 27, 0, 51, 46, 100) +# define av_get_packed_sample_fmt(fmt) (fmt < AV_SAMPLE_FMT_U8P ? fmt : fmt - (AV_SAMPLE_FMT_U8P - AV_SAMPLE_FMT_U8)) +# endif #endif #endif // FFMSCOMPAT_H diff -ru ffmpegsource/include/ffms.h ffms2/include/ffms.h --- ffmpegsource/include/ffms.h 2013-02-27 16:53:38.920706525 +0100 +++ ffms2/include/ffms.h 2013-02-27 16:53:31.737713841 +0100 @@ -113,6 +113,7 @@ FFMS_ERROR_TRACK, // track handling FFMS_ERROR_WAVE_WRITER, // WAVE64 file writer FFMS_ERROR_CANCELLED, // operation aborted + FFMS_ERROR_RESAMPLING, // audio resampling (libavresample) // Subtypes - what caused the error FFMS_ERROR_UNKNOWN = 20, // unknown error @@ -237,6 +238,53 @@ FFMS_CR_JPEG = 2 // 2^n-1, or "fullrange" } FFMS_ColorRanges; +typedef enum FFMS_MixingCoefficientType { + FFMS_MIXING_COEFFICIENT_Q8 = 0, + FFMS_MIXING_COEFFICIENT_Q15 = 1, + FFMS_MIXING_COEFFICIENT_FLT = 2 +} FFMS_MixingCoefficientType; + +typedef enum FFMS_MatrixEncoding { + FFMS_MATRIX_ENCODING_NONE = 0, + FFMS_MATRIX_ENCODING_DOBLY = 1, + FFMS_MATRIX_ENCODING_PRO_LOGIC_II = 2 +} FFMS_MatrixEncoding; + +typedef enum FFMS_ResampleFilterType { + FFMS_RESAMPLE_FILTER_CUBIC = 0, + FFMS_RESAMPLE_FILTER_SINC = 1, + FFMS_RESAMPLE_FILTER_KAISER = 2 +} FFMS_ResampleFilterType; + +typedef enum FFMS_AudioDitherMethod { + FFMS_RESAMPLE_DITHER_NONE = 0, + FFMS_RESAMPLE_DITHER_RECTANGULAR = 1, + FFMS_RESAMPLE_DITHER_TRIANGULAR = 2, + FFMS_RESAMPLE_DITHER_TRIANGULAR_HIGHPASS = 3, + FFMS_RESAMPLE_DITHER_TRIANGULAR_NOISESHAPING = 4 +} FFMS_AudioDitherMethod; + +typedef struct FFMS_ResampleOptions { + int64_t ChannelLayout; + FFMS_SampleFormat SampleFormat; + int SampleRate; + FFMS_MixingCoefficientType MixingCoefficientType; + double CenterMixLevel; + double SurroundMixLevel; + double LFEMixLevel; + int Normalize; + int ForceResample; + int ResampleFilterSize; + int ResamplePhaseShift; + int LinearInterpolation; + double CutoffFrequencyRatio; + FFMS_MatrixEncoding MatrixedStereoEncoding; + FFMS_ResampleFilterType FilterType; + int KaiserBeta; + FFMS_AudioDitherMethod DitherMethod; +} FFMS_ResampleOptions; + + typedef struct FFMS_Frame { uint8_t *Data[4]; int Linesize[4]; @@ -319,6 +367,9 @@ FFMS_API(void) FFMS_ResetOutputFormatV(FFMS_VideoSource *V); FFMS_API(int) FFMS_SetInputFormatV(FFMS_VideoSource *V, int ColorSpace, int ColorRange, int Format, FFMS_ErrorInfo *ErrorInfo); /* Introduced in FFMS_VERSION ((2 << 24) | (17 << 16) | (1 << 8) | 0) */ FFMS_API(void) FFMS_ResetInputFormatV(FFMS_VideoSource *V); +FFMS_API(FFMS_ResampleOptions *) FFMS_CreateResampleOptions(FFMS_AudioSource *A); /* Introduced in FFMS_VERSION ((2 << 24) | (15 << 16) | (4 << 8) | 0) */ +FFMS_API(int) FFMS_SetOutputFormatA(FFMS_AudioSource *A, const FFMS_ResampleOptions*options, FFMS_ErrorInfo *ErrorInfo); /* Introduced in FFMS_VERSION ((2 << 24) | (15 << 16) | (4 << 8) | 0) */ +FFMS_API(void) FFMS_DestroyResampleOptions(FFMS_ResampleOptions *options); /* Introduced in FFMS_VERSION ((2 << 24) | (15 << 16) | (4 << 8) | 0) */ FFMS_API(void) FFMS_DestroyIndex(FFMS_Index *Index); FFMS_API(int) FFMS_GetSourceType(FFMS_Index *Index); FFMS_API(int) FFMS_GetSourceTypeI(FFMS_Indexer *Indexer); diff -ru ffmpegsource/Makefile.am ffms2/Makefile.am --- ffmpegsource/Makefile.am 2013-02-27 16:53:39.310688030 +0100 +++ ffms2/Makefile.am 2013-02-27 16:53:31.724381141 +0100 @@ -9,7 +9,7 @@ INCLUDES = -I. -I$(top_srcdir)/include -I$(top_srcdir)/src/config @LIBAV_CFLAGS@ @ZLIB_CPPFLAGS@ -include config.h lib_LTLIBRARIES = src/core/libffms2.la -src_core_libffms2_la_LIBADD = @LIBAV_LIBS@ @ZLIB_LDFLAGS@ -lz @LTUNDEF@ +src_core_libffms2_la_LIBADD = @LIBAV_LIBS@ @AVRESAMPLE_LIBS@ @ZLIB_LDFLAGS@ -lz @LTUNDEF@ src_core_libffms2_la_SOURCES = \ src/core/audiosource.h \ src/core/audiosource.cpp \ diff -ru ffmpegsource/src/config/config.h.in ffms2/src/config/config.h.in --- ffmpegsource/src/config/config.h.in 2013-02-27 16:53:39.017368608 +0100 +++ ffms2/src/config/config.h.in 2013-02-27 16:53:31.744380192 +0100 @@ -90,5 +90,8 @@ /* Version number of package */ #undef VERSION +/* Use avresample */ +#undef WITH_AVRESAMPLE + /* Define to `unsigned int' if does not define. */ #undef size_t diff -ru ffmpegsource/src/config/libs.cpp ffms2/src/config/libs.cpp --- ffmpegsource/src/config/libs.cpp 2013-02-27 16:53:39.017368608 +0100 +++ ffms2/src/config/libs.cpp 2013-02-27 16:53:31.744380192 +0100 @@ -45,6 +45,9 @@ #pragma comment(lib, "libavcodec.a") #pragma comment(lib, "libavformat.a") #pragma comment(lib, "libswscale.a") +#ifdef WITH_AVRESAMPLE +#pragma comment(lib, "libavresample.a") +#endif #ifdef WITH_OPENCORE_AMR_NB #ifdef WITH_GCC_LIBAV diff -ru ffmpegsource/src/core/audiosource.cpp ffms2/src/core/audiosource.cpp --- ffmpegsource/src/core/audiosource.cpp 2013-02-27 16:53:39.137362917 +0100 +++ ffms2/src/core/audiosource.cpp 2013-02-27 16:53:31.744380192 +0100 @@ -23,17 +23,45 @@ #include #include +namespace { + + int64_t ChannelLayout; + FFMS_SampleFormat SampleFormat; + int SampleRate; +#define MAPPER(m, n) OptionMapper(n, &FFMS_ResampleOptions::m) +OptionMapper resample_options[] = { + MAPPER(ChannelLayout, "out_channel_layout"), + MAPPER(SampleFormat, "out_sample_fmt"), + MAPPER(SampleRate, "out_sample_rate"), + MAPPER(MixingCoefficientType, "mix_coeff_type"), + MAPPER(CenterMixLevel, "center_mix_level"), + MAPPER(SurroundMixLevel, "surround_mix_level"), + MAPPER(LFEMixLevel, "lfe_mix_level"), + MAPPER(Normalize, "normalize_mix_level"), + MAPPER(ForceResample, "force_resampling"), + MAPPER(ResampleFilterSize, "filter_size"), + MAPPER(ResamplePhaseShift, "phase_shift"), + MAPPER(LinearInterpolation, "linear_interp"), + MAPPER(CutoffFrequencyRatio, "cutoff"), + MAPPER(MatrixedStereoEncoding, "matrix_encoding"), + MAPPER(FilterType, "filter_type"), + MAPPER(KaiserBeta, "kaiser_beta"), + MAPPER(DitherMethod, "dither_method") +}; +#undef MAPPER + +} + FFMS_AudioSource::FFMS_AudioSource(const char *SourceFile, FFMS_Index &Index, int Track) : Delay(0) , MaxCacheBlocks(50) , BytesPerSample(0) -, Decoded(0) +, NeedsResample(false) , CurrentSample(-1) , PacketNumber(0) , CurrentFrame(NULL) , TrackNumber(Track) , SeekOffset(0) -, DecodingBuffer(AVCODEC_MAX_AUDIO_FRAME_SIZE * 10) , Index(Index) { if (Track < 0 || Track >= static_cast(Index.size())) @@ -57,44 +85,14 @@ Index.AddRef(); } - #define EXCESSIVE_CACHE_SIZE 400 void FFMS_AudioSource::Init(const 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 - - // 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)) { - - // Vorbis in particular seems to like having 60+ packets at the start of the file with a PTS of 0, - // so we might need to expand the search range to account for that. - if (Cache.size() >= MaxCacheBlocks - 1) { - if (MaxCacheBlocks >= EXCESSIVE_CACHE_SIZE) - throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_ALLOCATION_FAILED, "Exceeded the search range for an initial valid audio PTS"); - MaxCacheBlocks *= 2; - } - + // Decode the first packet to ensure all properties are initialized + // Don't cache it since it might be in the wrong format + // Instead, leave it in DecodeFrame and it'll get cached later + while (DecodeFrame->nb_samples == 0) 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 @@ -104,6 +102,11 @@ throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_CODEC, "Codec returned zero size audio"); + if (av_sample_fmt_is_planar(CodecContext->sample_fmt)) { + std::auto_ptr opt(CreateResampleOptions()); + SetOutputFormat(opt.get()); + } + if (DelayMode < FFMS_DELAY_NO_SHIFT) throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT, "Bad audio delay compensation mode"); @@ -146,8 +149,133 @@ AP.NumSamples += Delay; } -void FFMS_AudioSource::CacheBlock(CacheIterator &pos, int64_t Start, size_t Samples, uint8_t *SrcData) { - Cache.insert(pos, AudioBlock(Start, Samples, SrcData, Samples * BytesPerSample)); +void FFMS_AudioSource::CacheBeginning() { + // Nothing to do if the cache is already populated + if (!Cache.empty()) return; + + // The first frame is already decoded, so add it to the cache + CacheBlock(Cache.end()); + + // 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 + + // 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)) { + + // Vorbis in particular seems to like having 60+ packets at the start + // of the file with a PTS of 0, so we might need to expand the search + // range to account for that. + // Expanding slightly before it's strictly needed to ensure there's a + // bit of space for an actual cache + if (Cache.size() >= MaxCacheBlocks - 5) { + if (MaxCacheBlocks >= EXCESSIVE_CACHE_SIZE) + throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_ALLOCATION_FAILED, + "Exceeded the search range for an initial valid audio PTS"); + MaxCacheBlocks *= 2; + } + + DecodeNextBlock(&end); + } + // 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; +} + +void FFMS_AudioSource::SetOutputFormat(const FFMS_ResampleOptions *opt) { + if (!Cache.empty()) + throw FFMS_Exception(FFMS_ERROR_RESAMPLING, FFMS_ERROR_USER, + "Cannot change the output format after audio decoding has begun"); + + BytesPerSample = av_get_bytes_per_sample(static_cast(opt->SampleFormat)) * av_get_channel_layout_nb_channels(opt->ChannelLayout); + + NeedsResample = + opt->SampleFormat != (int)CodecContext->sample_fmt || + opt->SampleRate != AP.SampleRate || + opt->ChannelLayout != AP.ChannelLayout || + opt->ForceResample; + if (!NeedsResample) return; + + if (opt->SampleRate != AP.SampleRate) + throw FFMS_Exception(FFMS_ERROR_RESAMPLING, FFMS_ERROR_UNSUPPORTED, + "Sample rate changes are currently unsupported."); + +#ifdef WITH_AVRESAMPLE + if (opt->SampleRate != AP.SampleRate) + throw FFMS_Exception(FFMS_ERROR_RESAMPLING, FFMS_ERROR_UNSUPPORTED, + "Changing the audio sample rate is currently not supported"); + + std::auto_ptr oldOptions(ReadOptions(ResampleContext, resample_options)); + SetOptions(opt, ResampleContext, resample_options); + av_opt_set_int(ResampleContext, "in_sample_rate", AP.SampleRate, 0); + av_opt_set_int(ResampleContext, "in_sample_fmt", CodecContext->sample_fmt, 0); + av_opt_set_int(ResampleContext, "in_channel_layout", AP.ChannelLayout, 0); + + if (avresample_open(ResampleContext)) { + SetOptions(oldOptions.get(), ResampleContext, resample_options); + avresample_open(ResampleContext); + throw FFMS_Exception(FFMS_ERROR_RESAMPLING, FFMS_ERROR_UNKNOWN, + "Could not open avresample context"); + } +#else + if (opt->SampleFormat != AP.SampleFormat || opt->SampleRate != AP.SampleRate || opt->ChannelLayout != AP.ChannelLayout) + throw FFMS_Exception(FFMS_ERROR_RESAMPLING, FFMS_ERROR_UNSUPPORTED, + "FFMS was not built with resampling enabled. The only supported conversion is interleaving planar audio."); +#endif +} + +FFMS_ResampleOptions *FFMS_AudioSource::CreateResampleOptions() const { +#ifdef WITH_AVRESAMPLE + FFMS_ResampleOptions *ret = ReadOptions(ResampleContext, resample_options); +#else + FFMS_ResampleOptions *ret = new FFMS_ResampleOptions; + memset(ret, 0, sizeof(FFMS_ResampleOptions)); +#endif + ret->SampleRate = AP.SampleRate; + ret->SampleFormat = static_cast(AP.SampleFormat); + ret->ChannelLayout = AP.ChannelLayout; + return ret; +} + +void FFMS_AudioSource::ResampleAndCache(CacheIterator pos) { + AudioBlock& block = *Cache.insert(pos, AudioBlock(CurrentSample, DecodeFrame->nb_samples)); + block.Data.reserve(DecodeFrame->nb_samples * BytesPerSample); + +#ifdef WITH_AVRESAMPLE + block.Data.resize(block.Data.capacity()); + + uint8_t *OutPlanes[1] = { static_cast(&block.Data[0]) }; + avresample_convert(ResampleContext, + OutPlanes, block.Data.size(), DecodeFrame->nb_samples, + DecodeFrame->extended_data, DecodeFrame->nb_samples * av_get_bytes_per_sample(CodecContext->sample_fmt), DecodeFrame->nb_samples); +#else + int width = av_get_bytes_per_sample(CodecContext->sample_fmt); + uint8_t **Data = DecodeFrame->extended_data; + + for (int s = 0; s < DecodeFrame->nb_samples; ++s) { + for (int c = 0; c < CodecContext->channels; ++c) + block.Data.insert(block.Data.end(), &Data[c][s * width], &Data[c][(s + 1) * width]); + } +#endif +} + +void FFMS_AudioSource::CacheBlock(CacheIterator pos) { + if (NeedsResample) + ResampleAndCache(pos); + else + Cache.insert(pos, AudioBlock(CurrentSample, DecodeFrame->nb_samples, DecodeFrame->extended_data[0], DecodeFrame->nb_samples * BytesPerSample)); if (Cache.size() >= MaxCacheBlocks) { // Kill the oldest one @@ -162,45 +290,45 @@ } } -void FFMS_AudioSource::DecodeNextBlock() { - if (BytesPerSample == 0) BytesPerSample = av_get_bytes_per_sample(CodecContext->sample_fmt) * CodecContext->channels; - +void FFMS_AudioSource::DecodeNextBlock(CacheIterator *pos) { CurrentFrame = &Frames[PacketNumber]; AVPacket Packet; if (!ReadPacket(&Packet)) - throw FFMS_Exception(FFMS_ERROR_PARSER, FFMS_ERROR_UNKNOWN, "ReadPacket unexpectedly failed to read a 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]; + bool GotSamples = false; 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); + DecodeFrame.reset(); + int GotFrame = 0; + int Ret = avcodec_decode_audio4(CodecContext, DecodeFrame, &GotFrame, &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) { + if (Ret > 0 && GotFrame) { Packet.size -= Ret; Packet.data += Ret; - Buf += TempOutputBufSize; + if (DecodeFrame->nb_samples > 0) { + GotSamples = true; + if (pos) + CacheBlock(*pos); + } } } 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; - } + // Zero sample packets aren't included in the index + if (GotSamples) + ++PacketNumber; } static bool SampleStartComp(const TFrameInfo &a, const TFrameInfo &b) { @@ -216,6 +344,8 @@ throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_INVALID_ARGUMENT, "Out of bounds audio samples requested"); + CacheBeginning(); + uint8_t *Dst = static_cast(Buf); // Apply audio delay (if any) and fill any samples before the start time with zero @@ -253,10 +383,12 @@ } // Decode another block else { + CacheIterator cachePos = it; --cachePos; + 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)) { + if (SeekOffset >= 0 && (Start < CurrentSample || Start > CurrentSample + DecodeFrame->nb_samples * 5)) { TFrameInfo f; f.SampleStart = Start; int NewPacketNumber = std::distance(Frames.begin(), std::lower_bound(Frames.begin(), Frames.end(), f, SampleStartComp)); @@ -266,32 +398,22 @@ // Only seek forward if it'll actually result in moving forward if (Start < CurrentSample || static_cast(NewPacketNumber) > PacketNumber) { PacketNumber = NewPacketNumber; - Decoded = 0; CurrentSample = -1; + DecodeFrame.reset(); avcodec_flush_buffers(CodecContext); Seek(); } } - // Decode everything between the last keyframe and the block we want + // Decode until we hit the block we want if (PacketNumber >= Frames.size()) throw FFMS_Exception(FFMS_ERROR_SEEKING, FFMS_ERROR_CODEC, "Seeking is severely broken"); - while (CurrentSample + Decoded <= Start && PacketNumber < Frames.size()) - DecodeNextBlock(); + while (CurrentSample + DecodeFrame->nb_samples <= Start && PacketNumber < Frames.size()) + DecodeNextBlock(&it); 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(Start - CurrentSample); - size_t Samples = static_cast(Decoded - FirstSample); - size_t Bytes = FFMIN(Samples, static_cast(Count)) * BytesPerSample; - - memcpy(Dst, &DecodingBuffer[FirstSample * BytesPerSample], Bytes); - - Start += Samples; - Count -= Samples; - Dst += Bytes; + it = cachePos; } } } diff -ru ffmpegsource/src/core/audiosource.h ffms2/src/core/audiosource.h --- ffmpegsource/src/core/audiosource.h 2013-02-27 16:53:39.130696566 +0100 +++ ffms2/src/core/audiosource.h 2013-02-27 16:53:31.744380192 +0100 @@ -46,7 +46,6 @@ #endif struct FFMS_AudioSource { -private: struct AudioBlock { int64_t Age; int64_t Start; @@ -54,9 +53,17 @@ std::vector Data; AudioBlock(int64_t Start, int64_t Samples, uint8_t *SrcData, size_t SrcBytes) - : Start(Start) - , Samples(Samples) - , Data(SrcData, SrcData + SrcBytes) + : Start(Start) + , Samples(Samples) + , Data(SrcData, SrcData + SrcBytes) + { + static int64_t Now = 0; + Age = Now++; + } + + AudioBlock(int64_t Start, int64_t Samples) + : Start(Start) + , Samples(Samples) { static int64_t Now = 0; Age = Now++; @@ -74,11 +81,18 @@ 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); + bool NeedsResample; + FFResampleContext ResampleContext; + + // Insert the current audio frame into the cache + void CacheBlock(CacheIterator pos); + + // Interleave the current audio frame and insert it into the cache + void ResampleAndCache(CacheIterator pos); + + // Cache the unseekable beginning of the file once the output format is set + void CacheBeginning(); // Called after seeking virtual void Seek() { }; @@ -99,13 +113,13 @@ int SeekOffset; // Buffer which audio is decoded into - AlignedBuffer DecodingBuffer; + ScopedFrame DecodeFrame; FFMS_Index &Index; FFMS_Track Frames; FFCodecContext CodecContext; FFMS_AudioProperties AP; - void DecodeNextBlock(); + void DecodeNextBlock(CacheIterator *cachePos = 0); // Initialization which has to be done after the codec is opened void Init(const FFMS_Index &Index, int DelayMode); @@ -116,6 +130,9 @@ FFMS_Track *GetTrack() { return &Frames; } const FFMS_AudioProperties& GetAudioProperties() const { return AP; } void GetAudio(void *Buf, int64_t Start, int64_t Count); + + FFMS_ResampleOptions *CreateResampleOptions() const; + void SetOutputFormat(const FFMS_ResampleOptions *opt); }; class FFLAVFAudio : public FFMS_AudioSource { diff -ru ffmpegsource/src/core/ffms.cpp ffms2/src/core/ffms.cpp --- ffmpegsource/src/core/ffms.cpp 2013-02-27 16:53:39.137362917 +0100 +++ ffms2/src/core/ffms.cpp 2013-02-27 16:53:31.744380192 +0100 @@ -256,6 +256,24 @@ V->ResetInputFormat(); } +FFMS_API(FFMS_ResampleOptions *) FFMS_CreateResampleOptions(FFMS_AudioSource *A) { + return A->CreateResampleOptions(); +} + +FFMS_API(void) FFMS_DestroyResampleOptions(FFMS_ResampleOptions *options) { + delete options; +} + +FFMS_API(int) FFMS_SetOutputFormatA(FFMS_AudioSource *A, const FFMS_ResampleOptions *options, FFMS_ErrorInfo *ErrorInfo) { + ClearErrorInfo(ErrorInfo); + try { + A->SetOutputFormat(options); + } catch (FFMS_Exception &e) { + return e.CopyOut(ErrorInfo); + } + return FFMS_ERROR_SUCCESS; +} + FFMS_API(void) FFMS_DestroyIndex(FFMS_Index *Index) { assert(Index != NULL); if (Index == NULL) diff -ru ffmpegsource/src/core/indexing.cpp ffms2/src/core/indexing.cpp --- ffmpegsource/src/core/indexing.cpp 2013-02-27 16:53:39.134029741 +0100 +++ ffms2/src/core/indexing.cpp 2013-02-27 16:53:31.744380192 +0100 @@ -693,7 +693,6 @@ , ANC(0) , ANCPrivate(0) , SourceFile(Filename) -, DecodingBuffer(AVCODEC_MAX_AUDIO_FRAME_SIZE * 10) { FFMS_Index::CalculateFileSignature(Filename, &Filesize, Digest); } @@ -702,9 +701,9 @@ } -void FFMS_Indexer::WriteAudio(SharedAudioContext &AudioContext, FFMS_Index *Index, int Track, int DBSize) { +void FFMS_Indexer::WriteAudio(SharedAudioContext &AudioContext, FFMS_Index *Index, int Track) { // 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) return; + if (DecodeFrame->nb_samples) return; if (!AudioContext.W64Writer) { FFMS_AudioProperties AP; @@ -715,6 +714,8 @@ return; } + int Format = av_get_packed_sample_fmt(AudioContext.CodecContext->sample_fmt); + std::vector WName(FNSize); (*ANC)(SourceFile.c_str(), Track, &AP, &WName[0], FNSize, ANCPrivate); std::string WN(&WName[0]); @@ -724,14 +725,14 @@ av_get_bytes_per_sample(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)); + (Format == AV_SAMPLE_FMT_FLT) || (Format == 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); + AudioContext.W64Writer->WriteData(*DecodeFrame); } int64_t FFMS_Indexer::IndexAudioPacket(int Track, AVPacket *Packet, SharedAudioContext &Context, FFMS_Index &TrackIndices) { @@ -739,8 +740,10 @@ 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); + DecodeFrame.reset(); + + int GotFrame = 0; + int Ret = avcodec_decode_audio4(CodecContext, DecodeFrame, &GotFrame, Packet); if (Ret < 0) { if (ErrorHandling == FFMS_IEH_ABORT) { throw FFMS_Exception(FFMS_ERROR_CODEC, FFMS_ERROR_DECODING, "Audio decoding error"); @@ -756,13 +759,14 @@ Packet->data += Ret; Read += Ret; - CheckAudioProperties(Track, CodecContext); + if (GotFrame) { + CheckAudioProperties(Track, CodecContext); - if (dbsize > 0) - Context.CurrentSample += dbsize / (av_get_bytes_per_sample(CodecContext->sample_fmt) * CodecContext->channels); + Context.CurrentSample += DecodeFrame->nb_samples; - if (DumpMask & (1 << Track)) - WriteAudio(Context, &TrackIndices, Track, dbsize); + if (DumpMask & (1 << Track)) + WriteAudio(Context, &TrackIndices, Track); + } } Packet->size += Read; Packet->data -= Read; diff -ru ffmpegsource/src/core/indexing.h ffms2/src/core/indexing.h --- ffmpegsource/src/core/indexing.h 2013-02-27 16:53:39.127363391 +0100 +++ ffms2/src/core/indexing.h 2013-02-27 16:53:31.744380192 +0100 @@ -155,7 +155,6 @@ }; struct FFMS_Indexer { -private: std::map LastAudioProperties; protected: int IndexMask; @@ -166,12 +165,12 @@ TAudioNameCallback ANC; void *ANCPrivate; std::string SourceFile; - AlignedBuffer DecodingBuffer; + ScopedFrame DecodeFrame; int64_t Filesize; uint8_t Digest[20]; - void WriteAudio(SharedAudioContext &AudioContext, FFMS_Index *Index, int Track, int DBSize); + void WriteAudio(SharedAudioContext &AudioContext, FFMS_Index *Index, int Track); void CheckAudioProperties(int Track, AVCodecContext *Context); int64_t IndexAudioPacket(int Track, AVPacket *Packet, SharedAudioContext &Context, FFMS_Index &TrackIndices); void ParseVideoPacket(SharedVideoContext &VideoContext, AVPacket &pkt, int *RepeatPict, int *FrameType, bool *Invisible); diff -ru ffmpegsource/src/core/utils.cpp ffms2/src/core/utils.cpp --- ffmpegsource/src/core/utils.cpp 2013-02-27 16:53:39.134029741 +0100 +++ ffms2/src/core/utils.cpp 2013-02-27 16:53:31.744380192 +0100 @@ -214,10 +214,32 @@ pkt.size = 0; } +extern "C" { +#if VERSION_CHECK(LIBAVUTIL_VERSION_INT, >=, 52, 2, 0, 52, 6, 100) +#include +#elif VERSION_CHECK(LIBAVUTIL_VERSION_INT, >=, 51, 26, 0, 51, 45, 100) +#include +#else +static int64_t av_get_default_channel_layout(int nb_channels) { + switch(nb_channels) { + case 1: return AV_CH_LAYOUT_MONO; + case 2: return AV_CH_LAYOUT_STEREO; + case 3: return AV_CH_LAYOUT_SURROUND; + case 4: return AV_CH_LAYOUT_QUAD; + case 5: return AV_CH_LAYOUT_5POINT0; + case 6: return AV_CH_LAYOUT_5POINT1; + case 7: return AV_CH_LAYOUT_6POINT1; + case 8: return AV_CH_LAYOUT_7POINT1; + default: return 0; + } +} +#endif +} + void FillAP(FFMS_AudioProperties &AP, AVCodecContext *CTX, FFMS_Track &Frames) { - AP.SampleFormat = static_cast(CTX->sample_fmt); + AP.SampleFormat = static_cast(av_get_packed_sample_fmt(CTX->sample_fmt)); AP.BitsPerSample = av_get_bytes_per_sample(CTX->sample_fmt) * 8; - AP.Channels = CTX->channels;; + AP.Channels = CTX->channels; AP.ChannelLayout = CTX->channel_layout; AP.SampleRate = CTX->sample_rate; if (!Frames.empty()) { @@ -225,6 +247,9 @@ 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 (AP.ChannelLayout == 0) + AP.ChannelLayout = av_get_default_channel_layout(AP.Channels); } #ifdef HAALISOURCE diff -ru ffmpegsource/src/core/utils.h ffms2/src/core/utils.h --- ffmpegsource/src/core/utils.h 2013-02-27 16:53:39.127363391 +0100 +++ ffms2/src/core/utils.h 2013-02-27 16:53:31.744380192 +0100 @@ -31,9 +31,13 @@ extern "C" { #include "stdiostream.h" #include +#include #include #include #include +#ifdef WITH_AVRESAMPLE +#include +#endif } // must be included after ffmpeg headers @@ -133,6 +137,34 @@ } }; +template +class unknown_size { + T *ptr; + + unknown_size(unknown_size const&); + unknown_size& operator=(unknown_size const&); +public: + operator T*() const { return ptr; } + operator void*() const { return ptr; } + T *operator->() const { return ptr; } + + unknown_size() : ptr(Alloc()) { } + ~unknown_size() { Del(&ptr); } +}; + +class ScopedFrame : public unknown_size { +public: + void reset() { + avcodec_get_frame_defaults(*this); + } +}; + +#ifdef WITH_AVRESAMPLE +typedef unknown_size FFResampleContext; +#else +typedef struct {} FFResampleContext; +#endif + inline void DeleteHaaliCodecContext(AVCodecContext *CodecContext) { av_freep(&CodecContext->extradata); av_freep(&CodecContext); @@ -228,4 +240,68 @@ void FlushBuffers(AVCodecContext *CodecContext); +namespace optdetail { + template + T get_av_opt(void *v, const char *name) { + return static_cast(av_get_int(v, name, 0)); + } + + template<> + inline double get_av_opt(void *v, const char *name) { + return av_get_double(v, name, 0); + } + + template + void set_av_opt(void *v, const char *name, T value) { + av_opt_set_int(v, name, value, 0); + } + + template<> + inline void set_av_opt(void *v, const char *name, double value) { + av_opt_set_double(v, name, value, 0); + } +} + +template +class OptionMapper { + struct OptionMapperBase { + virtual void ToOpt(const FFMS_Struct *src, void *dst) const=0; + virtual void FromOpt(FFMS_Struct *dst, void *src) const=0; + }; + + template + class OptionMapperImpl : public OptionMapperBase { + T (FFMS_Struct::*ptr); + const char *name; + + public: + OptionMapperImpl(T (FFMS_Struct::*ptr), const char *name) : ptr(ptr), name(name) { } + void ToOpt(const FFMS_Struct *src, void *dst) const { optdetail::set_av_opt(dst, name, src->*ptr); } + void FromOpt(FFMS_Struct *dst, void *src) const { dst->*ptr = optdetail::get_av_opt(src, name); } + }; + + OptionMapperBase *impl; + +public: + template + OptionMapper(const char *opt_name, T (FFMS_Struct::*member)) : impl(new OptionMapperImpl(member, opt_name)) { } + + void ToOpt(const FFMS_Struct *src, void *dst) const { impl->ToOpt(src, dst); } + void FromOpt(FFMS_Struct *dst, void *src) const { impl->FromOpt(dst, src); } +}; + +template +T *ReadOptions(void *opt, OptionMapper (&options)[N]) { + T *ret = new T; + for (int i = 0; i < N; ++i) + options[i].FromOpt(ret, opt); + return ret; +} + +template +void SetOptions(const T* src, void *opt, OptionMapper (&options)[N]) { + for (int i = 0; i < N; ++i) + options[i].ToOpt(src, opt); +} + #endif diff -ru ffmpegsource/src/core/wave64writer.cpp ffms2/src/core/wave64writer.cpp --- ffmpegsource/src/core/wave64writer.cpp 2013-02-27 16:53:39.134029741 +0100 +++ ffms2/src/core/wave64writer.cpp 2013-02-27 16:53:31.744380192 +0100 @@ -106,7 +106,16 @@ WavFile.seekp(CPos, std::ios::beg); } -void Wave64Writer::WriteData(void *Data, std::streamsize Length) { - WavFile.write(reinterpret_cast(Data), Length); +void Wave64Writer::WriteData(AVFrame const& Frame) { + uint64_t Length = Frame.nb_samples * BytesPerSample * Channels; + if (Channels > 1 && av_sample_fmt_is_planar(static_cast(Frame.format))) { + for (int32_t sample = 0; sample < Frame.nb_samples; ++sample) { + for (int32_t channel = 0; channel < Channels; ++channel) + WavFile.write(reinterpret_cast(&Frame.extended_data[channel][sample * BytesPerSample]), BytesPerSample); + } + } + else { + WavFile.write(reinterpret_cast(Frame.extended_data[0]), Length); + } BytesWritten += Length; } diff -ru /tmp/ffmpegsource/src/ffmpegsource/src/core/wave64writer.h ffms2/src/core/wave64writer.h --- /tmp/ffmpegsource/src/ffmpegsource/src/core/wave64writer.h 2013-02-27 16:53:39.127363391 +0100 +++ ffms2/src/core/wave64writer.h 2013-02-27 16:53:31.744380192 +0100 @@ -28,8 +28,8 @@ class Wave64Writer { public: Wave64Writer(const char *Filename, uint16_t BitsPerSample, uint16_t Channels, uint32_t SamplesPerSec, bool IsFloat); ~Wave64Writer(); - void WriteData(void *Data, std::streamsize Length); + void WriteData(AVFrame const& Frame); private: ffms_fstream WavFile; int32_t BytesPerSample;