diff --git a/configure.ac b/configure.ac index 0f00b6c..aae1563 100644 --- a/configure.ac +++ b/configure.ac @@ -784,6 +784,15 @@ if test x"$build_media_ffmpeg" != x"no"; then # yes or auto fi fi +dnl This method of checking is most likely not good enough. +OLDLIBS="$LIBS" +LIBS="$FFMPEG_LIBS" +AC_CHECK_FUNCS(avcodec_decode_audio2) +AC_CHECK_FUNCS(avcodec_decode_audio3) +AC_CHECK_FUNCS(av_parser_parse) +AC_CHECK_FUNCS(av_parser_parse2) +LIBS="$OLDLIBS" + MEDIA_CONFIG="${media_list}" AC_SUBST(MEDIA_CONFIG) @@ -1117,6 +1126,12 @@ AM_CONDITIONAL(HAVE_PERL, test x"$PERL" != x) AC_PATH_PROG(CSOUND, csound) AM_CONDITIONAL(HAVE_CSOUND, test x"$CSOUND" != x) +AC_PATH_PROG(FFMPEG, ffmpeg) +AM_CONDITIONAL(HAVE_FFMPEG, test x"$FFMPEG" != x) + +AC_PATH_PROG(LAME, lame) +AM_CONDITIONAL(HAVE_LAME, test x"$LAME" != x) + AC_PATH_PROG(GIT, git) AC_SUBST(GIT) @@ -3567,7 +3582,7 @@ fi if test x"$PERL" != x; then echo " PERL is $PERL" else - PKG_WAR([You need to have perl installed to run some of the tests in Gnash testsuite.]) + PKG_WAR([You need to have perl installed to run some of the tests in the Gnash testsuite.]) PKG_SUGGEST([Install it from http://perl.org]) DEB_INSTALL([perl]) RPM_INSTALL([perl]) @@ -3576,6 +3591,29 @@ fi if test x"$testsuite" = x"yes"; then if test x"$CSOUND" != x; then echo " CSOUND is $CSOUND" + else + PKG_WAR([You need to have csound installed to run some of the tests in the Gnash testsuite.]) + PKG_SUGGEST([Install it from http://csounds.com]) + DEB_INSTALL([csound]) + RPM_INSTALL([csound]) + fi + + if test x"$FFMPEG" != x; then + echo " FFMPEG is $FFMPEG" + else + PKG_WAR([You need to have ffmpeg installed to run some of the tests in the Gnash testsuite.]) + PKG_SUGGEST([Install it from http://ffmpeg.org]) + DEB_INSTALL([ffmpeg]) + RPM_INSTALL([ffmpeg]) + fi + + if test x"$LAME" != x; then + echo " LAME is $LAME" + else + PKG_WAR([You need to have lame installed to run some of the tests in Gnash the testsuite.]) + PKG_SUGGEST([Install it from http://lame.sourceforge.net/]) + DEB_INSTALL([lame]) + RPM_INSTALL([lame]) fi fi diff --git a/libcore/asobj/NetStream_as.cpp b/libcore/asobj/NetStream_as.cpp index 3c453b5..8eb5244 100644 --- a/libcore/asobj/NetStream_as.cpp +++ b/libcore/asobj/NetStream_as.cpp @@ -662,10 +662,18 @@ NetStream_as::decodeNextAudioFrame() return 0; } +#ifdef GNASH_DEBUG_DECODING + int framesize = frame->dataSize; +#endif + boost::uint32_t datasize; + boost::uint8_t* data = _audioDecoder->decode(frame, datasize); + if ( data == 0 ) { return 0; } + // TODO: make the buffer cursored later ? BufferedAudioStreamer::CursoredBuffer* raw = new BufferedAudioStreamer::CursoredBuffer(); - raw->m_data = _audioDecoder->decode(*frame, raw->m_size); + raw->m_data = data; + raw->m_size = datasize; // TODO: let the sound_handler do this .. sounds cleaner if (_audioController) { @@ -687,7 +695,7 @@ NetStream_as::decodeNextAudioFrame() log_debug("NetStream_as::decodeNextAudioFrame: " "%d bytes of encoded audio " "decoded to %d bytes", - frame->dataSize, + framesize, raw->m_size); #endif diff --git a/libcore/asobj/Sound_as.cpp b/libcore/asobj/Sound_as.cpp index 5ec4559..c3e150e 100644 --- a/libcore/asobj/Sound_as.cpp +++ b/libcore/asobj/Sound_as.cpp @@ -849,10 +849,11 @@ Sound_as::getAudio(boost::int16_t* samples, unsigned int nSamples, bool& atEOF) continue; } - _leftOverData.reset( _audioDecoder->decode(*frame, _leftOverSize) ); + _leftOverData.reset( _audioDecoder->decode(frame, _leftOverSize) ); _leftOverPtr = _leftOverData.get(); if ( ! _leftOverData ) { - log_error("No samples decoded from input of %d bytes", frame->dataSize); + log_error("Error during audio decoding"); + atEOF=true; continue; } diff --git a/libmedia/AudioDecoder.h b/libmedia/AudioDecoder.h index 8c34ff8..a90bc76 100644 --- a/libmedia/AudioDecoder.h +++ b/libmedia/AudioDecoder.h @@ -19,6 +19,7 @@ #ifndef GNASH_AUDIODECODER_H #define GNASH_AUDIODECODER_H +#include #include // for C99 int types // Forward declarations @@ -75,10 +76,12 @@ public: /// /// @return a pointer to the decoded data, or NULL if decoding fails. /// The caller owns the decoded data, which was allocated with new []. + /// If no data is available yet, the function returns a buffer of size + /// of size 0 allocated with new []. /// /// @todo return a SimpleBuffer by auto_ptr /// - virtual boost::uint8_t* decode(const EncodedAudioFrame& input, + virtual boost::uint8_t* decode(std::auto_ptr input, boost::uint32_t& outputSize); }; @@ -91,7 +94,7 @@ AudioDecoder::decode(const boost::uint8_t*, boost::uint32_t, boost::uint32_t&, } inline boost::uint8_t* -AudioDecoder::decode(const EncodedAudioFrame&, boost::uint32_t&) +AudioDecoder::decode(std::auto_ptr, boost::uint32_t&) { return 0; } diff --git a/libmedia/MediaParser.h b/libmedia/MediaParser.h index 5a32431..69a7adb 100644 --- a/libmedia/MediaParser.h +++ b/libmedia/MediaParser.h @@ -419,9 +419,12 @@ public: boost::uint32_t dataSize; boost::scoped_array data; boost::uint64_t timestamp; + int dataAlreadyUsed; // FIXME: should have better encapsulation for this sort of stuff. std::auto_ptr extradata; + + EncodedAudioFrame() : dataAlreadyUsed(0) { } }; /// The MediaParser class provides cursor-based access to encoded %media frames diff --git a/libmedia/ffmpeg/AudioDecoderFfmpeg.cpp b/libmedia/ffmpeg/AudioDecoderFfmpeg.cpp index 2b1ce0e..1dc8dce 100644 --- a/libmedia/ffmpeg/AudioDecoderFfmpeg.cpp +++ b/libmedia/ffmpeg/AudioDecoderFfmpeg.cpp @@ -19,6 +19,7 @@ #include "AudioDecoderFfmpeg.h" +#include // for memcpy and memset #include // for std::ceil #include // for std::copy, std::max @@ -29,14 +30,13 @@ //#define GNASH_DEBUG_AUDIO_DECODING -#define AVCODEC_DECODE_AUDIO avcodec_decode_audio2 - namespace gnash { namespace media { namespace ffmpeg { AudioDecoderFfmpeg::AudioDecoderFfmpeg(const AudioInfo& info) : + _output8cached(NULL), _audioCodec(NULL), _audioCodecCtx(NULL), _parser(NULL), @@ -57,6 +57,7 @@ AudioDecoderFfmpeg::AudioDecoderFfmpeg(const AudioInfo& info) AudioDecoderFfmpeg::AudioDecoderFfmpeg(SoundInfo& info) : + _output8cached(NULL), _audioCodec(NULL), _audioCodecCtx(NULL), _parser(NULL) @@ -75,6 +76,7 @@ AudioDecoderFfmpeg::~AudioDecoderFfmpeg() av_free(_audioCodecCtx); } if (_parser) av_parser_close(_parser); + if (_output8cached) delete[] _output8cached; } void AudioDecoderFfmpeg::setup(SoundInfo& info) @@ -402,19 +404,31 @@ AudioDecoderFfmpeg::decode(const boost::uint8_t* input, continue; } + // Here's a bit of a problem. The FFmpeg parser function garbage + // collects anything it gives us upon the next call, and the real decoding + // may not succceed right-away. While inefficient, we *need* to make a + // copy of the data. + boost::uint8_t* framecopy = new boost::uint8_t[framesize + + FF_INPUT_BUFFER_PADDING_SIZE]; + memcpy(framecopy, frame, framesize); + memset(framecopy + framesize, 0, FF_INPUT_BUFFER_PADDING_SIZE); // Now, decode the frame. We use the ::decodeFrame specialized function // here so resampling is done appropriately + std::auto_ptr frameptr(new EncodedAudioFrame); + frameptr->data.reset(framecopy); + frameptr->dataSize = framesize; + boost::uint32_t outSize = 0; - boost::scoped_array outBuf( - decodeFrame(frame, framesize, outSize)); + boost::scoped_array outBuf(decode(frameptr, outSize)); if (!outBuf) { // Setting data position to data size will get the sound removed // from the active sound list later on. decodedBytes = inputSize; - break; + delete[] retBuf; + return NULL; } #ifdef GNASH_DEBUG_AUDIO_DECODING @@ -461,145 +475,164 @@ AudioDecoderFfmpeg::decode(const boost::uint8_t* input, } boost::uint8_t* -AudioDecoderFfmpeg::decode(const EncodedAudioFrame& ef, - boost::uint32_t& outputSize) -{ - return decodeFrame(ef.data.get(), ef.dataSize, outputSize); -} - -boost::uint8_t* -AudioDecoderFfmpeg::decodeFrame(const boost::uint8_t* input, - boost::uint32_t inputSize, boost::uint32_t& outputSize) +AudioDecoderFfmpeg::decode(std::auto_ptr af, + boost::uint32_t& outputSize) { - //GNASH_REPORT_FUNCTION; - - assert(inputSize); - - const size_t bufsize = AVCODEC_MAX_AUDIO_FRAME_SIZE; + boost::shared_ptr packet(af.release()); - // TODO: make this a private member, to reuse (see NetStreamFfmpeg in 0.8.3) - boost::uint8_t* output; - - output = reinterpret_cast(av_malloc(bufsize)); - if (!output) { - log_error(_("failed to allocate audio buffer.")); - outputSize = 0; - return NULL; + // We may have older, unhandled packets we need to decode first. + if ( packet.get() != NULL && !_pendingPackets.empty() ) + { + assert(packet.get()->data.get() != NULL); + _pendingPackets.push_back(packet); + packet.reset(); + } + if ( packet.get() == NULL ) + { + assert(!_pendingPackets.empty()); + packet = _pendingPackets.front(); + _pendingPackets.pop_front(); } - boost::int16_t* outPtr = reinterpret_cast(output); - - // We initialize output size to the full size - // then decoding will eventually reduce it - int outSize = bufsize; + const size_t bufsize = AVCODEC_MAX_AUDIO_FRAME_SIZE; -#ifdef GNASH_DEBUG_AUDIO_DECODING - log_debug("AudioDecoderFfmpeg: about to decode %d bytes; " - "ctx->channels:%d, avctx->frame_size:%d", - inputSize, _audioCodecCtx->channels, _audioCodecCtx->frame_size); + union + { + boost::uint8_t* output8; + boost::int16_t* output16; + }; + + // Create an output buffer or recycle an old one. + output8 = (_output8cached) ? _output8cached : new boost::uint8_t[bufsize]; + _output8cached = NULL; + + // Ignore data we have already decoded. + boost::uint8_t* data = packet->data.get() + packet->dataAlreadyUsed; + int dataSize = packet->dataSize - packet->dataAlreadyUsed; + assert(0 < dataSize); + + int outSize = bufsize; +#ifdef HAVE_AVCODEC_DECODE_AUDIO3 + AVPacket avpacket; + av_init_packet(&avpacket); + avpacket.data = data; + avpacket.size = (int) dataSize; + int bytesdecoded = avcodec_decode_audio3(_audioCodecCtx, output16, + &outSize, &avpacket); +#elif defined(HAVE_AVCODEC_DECODE_AUDIO2) + int bytesdecoded = avcodec_decode_audio2(_audioCodecCtx, output16, + &outSize, data, dataSize); +#else + // In case of avcodec_decode_audio4. + #error "too old or new libavcodec" #endif - // older ffmpeg versions didn't accept a const input.. - int tmp = AVCODEC_DECODE_AUDIO(_audioCodecCtx, outPtr, &outSize, - input, inputSize); - #ifdef GNASH_DEBUG_AUDIO_DECODING - log_debug(" avcodec_decode_audio[2](ctx, bufptr, %d, input, %d) " - "returned %d; set frame_size=%d", - bufsize, inputSize, tmp, outSize); + log_debug("gave %d bytes to avcodec_decode_audio, it accepted %d of them, " + "and gave us %d bytes in return", dataSize, bytesdecoded, + outSize); #endif - if (tmp < 0) { + if ( bytesdecoded < 0 ) + { log_error(_("avcodec_decode_audio returned %d. Upgrading " - "ffmpeg/libavcodec might fix this issue."), tmp); - outputSize = 0; + "ffmpeg/libavcodec might fix this issue."), bytesdecoded); + delete[] output8; + return NULL; + } - av_free(output); + // If no data was used, save it for later. + if ( bytesdecoded == 0 ) + { + // TODO: FIXME: Does this really mean that libavcodec wants us to + // discard the data or provide it as a prefix to the next packet? + log_error(_("avcodec_decode_audio used no input data - this situation " + "is not supported just yet!")); + assert(false); + delete[] output8; return NULL; } - if (outSize < 2) { - log_error(_("outputSize:%d after decoding %d bytes of input audio " - "data. Upgrading ffmpeg/libavcodec might fix this issue."), - outputSize, inputSize); - outputSize = 0; + outputSize = outSize; - av_free(output); - return NULL; + // If the whole packet was not used (e.g. multiple frames), save some data + // for later by putting it in our packet queue. Then return we we got. + assert(bytesdecoded <= dataSize); + if ( bytesdecoded < dataSize ) + { + packet->dataAlreadyUsed += bytesdecoded; + assert((unsigned) packet->dataAlreadyUsed < packet->dataSize); + _pendingPackets.push_back(packet); } - // Resampling is needed. - if (_resampler.init(_audioCodecCtx)) { - // Resampling is needed. + // If no data is ready, just return a buffer of containing nothing. + if ( outputSize == 0 ) + { + _output8cached = output8; + + // Check if we have any pending packets that we might as well decode. + if ( !_pendingPackets.empty() ) + { + std::auto_ptr autonull(NULL); + return decode(autonull, outputSize); + } - // Compute new size based on frame_size and - // resampling configuration - double resampleFactor = (44100.0/_audioCodecCtx->sample_rate) * (2.0/_audioCodecCtx->channels); + output8 = new boost::uint8_t[0]; + outputSize = 0; + return output8; + } + + if (_resampler.init(_audioCodecCtx)) + { + // Resampling is needed. + // Compute new size based on frame_size and resampling configuration + double resampleFactor = (44100.0/_audioCodecCtx->sample_rate) * + (2.0/_audioCodecCtx->channels); bool stereo = _audioCodecCtx->channels > 1 ? true : false; - int inSamples = stereo ? outSize >> 2 : outSize >> 1; + int inSamples = stereo ? outputSize >> 2 : outputSize >> 1; int expectedMaxOutSamples = std::ceil(inSamples*resampleFactor); // *channels *sampleSize - int resampledFrameSize = expectedMaxOutSamples*2*2; + int resampledFrameSize = expectedMaxOutSamples*2*sizeof(boost::int16_t); // Allocate just the required amount of bytes - boost::uint8_t* resampledOutput = new boost::uint8_t[resampledFrameSize]; + union + { + boost::uint8_t* resampled8; + boost::int16_t* resampled16; + }; + + resampled8 = new boost::uint8_t[resampledFrameSize]; #ifdef GNASH_DEBUG_AUDIO_DECODING log_debug("Calling the resampler; resampleFactor:%d; " - "ouput to 44100hz, 2channels, %dbytes; " + "output to 44100hz, 2channels, %dbytes; " "input is %dhz, %dchannels, %dbytes, %dsamples", resampleFactor, resampledFrameSize, _audioCodecCtx->sample_rate, - _audioCodecCtx->channels, outSize, inSamples); + _audioCodecCtx->channels, outputSize, inSamples); #endif - int outSamples = _resampler.resample(outPtr, // input - reinterpret_cast(resampledOutput), // output - inSamples); // input.. + int outSamples = _resampler.resample(output16, resampled16, inSamples); #ifdef GNASH_DEBUG_AUDIO_DECODING log_debug("resampler returned %d samples ", outSamples); #endif - // make sure to set outPtr *after* we use it as input to the resampler - outPtr = reinterpret_cast(resampledOutput); + // make sure to set output8 *after* we use it as input to the resampler + _output8cached = output8; // Recycle this memory buffer. + output8 = resampled8; - av_free(output); - - if (expectedMaxOutSamples < outSamples) { - log_error(" --- Computation of resampled samples (%d) < then the actual returned samples (%d)", - expectedMaxOutSamples, outSamples); - - log_debug(" input frame size: %d", outSize); - log_debug(" input sample rate: %d", _audioCodecCtx->sample_rate); - log_debug(" input channels: %d", _audioCodecCtx->channels); - log_debug(" input samples: %d", inSamples); - - log_debug(" output sample rate (assuming): %d", 44100); - log_debug(" output channels (assuming): %d", 2); - log_debug(" output samples: %d", outSamples); - - /// Memory errors... - abort(); - } + assert(outSamples <= expectedMaxOutSamples); // Use the actual number of samples returned, multiplied // to get size in bytes (not two-byte samples) and for // stereo? - outSize = outSamples * 2 * 2; - - } - else { - boost::uint8_t* newOutput = new boost::uint8_t[outSize]; - std::memcpy(newOutput, output, outSize); - outPtr = reinterpret_cast(newOutput); - av_free(output); + outputSize = outSamples * 2 * sizeof(boost::int16_t); } - outputSize = outSize; - return reinterpret_cast(outPtr); + return output8; } int @@ -609,6 +642,14 @@ AudioDecoderFfmpeg::parseInput(const boost::uint8_t* input, { if ( _needsParsing ) { + +#ifdef HAVE_AV_PARSER_PARSE2 + return av_parser_parse2(_parser, _audioCodecCtx, + const_cast(outFrame), + outFrameSize, + input, inputSize, + 0, 0, AV_NOPTS_VALUE); // pts & dts +#elif defined(HAVE_AV_PARSER_PARSE) return av_parser_parse(_parser, _audioCodecCtx, // as of 2008-10-28 SVN, ffmpeg doesn't // accept a pointer to pointer to const.. @@ -616,6 +657,10 @@ AudioDecoderFfmpeg::parseInput(const boost::uint8_t* input, outFrameSize, input, inputSize, 0, 0); // pts & dts +#else +// In case of av_parser_parse3. +#error "Your libavcodec version is too new" +#endif } else { diff --git a/libmedia/ffmpeg/AudioDecoderFfmpeg.h b/libmedia/ffmpeg/AudioDecoderFfmpeg.h index e28b745..8c68ab7 100644 --- a/libmedia/ffmpeg/AudioDecoderFfmpeg.h +++ b/libmedia/ffmpeg/AudioDecoderFfmpeg.h @@ -19,6 +19,8 @@ #ifndef GNASH_AUDIODECODERFFMPEG_H #define GNASH_AUDIODECODERFFMPEG_H +#include + #include "ffmpegHeaders.h" #include "log.h" @@ -59,7 +61,7 @@ public: boost::uint32_t inputSize, boost::uint32_t& outputSize, boost::uint32_t& decodedBytes); - boost::uint8_t* decode(const EncodedAudioFrame& af, + boost::uint8_t* decode(std::auto_ptr af, boost::uint32_t& outputSize); private: @@ -67,8 +69,9 @@ private: void setup(const AudioInfo& info); void setup(SoundInfo& info); - boost::uint8_t* decodeFrame(const boost::uint8_t* input, - boost::uint32_t inputSize, boost::uint32_t& outputSize); + std::list< boost::shared_ptr > _pendingPackets; + + boost::uint8_t* _output8cached; AVCodec* _audioCodec; AVCodecContext* _audioCodecCtx; diff --git a/libmedia/ffmpeg/MediaParserFfmpeg.cpp b/libmedia/ffmpeg/MediaParserFfmpeg.cpp index 492d26d..3c82070 100644 --- a/libmedia/ffmpeg/MediaParserFfmpeg.cpp +++ b/libmedia/ffmpeg/MediaParserFfmpeg.cpp @@ -17,6 +17,7 @@ // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // +#include // for memset #include "ffmpegHeaders.h" #include "MediaParserFfmpeg.h" #include "GnashException.h" @@ -216,12 +217,10 @@ MediaParserFfmpeg::parseAudioFrame(AVPacket& packet) std::auto_ptr frame ( new EncodedAudioFrame ); - // TODO: FIXME: *2 is an hack to avoid libavcodec reading past end of allocated space - // we might do proper padding or (better) avoid the copy as a whole by making - // EncodedVideoFrame virtual. - size_t allocSize = packet.size*2; + size_t allocSize = packet.size + FF_INPUT_BUFFER_PADDING_SIZE; boost::uint8_t* data = new boost::uint8_t[allocSize]; std::copy(packet.data, packet.data+packet.size, data); + memset(data + packet.size, 0, FF_INPUT_BUFFER_PADDING_SIZE); frame->data.reset(data); frame->dataSize = packet.size; diff --git a/libmedia/gst/AudioDecoderGst.cpp b/libmedia/gst/AudioDecoderGst.cpp index 8932e7b..e67acc5 100644 --- a/libmedia/gst/AudioDecoderGst.cpp +++ b/libmedia/gst/AudioDecoderGst.cpp @@ -225,7 +225,8 @@ AudioDecoderGst::pullBuffers(boost::uint32_t& outputSize) if (!outputSize) { log_debug(_("Pushed data, but there's nothing to pull (yet)")); - return 0; + outputSize = 0; + return new boost::uint8_t[0]; } boost::uint8_t* rbuf = new boost::uint8_t[outputSize]; @@ -270,20 +271,20 @@ AudioDecoderGst::decode(const boost::uint8_t* input, boost::uint32_t inputSize, } boost::uint8_t* -AudioDecoderGst::decode(const EncodedAudioFrame& ef, boost::uint32_t& outputSize) +AudioDecoderGst::decode(std::auto_ptr ef, boost::uint32_t& outputSize) { outputSize = 0; GstBuffer* gstbuf; - EncodedExtraGstData* extradata = dynamic_cast(ef.extradata.get()); + EncodedExtraGstData* extradata = dynamic_cast(ef->extradata.get()); if (extradata) { gstbuf = extradata->buffer; } else { - gstbuf = gst_buffer_new_and_alloc(ef.dataSize); - memcpy (GST_BUFFER_DATA (gstbuf), ef.data.get(), ef.dataSize); + gstbuf = gst_buffer_new_and_alloc(ef->dataSize); + memcpy (GST_BUFFER_DATA (gstbuf), ef->data.get(), ef->dataSize); } bool success = swfdec_gst_decoder_push(&_decoder, gstbuf); diff --git a/libmedia/gst/AudioDecoderGst.h b/libmedia/gst/AudioDecoderGst.h index c25ffa7..cc6eda8 100644 --- a/libmedia/gst/AudioDecoderGst.h +++ b/libmedia/gst/AudioDecoderGst.h @@ -50,7 +50,7 @@ public: boost::uint8_t* decode(const boost::uint8_t* input, boost::uint32_t inputSize, boost::uint32_t& outputSize, boost::uint32_t& decodedData); - boost::uint8_t* decode(const EncodedAudioFrame& ef, boost::uint32_t& outputSize); + boost::uint8_t* decode(std::auto_ptr ef, boost::uint32_t& outputSize); private: diff --git a/testsuite/libmedia.all/.gitignore b/testsuite/libmedia.all/.gitignore new file mode 100644 index 0000000..81154dd --- /dev/null +++ b/testsuite/libmedia.all/.gitignore @@ -0,0 +1 @@ +samples diff --git a/testsuite/libmedia.all/Makefile.am b/testsuite/libmedia.all/Makefile.am index b1e1e2c..6a98aa4 100644 --- a/testsuite/libmedia.all/Makefile.am +++ b/testsuite/libmedia.all/Makefile.am @@ -49,7 +49,11 @@ INCLUDES = \ $(GSTAPP_CFLAGS) \ $(GSTINTERFACES_CFLAGS) -check_PROGRAMS = +check_PROGRAMS = test_decoding + +test_decoding_SOURCES = test_decoding.cpp +test_decoding_LDADD = $(AM_LDFLAGS) +test_decoding_DEPENDENCIES = site-update if USE_GST_ENGINE @@ -73,7 +77,8 @@ CLEANFILES = \ site.exp.bak \ testrun.* \ fooBar* \ - *.bin + *.bin \ + samples/* check-DEJAGNU: site-update @runtest=$(RUNTEST); \ @@ -92,3 +97,11 @@ site-update: site.exp @sed -e '/testcases/d' site.exp.bak > site.exp @echo "# This is a list of the pre-compiled testcases" >> site.exp @echo "set testcases \"$(check_PROGRAMS)\"" >> site.exp + +test_decoding.cpp: $(builddir)/samples + +.PHONY: $(builddir)/samples +$(builddir)/samples: + $(SHELL) $(srcdir)/mksamples.sh "$(srcdir)" "$(builddir)" "$(CSOUND)" "$(FFMPEG)" "$(LAME)" + + diff --git a/testsuite/libmedia.all/mksamples.sh b/testsuite/libmedia.all/mksamples.sh new file mode 100755 index 0000000..d62520b --- /dev/null +++ b/testsuite/libmedia.all/mksamples.sh @@ -0,0 +1,47 @@ +#!/bin/sh + +SRCDIR="$1" +BUILDDIR="$2" +if [ "$SRCDIR" == "" -o "$BUILDDIR" == "" ]; then + echo "usage: $0 [csound-program-path] [ffmpeg-program-path] [lame-program-path]" + exit +fi + +CSOUND="$3" +FFMPEG="$4" +LAME="$5" + +SMPDIR="$BUILDDIR/samples" +rm -rf "$SMPDIR" +mkdir -p "$SMPDIR" + +# TODO: Add a lot of different codecs in various containers below. The more +# diverse formats are generated, the better Gnash's media playback abillity +# is tested. + +# TODO: Generate files with video as well as audio, such that video playback is +# tested thoroughly too. + +if [ "$CSOUND" != "" ]; then + + "$CSOUND" --nodisplays --wave "$SRCDIR/sample.orc" "$SRCDIR/sample.sco" -o "$SMPDIR/sample-pcm.wav" + + if [ "$FFMPEG" != "" ]; then + "$FFMPEG" -i "$SMPDIR/sample-pcm.wav" -y -acodec flac "$SMPDIR/sample-flac.flac" + "$FFMPEG" -i "$SMPDIR/sample-pcm.wav" -y -acodec libvorbis "$SMPDIR/sample-vorbis.ogg" + "$FFMPEG" -i "$SMPDIR/sample-pcm.wav" -y -acodec libmp3lame "$SMPDIR/sample-mp3.mp4" + "$FFMPEG" -i "$SMPDIR/sample-pcm.wav" -y "$SMPDIR/sample-pcm.flv" + "$FFMPEG" -i "$SMPDIR/sample-pcm.wav" -y -acodec libvorbis "$SMPDIR/sample-vorbis.webm" + "$FFMPEG" -i "$SMPDIR/sample-pcm.wav" -y -acodec libmp3lame "$SMPDIR/sample-mp3.mkv" + "$FFMPEG" -i "$SMPDIR/sample-pcm.wav" -y -acodec libvorbis "$SMPDIR/sample-vorbis.mkv" + fi + + if [ "$LAME" != "" ]; then + "$LAME" "$SMPDIR/sample-pcm.wav" "$SMPDIR/sample-mp3.mp3" + if [ "$FFMPEG" != "" ]; then + # "$FFMPEG" -i "$SMPDIR/sample-mp3.mp3" -y "$SMPDIR/sample-mp3.mkv" # crashes the testcase on some systems + "$FFMPEG" -i "$SMPDIR/sample-mp3.mp3" -y "$SMPDIR/sample-mp3.flv" + fi + fi + +fi diff --git a/testsuite/libmedia.all/sample.orc b/testsuite/libmedia.all/sample.orc new file mode 100644 index 0000000..8b35f99 --- /dev/null +++ b/testsuite/libmedia.all/sample.orc @@ -0,0 +1,9 @@ +sr = 44100 ; audio sampling rate is 44.1 kHz +kr = 4410 ; control rate is 4410 Hz +ksmps = 10 ; number of samples in a control period (sr/kr) +nchnls = 1 ; number of channels of audio output + +instr 1 + asig oscil 10000, p5, 1 ; audio oscillator + out asig ; send signal to channel 1 +endin diff --git a/testsuite/libmedia.all/sample.sco b/testsuite/libmedia.all/sample.sco new file mode 100644 index 0000000..5f8fa19 --- /dev/null +++ b/testsuite/libmedia.all/sample.sco @@ -0,0 +1,7 @@ +f1 0 256 10 1 ; a sine wave function table + + +; a pentatonic scale +i1 0 5 0 1200 +e + diff --git a/testsuite/libmedia.all/test_decoding.cpp b/testsuite/libmedia.all/test_decoding.cpp new file mode 100644 index 0000000..841ee6f --- /dev/null +++ b/testsuite/libmedia.all/test_decoding.cpp @@ -0,0 +1,317 @@ +// test_decoding.cpp: Tests installed media handlers, +// +// Copyright (C) 2011 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#ifdef HAVE_CONFIG_H +#include "gnashconfig.h" +#endif + +#include "check.h" +#include "log.h" +#include +#include +#include +#include + +#include "GnashSleep.h" +#include "MediaHandler.h" +#include "MediaParser.h" +#include "AudioDecoder.h" +#include "tu_file.h" + +using namespace std; +using namespace gnash; +using namespace gnash::media; + +/// Scans the given dir and returns every element in it. +#include +vector* +lsdir(string path) +{ + DIR* dir = opendir(path.c_str()); + if ( dir == NULL ) { return NULL; } + + vector* result = new vector(); + + dirent* entry; + while ( (entry = readdir(dir)) != NULL ) + { + if ( entry->d_name[0] == '.' ) { continue; } + result->push_back(string(entry->d_name)); + } + + return result; +} + +/// A simple hack to shorten the description of various mediahandlers. +string +getprefix(string desc) +{ + if ( desc.compare(0, 3, "FFm") == 0 ) { return "ffmpeg"; } + if ( desc.compare(0, 3, "Gst") == 0 ) { return "gst"; } + if ( desc.compare(0, 3, "Hai") == 0 ) { return "haiku"; } + return desc; +} + +/// Returns whether a file is expected to fail the dest for a given decoder. +bool +isFailureExpected(string prefix, string file) +{ + // TODO: Produce even more files that can be tested! + + if ( prefix.compare("ffmpeg: ") == 0 ) + { + if ( file.compare("samples/sample-vorbis.ogg") == 0 ) { return false; } + if ( file.compare("samples/sample-mp3.flv") == 0 ) { return false; } + if ( file.compare("samples/sample-pcm.flv") == 0 ) { return false; } + if ( file.compare("samples/sample-mp3.mkv") == 0 ) { return false; } + if ( file.compare("samples/sample-vorbis.webm") == 0 ) { return false; } + if ( file.compare("samples/sample-vorbis.mkv") == 0 ) { return false; } + if ( file.compare("samples/sample-mp3.mp3") == 0 ) { return false; } + if ( file.compare("samples/sample-pcm.wav") == 0 ) { return false; } + if ( file.compare("samples/sample-flac.flac") == 0 ) { return false; } + } + else if ( prefix.compare("gst: ") == 0 ) + { + if ( file.compare("samples/sample-vorbis.ogg") == 0 ) { return false; } + if ( file.compare("samples/sample-mp3.flv") == 0 ) { return false; } + if ( file.compare("samples/sample-pcm.flv") == 0 ) { return false; } + if ( file.compare("samples/sample-mp3.mkv") == 0 ) { return false; } + if ( file.compare("samples/sample-vorbis.webm") == 0 ) { return false; } + if ( file.compare("samples/sample-vorbis.mkv") == 0 ) { return false; } + if ( file.compare("samples/sample-mp3.mp3") == 0 ) { return false; } + if ( file.compare("samples/sample-pcm.wav") == 0 ) { return true; } + if ( file.compare("samples/sample-flac.flac") == 0 ) { return false; } + } + + return false; +} + +/// Outputs the correct result to stdout depending on the expected result. +void +printresult(bool expectedfail, bool result, string msg) +{ + if ( expectedfail && result ) + { + _runtest.xpass(msg); + } + else if ( expectedfail && !result ) + { + _runtest.xfail(msg); + } + else if ( !expectedfail && result ) + { + _runtest.pass(msg); + } + else // if ( !expectedfail && !result ) + { + _runtest.fail(msg); + } +} + +/// Attempts to decode 'filepath' using 'handler' and prints information to +/// stdout depending on whether a failure is expected and what happened. +void +testFile(const string& filepath, MediaHandler* handler, bool expectedfail, + string prefix) +{ + // Open the file for reading. + auto_ptr channel = makeFileChannel(filepath.c_str(), "rb"); + + if ( channel.get() == 0 ) + { + printresult(false, false, prefix + filepath + ": could not open for " + "reading"); + return; + } + + // Create a media parser to parse the file. + std::auto_ptr parser = handler->createMediaParser(channel); + + if ( parser.get() == 0 ) + { + printresult(expectedfail, false, prefix + filepath + ": could not " + "create parser"); + return; + } + + // Wait for the media parser to parse the file's headers and break if it + // takes too long. (I don't see any way to detect whether parsing failed). + // TODO: This is a very hacky method to detect when the parser->getFooInfo() + // is available. + boost::uint64_t unused; + unsigned waited = 0; + while ( !parser->nextFrameTimestamp(unused) ) + { + if ( 2000 < waited ) + { + // NOTE: This could also be because no the headers could not be + // parsed or that no sound or video is available. + printresult(expectedfail, false, prefix + filepath + ": used more " + "than 2 seconds to parse headers"); + return; + } + + gnashSleep(10*1000); waited += 10; + } + + VideoInfo* videoInfo = parser->getVideoInfo(); + AudioInfo* audioInfo = parser->getAudioInfo(); + + std::auto_ptr audioDecoder; + std::auto_ptr videoDecoder; + + // Create a decoder for any sound found in the file. + if ( audioInfo ) + { + try + { + audioDecoder = handler->createAudioDecoder(*audioInfo); + } + catch ( gnash::MediaException& e ) + { + printresult(expectedfail, false, prefix + filepath + ": " + + e.what()); + return; + } + } + + // Create a decoder for any video found in the file. + if ( videoInfo ) + { + try + { + videoDecoder = handler->createVideoDecoder(*videoInfo); + } + catch ( gnash::MediaException& e ) + { + printresult(expectedfail, false, prefix + filepath + ": " + + e.what()); + return; + } + } + + size_t read = 0; // Number of bytes decoded. + + // TODO: Attempt to decode video as well as audio. + + // Attempt to decode the entire file to see if bad things happen. + while ( audioInfo ) + { + std::auto_ptr audioEncodedFrame; + + unsigned milisecondssincelastframe = 0; + const unsigned maxmiliseconds = 2000; + + // Wait for the next frame, and break if we have possibly entered + // a deadlock or an infinite loop. + while ( audioEncodedFrame.get() == 0 ) + { + if ( maxmiliseconds < milisecondssincelastframe ) + { + printresult(expectedfail, false, prefix + filepath + ": used " + "more than 2000 ms to decode a frame"); + return; + } + + audioEncodedFrame = parser->nextAudioFrame(); + if ( audioEncodedFrame.get() == 0 ) + { + if ( parser->parsingCompleted() ) { break; } + gnashSleep(10*1000); milisecondssincelastframe += 10; + } + } + + // Check for end-of-file conditions and terminate. + if ( parser->parsingCompleted() ) { break; } + + // Attempt to decode the packet received. + boost::uint32_t soundSize = 0; + boost::uint8_t* sound = audioDecoder->decode(audioEncodedFrame, + soundSize); + + // Check if the decoding went well (return non-null). + if ( sound == NULL ) + { + // NOTE: there is currently no way to tell whether a decoding error + // happened or if the decoder has encountered a frame split over + // multiple packages. Gstreamer spuriously generates a lot of this. + printresult(expectedfail, false, prefix + filepath + ": no frame " + "was available after decoding a packet"); + return; + } + + // For debugging reasons, record how much data was decoded. + read += soundSize; + + delete[] sound; + } + + // Declare the file successfully decoded. + printresult(expectedfail, true, prefix + filepath); +} + +/// Tests this media handler on every sample file in the samples/ directory. +void +testHandler(MediaHandler* handler) +{ + string sampledir = "samples"; + + // Get a list of test files. + vector* filelist = lsdir(sampledir); + if ( filelist == NULL ) + { + cerr << "couldn't list elements in dir '" << sampledir << "'" << endl; + return; + } + + // Reduce the mediahandler's description in a short string. + string prefix = getprefix(handler->description()) + ": "; + + // Test every file in the samples directory with this handler. + vector::iterator it; + for ( it = filelist->begin(); it != filelist->end(); it++ ) + { + string file = sampledir + "/" + *it; + bool expectedFailure = isFailureExpected(prefix, file); + testFile(file, handler, expectedFailure, prefix); + } + + delete filelist; +} + +/// Tests every install mediahandler on all sample files. +int +main(int /*argc*/, char* /*argv*/[]) +{ + // Useful when determining exactly what is going on. + //LogFile::getDefaultInstance().setVerbosity(3); + + // Get a list of each installed mediahandler. + vector mediaHandlers; + MediaFactory::instance().listKeys(back_inserter(mediaHandlers)); + + // Run our test on each of the mediahandlers. + vector::iterator it; + for ( it = mediaHandlers.begin(); it != mediaHandlers.end(); it++ ) + { + MediaHandler* handler = MediaFactory::instance().get(*it); + testHandler(handler); + } + + return 0; +}