diff --git a/aegisub/libmedia/audio/avs_audio.cpp b/aegisub/libmedia/audio/avs_audio.cpp new file mode 100644 index 000000000..82ca5f35f --- /dev/null +++ b/aegisub/libmedia/audio/avs_audio.cpp @@ -0,0 +1,170 @@ +// Copyright (c) 2005-2006, Rodrigo Braz Monteiro, Fredrik Mellbin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file audio_provider_avs.cpp +/// @brief Avisynth-based audio provider +/// @ingroup audio_input +/// + +#include "config.h" + +#ifdef WITH_AVISYNTH + +#ifndef AGI_PRE +#include +#include + +#include +#endif + +#include "audio_provider_avs.h" +#include "charset_conv.h" +#include "compat.h" +#include "main.h" +#include "standard_paths.h" +#include "utils.h" + +/// @brief Constructor +/// @param _filename +/// +AvisynthAudioProvider::AvisynthAudioProvider(wxString filename) try : filename(filename) { + AVSValue script; + wxMutexLocker lock(AviSynthMutex); + + wxFileName fn(filename); + if (!fn.FileExists()) + throw agi::FileNotFoundError(STD_STR(filename)); + + // Include + if (filename.EndsWith(_T(".avs"))) { + char *fname = env->SaveString(fn.GetShortPath().mb_str(csConvLocal)); + script = env->Invoke("Import", fname); + } + + // Use DirectShowSource + else { + const char * argnames[3] = { 0, "video", "audio" }; + AVSValue args[3] = { env->SaveString(fn.GetShortPath().mb_str(csConvLocal)), false, true }; + + // Load DirectShowSource.dll from app dir if it exists + wxFileName dsspath(StandardPaths::DecodePath(_T("?data/DirectShowSource.dll"))); + if (dsspath.FileExists()) { + env->Invoke("LoadPlugin",env->SaveString(dsspath.GetShortPath().mb_str(csConvLocal))); + } + + // Load audio with DSS if it exists + if (env->FunctionExists("DirectShowSource")) { + script = env->Invoke("DirectShowSource", AVSValue(args,3),argnames); + } + // Otherwise fail + else { + throw AudioOpenError("No suitable audio source filter found. Try placing DirectShowSource.dll in the Aegisub application directory."); + } + } + + LoadFromClip(script); +} +catch (AvisynthError &err) { + throw AudioOpenError("Avisynth error: " + std::string(err.msg)); +} + +/// @brief Read from environment +/// @param _clip +/// +void AvisynthAudioProvider::LoadFromClip(AVSValue _clip) { + AVSValue script; + + // Check if it has audio + VideoInfo vi = _clip.AsClip()->GetVideoInfo(); + if (!vi.HasAudio()) throw AudioOpenError("No audio found."); + + // Convert to one channel + char buffer[1024]; + strcpy(buffer,lagi_wxString(OPT_GET("Audio/Downmixer")->GetString()).mb_str(csConvLocal)); + script = env->Invoke(buffer, _clip); + + // Convert to 16 bits per sample + script = env->Invoke("ConvertAudioTo16bit", script); + vi = script.AsClip()->GetVideoInfo(); + + // Convert sample rate + int setsample = OPT_GET("Provider/Audio/AVS/Sample Rate")->GetInt(); + if (vi.SamplesPerSecond() < 32000) setsample = 44100; + if (setsample != 0) { + AVSValue args[2] = { script, setsample }; + script = env->Invoke("ResampleAudio", AVSValue(args,2)); + } + + // Set clip + PClip tempclip = script.AsClip(); + vi = tempclip->GetVideoInfo(); + + // Read properties + channels = vi.AudioChannels(); + num_samples = vi.num_audio_samples; + sample_rate = vi.SamplesPerSecond(); + bytes_per_sample = vi.BytesPerAudioSample(); + + clip = tempclip; +} + +/// @brief Get audio +/// @param buf +/// @param start +/// @param count +/// +void AvisynthAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) const { + // Requested beyond the length of audio + if (start+count > num_samples) { + int64_t oldcount = count; + count = num_samples-start; + if (count < 0) count = 0; + + // Fill beyond with zero + if (bytes_per_sample == 1) { + char *temp = (char *) buf; + for (int i=count;iGetAudio(buf,start,count,env); + } +} +#endif diff --git a/aegisub/libmedia/audio/avs_audio.h b/aegisub/libmedia/audio/avs_audio.h new file mode 100644 index 000000000..31836ec87 --- /dev/null +++ b/aegisub/libmedia/audio/avs_audio.h @@ -0,0 +1,68 @@ +// Copyright (c) 2005-2006, Rodrigo Braz Monteiro, Fredrik Mellbin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file audio_provider_avs.h +/// @see audio_provider_avs.cpp +/// @ingroup audio_input +/// + +#ifdef WITH_AVISYNTH +#include "include/aegisub/audio_provider.h" +#include "avisynth_wrap.h" + + +/// DOCME +/// @class AvisynthAudioProvider +/// @brief DOCME +/// +/// DOCME +class AvisynthAudioProvider : public AudioProvider, public AviSynthWrapper { + /// DOCME + wxString filename; + + /// DOCME + PClip clip; + + void LoadFromClip(AVSValue clip); + void SetFile(); + +public: + AvisynthAudioProvider(wxString _filename); + + wxString GetFilename() const { return filename; } + + bool AreSamplesNativeEndian() const { return true; } + bool NeedsCache() const { return true; } + + void GetAudio(void *buf, int64_t start, int64_t count) const; + void GetWaveForm(int *min,int *peak,int64_t start,int w,int h,int samples,float scale); +}; +#endif diff --git a/aegisub/libmedia/audio/convert.cpp b/aegisub/libmedia/audio/convert.cpp new file mode 100644 index 000000000..e08df165d --- /dev/null +++ b/aegisub/libmedia/audio/convert.cpp @@ -0,0 +1,220 @@ +// Copyright (c) 2008, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file audio_provider_convert.cpp +/// @brief Intermediate sample format-converting audio provider +/// @ingroup audio_input +/// + +#include "config.h" + +#include "aegisub_endian.h" +#include "audio_provider_convert.h" +#include "audio_provider_downmix.h" + + +/// @brief Constructor +/// @param src +/// +ConvertAudioProvider::ConvertAudioProvider(AudioProvider *src) : source(src) { + channels = source->GetChannels(); + num_samples = source->GetNumSamples(); + sample_rate = source->GetSampleRate(); + bytes_per_sample = 2; + + sampleMult = 1; + if (sample_rate < 16000) sampleMult = 4; + else if (sample_rate < 32000) sampleMult = 2; + sample_rate *= sampleMult; + num_samples *= sampleMult; +} + +/// @brief Convert to 16-bit +/// @param src +/// @param dst +/// @param count +/// +void ConvertAudioProvider::Make16Bit(const char *src, short *dst, int64_t count) const { + for (int64_t i=0;i + +/// @brief DOCME +/// @param src +/// @param dst +/// @param count +/// @param converter +/// +void ConvertAudioProvider::ChangeSampleRate(const short *src, short *dst, int64_t count, const SampleConverter &converter) const { + // Upsample by 2 + if (sampleMult == 2) { + int64_t size = count/2; + short cur; + short next = 0; + for (int64_t i=0;i 0) { + *dst++ = converter(*src++); + } + } +} + +/// DOCME +struct NullSampleConverter { + inline short operator()(const short val) const { + return val; + } +}; + +/// DOCME +struct EndianSwapSampleConverter { + inline short operator()(const short val) const { + return (short)Endian::Reverse((uint16_t)val); + }; +}; + + +/// @brief Get audio +/// @param destination +/// @param start +/// @param count +/// +void ConvertAudioProvider::GetAudio(void *destination, int64_t start, int64_t count) const { + // Bits per sample + int srcBps = source->GetBytesPerSample(); + + // Nothing to do + if (sampleMult == 1 && srcBps == 2) { + source->GetAudio(destination,start,count); + } + + // Convert + else { + // Allocate buffers with sufficient size for the entire operation + size_t fullSize = count; + int64_t srcCount = count / sampleMult; + short *buffer1 = NULL; + short *buffer2 = NULL; + short *last = NULL; + + // Read audio + buffer1 = new short[fullSize * channels]; + source->GetAudio(buffer1,start/sampleMult,srcCount); + + // Convert from 8-bit to 16-bit + if (srcBps == 1) { + if (sampleMult == 1) { + Make16Bit((const char*)buffer1,(short*)destination,srcCount * channels); + } + else { + buffer2 = new short[fullSize * channels]; + Make16Bit((const char*)buffer1,buffer2,srcCount * channels); + last = buffer2; + } + } + + // Already 16-bit + else if (srcBps == 2) last = buffer1; + + // Convert sample rate + if (sampleMult != 1 && source->AreSamplesNativeEndian()) { + ChangeSampleRate(last,(short*)destination,count * channels, NullSampleConverter()); + } + else if (!source->AreSamplesNativeEndian()) { + ChangeSampleRate(last,(short*)destination,count * channels, EndianSwapSampleConverter()); + } + + delete [] buffer1; + delete [] buffer2; + } +} + + +/// @brief See if we need to downmix the number of channels +/// @param source_provider +/// +AudioProvider *CreateConvertAudioProvider(AudioProvider *source_provider) { + AudioProvider *provider = source_provider; + + // Aegisub requires 16 bit samples, + // some audio players break with low samplerates, + // everything breaks with wrong-ended samples. + if (provider->GetBytesPerSample() != 2 || + provider->GetSampleRate() < 32000 || + !provider->AreSamplesNativeEndian()) + { + // @todo add support for more bitdepths (i.e. 24- and 32-bit audio) + if (provider->GetBytesPerSample() > 2) + throw AudioOpenError("Audio format converter: audio with bitdepths greater than 16 bits/sample is currently unsupported"); + + provider = new ConvertAudioProvider(provider); + } + + // We also require mono audio for historical reasons + if (provider->GetChannels() != 1) + { + provider = new DownmixingAudioProvider(provider); + } + + return provider; +} diff --git a/aegisub/libmedia/audio/convert.h b/aegisub/libmedia/audio/convert.h new file mode 100644 index 000000000..79b3e2ef8 --- /dev/null +++ b/aegisub/libmedia/audio/convert.h @@ -0,0 +1,70 @@ +// Copyright (c) 2008, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file audio_provider_convert.h +/// @see audio_provider_convert.cpp +/// @ingroup audio_input +/// + +#include "include/aegisub/audio_provider.h" + +#ifndef AGI_PRE +#include +#endif + +/// DOCME +/// @class ConvertAudioProvider +/// @brief DOCME +/// +/// DOCME +class ConvertAudioProvider : public AudioProvider { + /// DOCME + int sampleMult; + + /// DOCME + std::tr1::shared_ptr source; + void Make16Bit(const char *src, short *dst, int64_t count) const; + template + void ChangeSampleRate(const short *src, short *dst, int64_t count, const SampleConverter &converter) const; + +public: + ConvertAudioProvider(AudioProvider *source); + + /// By its nature, the ConvertAudioProvider always delivers machine endian. + /// That's one of the points of it! + bool AreSamplesNativeEndian() const { return true; } + + void GetAudio(void *buf, int64_t start, int64_t count) const; + + wxString GetFilename() const { return source->GetFilename(); } +}; + +AudioProvider *CreateConvertAudioProvider(AudioProvider *source_provider); diff --git a/aegisub/libmedia/audio/downmix.cpp b/aegisub/libmedia/audio/downmix.cpp new file mode 100644 index 000000000..a8c277e95 --- /dev/null +++ b/aegisub/libmedia/audio/downmix.cpp @@ -0,0 +1,125 @@ +// Copyright (c) 2007-2008, Niels Martin Hansen +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file audio_provider_downmix.cpp +/// @brief Intermediate audio provider downmixing the signal to mono +/// @ingroup audio_input +/// + + +////////////////// +// Headers +#include "config.h" + +#include "audio_provider_downmix.h" + + +/// @brief Constructor +/// @param source +/// +DownmixingAudioProvider::DownmixingAudioProvider(AudioProvider *source) : provider(source) { + filename = source->GetFilename(); + channels = 1; // target + src_channels = source->GetChannels(); + num_samples = source->GetNumSamples(); + bytes_per_sample = source->GetBytesPerSample(); + sample_rate = source->GetSampleRate(); + + if (!(bytes_per_sample == 1 || bytes_per_sample == 2)) + throw AudioOpenError("Downmixing Audio Provider: Can only downmix 8 and 16 bit audio"); + if (!source->AreSamplesNativeEndian()) + throw AudioOpenError("Downmixing Audio Provider: Source must have machine endian samples"); +} + +/// @brief Actual work happens here +/// @param buf +/// @param start +/// @param count +/// +void DownmixingAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) const { + if (count == 0) return; + + // We can do this ourselves + if (start >= num_samples) { + if (bytes_per_sample == 1) + // 8 bit formats are usually unsigned with bias 127 + memset(buf, 127, count); + else + // While everything else is signed + memset(buf, 0, count*bytes_per_sample); + + return; + } + + // So alloc some temporary memory for this + // Depending on use, this might be made faster by using + // a pre-allocced block of memory...? + char *tmp = new char[count*bytes_per_sample*src_channels]; + + try { + provider->GetAudio(tmp, start, count); + } + catch (...) { + delete tmp; + throw; + } + + // Now downmix + // Just average the samples over the channels (really bad if they're out of phase!) + // XXX: Assuming here that sample data are in machine endian, an upstream provider should ensure that + if (bytes_per_sample == 1) { + uint8_t *src = (uint8_t *)tmp; + uint8_t *dst = (uint8_t *)buf; + + while (count > 0) { + int sum = 0; + for (int c = 0; c < src_channels; c++) + sum += *(src++); + *(dst++) = (uint8_t)(sum / src_channels); + count--; + } + } + else if (bytes_per_sample == 2) { + int16_t *src = (int16_t *)tmp; + int16_t *dst = (int16_t *)buf; + + while (count > 0) { + int sum = 0; + for (int c = 0; c < src_channels; c++) + sum += *(src++); + *(dst++) = (int16_t)(sum / src_channels); + count--; + } + } + + // Done downmixing, free the work buffer + delete[] tmp; +} diff --git a/aegisub/libmedia/audio/downmix.h b/aegisub/libmedia/audio/downmix.h new file mode 100644 index 000000000..af79d316d --- /dev/null +++ b/aegisub/libmedia/audio/downmix.h @@ -0,0 +1,61 @@ +// Copyright (c) 2007-2008, Niels Martin Hansen +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file audio_provider_downmix.h +/// @see audio_provider_downmix.cpp +/// @ingroup audio_input +/// + +#include "include/aegisub/audio_provider.h" + +#ifndef AGI_PRE +#include +#endif + +/// DOCME +/// @class DownmixingAudioProvider +/// @brief DOCME +/// +/// DOCME +class DownmixingAudioProvider : public AudioProvider { + std::tr1::shared_ptr provider; + + /// DOCME + int src_channels; +public: + DownmixingAudioProvider(AudioProvider *source); + + /// @brief Downmixing requires samples to be native endian beforehand + /// + bool AreSamplesNativeEndian() const { return true; } + + void GetAudio(void *buf, int64_t start, int64_t count) const; +}; diff --git a/aegisub/libmedia/audio/dummy.h b/aegisub/libmedia/audio/dummy.h new file mode 100644 index 000000000..6448412eb --- /dev/null +++ b/aegisub/libmedia/audio/dummy.h @@ -0,0 +1,54 @@ +// Copyright (c) 2006, Niels Martin Hansen +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file audio_provider_dummy.h +/// @see audio_provider_dummy.cpp +/// @ingroup audio_input +/// + +#include "include/aegisub/audio_provider.h" + +/// DOCME +/// @class DummyAudioProvider +/// @brief DOCME +/// +/// DOCME +class DummyAudioProvider : public AudioProvider { + /// DOCME + bool noise; + +public: + DummyAudioProvider(unsigned long dur_ms, bool _noise); + ~DummyAudioProvider(); + + bool AreSamplesNativeEndian() const { return true; } + void GetAudio(void *buf, int64_t start, int64_t count) const; +}; diff --git a/aegisub/libmedia/audio/dummy_audio.cpp b/aegisub/libmedia/audio/dummy_audio.cpp new file mode 100644 index 000000000..08b1c5ce7 --- /dev/null +++ b/aegisub/libmedia/audio/dummy_audio.cpp @@ -0,0 +1,76 @@ +// Copyright (c) 2005-2006, Rodrigo Braz Monteiro, Fredrik Mellbin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file audio_provider_dummy.cpp +/// @brief Dummy (silence or noise) audio provider +/// @ingroup audio_input +/// + +#include "config.h" + +#include "audio_provider_dummy.h" +#include "utils.h" + + +/// @brief Constructor +/// @param dur_ms +/// @param _noise +/// +DummyAudioProvider::DummyAudioProvider(unsigned long dur_ms, bool _noise) { + noise = _noise; + channels = 1; + sample_rate = 44100; + bytes_per_sample = 2; + num_samples = (int64_t)dur_ms * sample_rate / 1000; +} + +/// @brief Destructor +/// +DummyAudioProvider::~DummyAudioProvider() { +} + +/// @brief Get audio +/// @param buf +/// @param start +/// @param count +/// +void DummyAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) const { + short *workbuf = (short*)buf; + + if (noise) { + while (--count > 0) + *workbuf++ = (rand() - RAND_MAX/2) * 10000 / RAND_MAX; + } + else { + while (--count > 0) + *workbuf++ = 0; + } +} diff --git a/aegisub/libmedia/audio/pcm.cpp b/aegisub/libmedia/audio/pcm.cpp new file mode 100644 index 000000000..ba1df2c65 --- /dev/null +++ b/aegisub/libmedia/audio/pcm.cpp @@ -0,0 +1,631 @@ +// Copyright (c) 2007-2008, Niels Martin Hansen +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file audio_provider_pcm.cpp +/// @brief PCM WAV and WAV64 audio provider +/// @ingroup audio_input +/// + + +#include "config.h" + +#ifndef AGI_PRE +#include +#include +#ifndef __WINDOWS__ +#include +#include +#include +#endif + +#include +#include +#include +#endif + +#include + +#include "aegisub_endian.h" +#include "audio_provider_pcm.h" +#include "compat.h" +#include "utils.h" + + +/// @brief DOCME +/// @param filename +/// +PCMAudioProvider::PCMAudioProvider(const wxString &filename) +{ +#ifdef _WIN32 + file_handle = CreateFile( + filename.c_str(), + FILE_READ_DATA, + FILE_SHARE_READ|FILE_SHARE_WRITE, + 0, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL|FILE_FLAG_RANDOM_ACCESS, + 0); + + if (file_handle == INVALID_HANDLE_VALUE) { + throw agi::FileNotFoundError(STD_STR(filename)); + } + + LARGE_INTEGER li_file_size = {0}; + if (!GetFileSizeEx(file_handle, &li_file_size)) { + CloseHandle(file_handle); + throw AudioOpenError("Failed getting file size"); + } + file_size = li_file_size.QuadPart; + + file_mapping = CreateFileMapping( + file_handle, + 0, + PAGE_READONLY, + 0, 0, + 0); + + if (file_mapping == 0) { + CloseHandle(file_handle); + throw AudioOpenError("Failed creating file mapping"); + } + + current_mapping = 0; + +#else + + file_handle = open(filename.mb_str(*wxConvFileName), O_RDONLY); + + if (file_handle == -1) { + throw agi::FileNotFoundError(STD_STR(filename)); + } + + struct stat filestats; + memset(&filestats, 0, sizeof(filestats)); + if (fstat(file_handle, &filestats)) { + close(file_handle); + throw AudioOpenError("Could not stat file to get size"); + } + file_size = filestats.st_size; + + current_mapping = 0; +#endif +} + +/// @brief DOCME +/// +PCMAudioProvider::~PCMAudioProvider() +{ +#ifdef _WIN32 + if (current_mapping) { + UnmapViewOfFile(current_mapping); + } + + CloseHandle(file_mapping); + CloseHandle(file_handle); +#else + if (current_mapping) { + munmap(current_mapping, mapping_length); + } + + close(file_handle); +#endif +} + +/// @brief DOCME +/// @param range_start +/// @param range_length +/// @return +/// +char * PCMAudioProvider::EnsureRangeAccessible(int64_t range_start, int64_t range_length) const +{ + if (range_start + range_length > file_size) { + throw AudioDecodeError("Attempted to map beyond end of file"); + } + + // Check whether the requested range is already visible + if (!current_mapping || range_start < mapping_start || range_start+range_length > mapping_start+(int64_t)mapping_length) { + // It's not visible, change the current mapping + if (current_mapping) { +#ifdef _WIN32 + UnmapViewOfFile(current_mapping); +#else + munmap(current_mapping, mapping_length); +#endif + } + + // Align range start on a 1 MB boundary and go 16 MB back + mapping_start = (range_start & ~0xFFFFF) - 0x1000000; + if (mapping_start < 0) mapping_start = 0; + + if (sizeof(void*) > 4) + // Large address space, use a 2 GB mapping + mapping_length = 0x80000000; + else + // Small (32 bit) address space, use a 256 MB mapping + mapping_length = 0x10000000; + + // Make sure to always make a mapping at least as large as the requested range + if ((int64_t)mapping_length < range_length) { + if (range_length > (int64_t)(~(size_t)0)) + throw AudioDecodeError("Requested range larger than max size_t, cannot create view of file"); + mapping_length = range_length; + } + // But also make sure we don't try to make a mapping larger than the file + if (mapping_start + (int64_t)mapping_length > file_size) + mapping_length = (size_t)(file_size - mapping_start); + // We already checked that the requested range doesn't extend over the end of the file + // Hopefully this should ensure that small files are always mapped in their entirety + +#ifdef _WIN32 + LARGE_INTEGER mapping_start_li; + mapping_start_li.QuadPart = mapping_start; + current_mapping = MapViewOfFile( + file_mapping, // Mapping handle + FILE_MAP_READ, // Access type + mapping_start_li.HighPart, // Offset high-part + mapping_start_li.LowPart, // Offset low-part + mapping_length); // Length of view +#else + current_mapping = mmap(0, mapping_length, PROT_READ, MAP_PRIVATE, file_handle, mapping_start); +#endif + + if (!current_mapping) { + throw AudioDecodeError("Failed mapping a view of the file"); + } + } + + assert(current_mapping); + assert(range_start >= mapping_start); + + // Difference between actual current mapping start and requested range start + ptrdiff_t rel_ofs = (ptrdiff_t)(range_start - mapping_start); + + // Calculate a pointer into current mapping for the requested range + return ((char*)current_mapping) + rel_ofs; +} + +/// @brief DOCME +/// @param buf +/// @param start +/// @param count +/// +void PCMAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) const +{ + // Read blocks from the file + size_t index = 0; + while (count > 0 && index < index_points.size()) { + // Check if this index contains the samples we're looking for + const IndexPoint &ip = index_points[index]; + if (ip.start_sample <= start && ip.start_sample+ip.num_samples > start) { + + // How many samples we can maximum take from this block + int64_t samples_can_do = ip.num_samples - start + ip.start_sample; + if (samples_can_do > count) samples_can_do = count; + + // Read as many samples we can + char *src = EnsureRangeAccessible( + ip.start_byte + (start - ip.start_sample) * bytes_per_sample * channels, + samples_can_do * bytes_per_sample * channels); + memcpy(buf, src, samples_can_do * bytes_per_sample * channels); + + // Update data + buf = (char*)buf + samples_can_do * bytes_per_sample * channels; + start += samples_can_do; + count -= samples_can_do; + } + index++; + } + + // If we exhausted all sample sections zerofill the rest + if (count > 0) { + if (bytes_per_sample == 1) + // 8 bit formats are usually unsigned with bias 127 + memset(buf, 127, count*channels); + else + // While everything else is signed + memset(buf, 0, count*bytes_per_sample*channels); + } +} + +/// DOCME +/// @class RiffWavPCMAudioProvider +/// @brief RIFF WAV PCM provider +/// +/// Overview of RIFF WAV: +class RiffWavPCMAudioProvider : public PCMAudioProvider { + /// DOCME + struct ChunkHeader { + /// Always "RIFF" + char type[4]; + /// File size minus sizeof(ChunkHeader) (i.e. 8) + uint32_t size; + }; + + /// DOCME + struct RIFFChunk { + + /// DOCME + ChunkHeader ch; + + /// Always "WAVE" + char format[4]; + }; + + /// DOCME + struct fmtChunk { + + /// compression format used + /// We support only PCM (0x1) + uint16_t compression; + + /// Number of channels + uint16_t channels; + + /// Samples per second + uint32_t samplerate; + + /// Bytes per second + /// can't always be trusted + uint32_t avg_bytes_sec; + + /// Bytes per sample + uint16_t block_align; + + /// Bits per sample that are actually used; rest should be ignored + uint16_t significant_bits_sample; + // Here was supposed to be some more fields but we don't need them + // and just skipping by the size of the struct wouldn't be safe + // either way, as the fields can depend on the compression. + }; + + + /// @brief DOCME + /// @param str1[] + /// @param str2[] + /// @return + /// + static bool CheckFourcc(const char str1[], const char str2[]) + { + assert(str1); + assert(str2); + return + (str1[0] == str2[0]) && + (str1[1] == str2[1]) && + (str1[2] == str2[2]) && + (str1[3] == str2[3]); + } + +public: + + /// @brief DOCME + /// @param _filename + /// + RiffWavPCMAudioProvider(const wxString &_filename) + : PCMAudioProvider(_filename) + { + filename = _filename; + + // Read header + void *filestart = EnsureRangeAccessible(0, sizeof(RIFFChunk)); + RIFFChunk &header = *(RIFFChunk*)filestart; + + // Check magic values + if (!CheckFourcc(header.ch.type, "RIFF")) + throw AudioOpenError("File is not a RIFF file"); + if (!CheckFourcc(header.format, "WAVE")) + throw AudioOpenError("File is not a RIFF WAV file"); + + // Count how much more data we can have in the entire file + // The first 4 bytes are already eaten by the header.format field + uint32_t data_left = Endian::LittleToMachine(header.ch.size) - 4; + // How far into the file we have processed. + // Must be incremented by the riff chunk size fields. + uint32_t filepos = sizeof(header); + + bool got_fmt_header = false; + + // Inherited from AudioProvider + num_samples = 0; + + // Continue reading chunks until out of data + while (data_left) { + ChunkHeader &ch = *(ChunkHeader*)EnsureRangeAccessible(filepos, sizeof(ChunkHeader)); + + // Update counters + data_left -= sizeof(ch); + filepos += sizeof(ch); + + if (CheckFourcc(ch.type, "fmt ")) { + if (got_fmt_header) throw AudioOpenError("Invalid file, multiple 'fmt ' chunks"); + got_fmt_header = true; + + fmtChunk &fmt = *(fmtChunk*)EnsureRangeAccessible(filepos, sizeof(fmtChunk)); + + if (Endian::LittleToMachine(fmt.compression) != 1) + throw AudioOpenError("Can't use file, not PCM encoding"); + + // Set stuff inherited from the AudioProvider class + sample_rate = Endian::LittleToMachine(fmt.samplerate); + channels = Endian::LittleToMachine(fmt.channels); + bytes_per_sample = (Endian::LittleToMachine(fmt.significant_bits_sample) + 7) / 8; // round up to nearest whole byte + } + + else if (CheckFourcc(ch.type, "data")) { + // This won't pick up 'data' chunks inside 'wavl' chunks + // since the 'wavl' chunks wrap those. + + if (!got_fmt_header) throw AudioOpenError("Found 'data' chunk before 'fmt ' chunk, file is invalid."); + + int64_t samples = Endian::LittleToMachine(ch.size) / bytes_per_sample; + int64_t frames = samples / channels; + + IndexPoint ip; + ip.start_sample = num_samples; + ip.num_samples = frames; + ip.start_byte = filepos; + index_points.push_back(ip); + + num_samples += frames; + } + + // Support wavl (wave list) chunks too? + + // Update counters + // Make sure they're word aligned + data_left -= (Endian::LittleToMachine(ch.size) + 1) & ~1; + filepos += (Endian::LittleToMachine(ch.size) + 1) & ~1; + } + } + + /// @brief DOCME + /// @return + /// + bool AreSamplesNativeEndian() const + { + // 8 bit samples don't consider endianness + if (bytes_per_sample < 2) return true; + // Otherwise test whether we're little endian + uint32_t testvalue = 0x008800ff; + return testvalue == Endian::LittleToMachine(testvalue); + } +}; + +/// DOCME +static const uint8_t w64GuidRIFF[16] = { + // {66666972-912E-11CF-A5D6-28DB04C10000} + 0x72, 0x69, 0x66, 0x66, 0x2E, 0x91, 0xCF, 0x11, 0xA5, 0xD6, 0x28, 0xDB, 0x04, 0xC1, 0x00, 0x00 +}; + + +/// DOCME +static const uint8_t w64GuidWAVE[16] = { + // {65766177-ACF3-11D3-8CD1-00C04F8EDB8A} + 0x77, 0x61, 0x76, 0x65, 0xF3, 0xAC, 0xD3, 0x11, 0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A +}; + + +/// DOCME +static const uint8_t w64Guidfmt[16] = { + // {20746D66-ACF3-11D3-8CD1-00C04F8EDB8A} + 0x66, 0x6D, 0x74, 0x20, 0xF3, 0xAC, 0xD3, 0x11, 0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A +}; + + +/// DOCME +static const uint8_t w64Guiddata[16] = { + // {61746164-ACF3-11D3-8CD1-00C04F8EDB8A} + 0x64, 0x61, 0x74, 0x61, 0xF3, 0xAC, 0xD3, 0x11, 0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A +}; + + +/// DOCME +/// @class Wave64AudioProvider +/// @brief Sony Wave64 audio provider +/// +/// http://www.vcs.de/fileadmin/user_upload/MBS/PDF/Whitepaper/Informations_about_Sony_Wave64.pdf +class Wave64AudioProvider : public PCMAudioProvider { + // Here's some copy-paste from the FFmpegSource2 code + + /// http://msdn.microsoft.com/en-us/library/dd757720(VS.85).aspx + struct WaveFormatEx { + uint16_t wFormatTag; + uint16_t nChannels; + uint32_t nSamplesPerSec; + uint32_t nAvgBytesPerSec; + uint16_t nBlockAlign; + uint16_t wBitsPerSample; + uint16_t cbSize; + }; + + /// DOCME + struct RiffChunk { + /// DOCME + uint8_t riff_guid[16]; + + /// DOCME + uint64_t file_size; + + /// DOCME + uint8_t format_guid[16]; + }; + + + /// DOCME + struct FormatChunk { + /// DOCME + uint8_t chunk_guid[16]; + + /// DOCME + uint64_t chunk_size; + + /// DOCME + WaveFormatEx format; + + /// DOCME + uint8_t padding[6]; + }; + + + /// DOCME + struct DataChunk { + /// DOCME + uint8_t chunk_guid[16]; + + /// DOCME + uint64_t chunk_size; + }; + + /// @brief DOCME + /// @param guid1 + /// @param guid2 + /// @return + /// + inline bool CheckGuid(const uint8_t *guid1, const uint8_t *guid2) + { + return memcmp(guid1, guid2, 16) == 0; + } + +public: + + /// @brief DOCME + /// @param _filename + /// + Wave64AudioProvider(const wxString &_filename) + : PCMAudioProvider(_filename) + { + filename = _filename; + + int64_t smallest_possible_file = sizeof(RiffChunk) + sizeof(FormatChunk) + sizeof(DataChunk); + + if (file_size < smallest_possible_file) + throw AudioOpenError("File is too small to be a Wave64 file"); + + // Read header + // This should throw an exception if the mapping fails + void *filestart = EnsureRangeAccessible(0, sizeof(RiffChunk)); + assert(filestart); + RiffChunk &header = *(RiffChunk*)filestart; + + // Check magic values + if (!CheckGuid(header.riff_guid, w64GuidRIFF)) + throw AudioOpenError("File is not a Wave64 RIFF file"); + if (!CheckGuid(header.format_guid, w64GuidWAVE)) + throw AudioOpenError("File is not a Wave64 WAVE file"); + + // Count how much more data we can have in the entire file + uint64_t data_left = Endian::LittleToMachine(header.file_size) - sizeof(RiffChunk); + // How far into the file we have processed. + // Must be incremented by the riff chunk size fields. + uint64_t filepos = sizeof(header); + + bool got_fmt_header = false; + + // Inherited from AudioProvider + num_samples = 0; + + // Continue reading chunks until out of data + while (data_left) { + uint8_t *chunk_guid = (uint8_t*)EnsureRangeAccessible(filepos, 16); + uint64_t chunk_size = Endian::LittleToMachine(*(uint64_t*)EnsureRangeAccessible(filepos+16, sizeof(uint64_t))); + + if (CheckGuid(chunk_guid, w64Guidfmt)) { + if (got_fmt_header) + throw AudioOpenError("Bad file, found more than one 'fmt' chunk"); + + FormatChunk &fmt = *(FormatChunk*)EnsureRangeAccessible(filepos, sizeof(FormatChunk)); + got_fmt_header = true; + + if (Endian::LittleToMachine(fmt.format.wFormatTag) == 3) + throw AudioOpenError("File is IEEE 32 bit float format which isn't supported. Bug the developers if this matters."); + if (Endian::LittleToMachine(fmt.format.wFormatTag) != 1) + throw AudioOpenError("Can't use file, not PCM encoding"); + + // Set stuff inherited from the AudioProvider class + sample_rate = Endian::LittleToMachine(fmt.format.nSamplesPerSec); + channels = Endian::LittleToMachine(fmt.format.nChannels); + bytes_per_sample = (Endian::LittleToMachine(fmt.format.wBitsPerSample) + 7) / 8; // round up to nearest whole byte + } + else if (CheckGuid(chunk_guid, w64Guiddata)) { + if (!got_fmt_header) + throw AudioOpenError("Found 'data' chunk before 'fmt ' chunk, file is invalid."); + + int64_t samples = chunk_size / bytes_per_sample; + int64_t frames = samples / channels; + + IndexPoint ip; + ip.start_sample = num_samples; + ip.num_samples = frames; + ip.start_byte = filepos; + index_points.push_back(ip); + + num_samples += frames; + } + + // Update counters + // Make sure they're 64 bit aligned + data_left -= (chunk_size + 7) & ~7; + filepos += (chunk_size + 7) & ~7; + } + } + + /// @brief DOCME + /// @return + /// + bool AreSamplesNativeEndian() const + { + // 8 bit samples don't consider endianness + if (bytes_per_sample < 2) return true; + // Otherwise test whether we're little endian + uint32_t testvalue = 0x008800ff; + return testvalue == Endian::LittleToMachine(testvalue); + } +}; + +/// @brief DOCME +/// @param filename +/// +AudioProvider *CreatePCMAudioProvider(const wxString &filename) +{ + std::string msg; + try { + return new RiffWavPCMAudioProvider(filename); + } + catch (AudioOpenError const& err) { + msg = "RIFF PCM WAV audio provider: " + err.GetMessage(); + } + try { + return new Wave64AudioProvider(filename); + } + catch (AudioOpenError const& err) { + msg += "\nWave64 audio provider: " + err.GetMessage(); + throw AudioOpenError(msg); + } +} diff --git a/aegisub/libmedia/audio/pcm.h b/aegisub/libmedia/audio/pcm.h new file mode 100644 index 000000000..e81c80f65 --- /dev/null +++ b/aegisub/libmedia/audio/pcm.h @@ -0,0 +1,115 @@ +// Copyright (c) 2007-2008, Niels Martin Hansen +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file audio_provider_pcm.h +/// @see audio_provider_pcm.cpp +/// @ingroup audio_input +/// + +#ifndef AGI_PRE +#include + +#include +#include +#endif + +#ifdef _WIN32 +#include +#endif + +#include "include/aegisub/audio_provider.h" + + +/// DOCME +/// @class PCMAudioProvider +/// @brief DOCME +/// +/// DOCME +class PCMAudioProvider : public AudioProvider { +private: +#ifdef _WIN32 + + /// DOCME + HANDLE file_handle; + + /// DOCME + HANDLE file_mapping; + + /// DOCME + mutable void *current_mapping; + + /// DOCME + mutable int64_t mapping_start; + + /// DOCME + mutable size_t mapping_length; +#else + int file_handle; + mutable void *current_mapping; + mutable off_t mapping_start; + mutable size_t mapping_length; +#endif + +protected: + PCMAudioProvider(const wxString &filename); // Create base object and open the file mapping + virtual ~PCMAudioProvider(); // Closes the file mapping + char * EnsureRangeAccessible(int64_t range_start, int64_t range_length) const; // Ensure that the given range of bytes are accessible in the file mapping and return a pointer to the first byte of the requested range + + + /// DOCME + int64_t file_size; // Size of the opened file + + + /// DOCME + struct IndexPoint { + + /// DOCME + int64_t start_byte; + + /// DOCME + int64_t start_sample; + + /// DOCME + int64_t num_samples; + }; + + /// DOCME + typedef std::vector IndexVector; + + /// DOCME + IndexVector index_points; + +public: + virtual void GetAudio(void *buf, int64_t start, int64_t count) const; +}; + +// Construct the right PCM audio provider (if any) for the file +AudioProvider *CreatePCMAudioProvider(const wxString &filename); diff --git a/aegisub/libmedia/cache/audio_hd.cpp b/aegisub/libmedia/cache/audio_hd.cpp new file mode 100644 index 000000000..c88664aec --- /dev/null +++ b/aegisub/libmedia/cache/audio_hd.cpp @@ -0,0 +1,172 @@ +// Copyright (c) 2005-2006, Rodrigo Braz Monteiro, Fredrik Mellbin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file audio_provider_hd.cpp +/// @brief Caching audio provider using a file for backing +/// @ingroup audio_input +/// + +#include "config.h" + +#ifndef AGI_PRE +#include +#include +#endif + +#include "audio_provider_hd.h" +#include "compat.h" +#include "dialog_progress.h" +#include "frame_main.h" +#include "main.h" +#include "standard_paths.h" +#include "utils.h" + +/// @brief Constructor +/// @param source +/// +HDAudioProvider::HDAudioProvider(AudioProvider *src) { + std::auto_ptr source(src); + // Copy parameters + bytes_per_sample = source->GetBytesPerSample(); + num_samples = source->GetNumSamples(); + channels = source->GetChannels(); + sample_rate = source->GetSampleRate(); + filename = source->GetFilename(); + samples_native_endian = source->AreSamplesNativeEndian(); + + // Check free space + wxLongLong freespace; + if (wxGetDiskSpace(DiskCachePath(), NULL, &freespace)) { + if (num_samples * channels * bytes_per_sample > freespace) { + throw AudioOpenError("Not enough free disk space in " + STD_STR(DiskCachePath()) + " to cache the audio"); + } + } + + // Open output file + diskCacheFilename = DiskCacheName(); + file_cache.Create(diskCacheFilename,true,wxS_DEFAULT); + file_cache.Open(diskCacheFilename,wxFile::read_write); + if (!file_cache.IsOpened()) throw AudioOpenError("Unable to write to audio disk cache."); + + // Start progress + volatile bool canceled = false; + DialogProgress *progress = new DialogProgress(AegisubApp::Get()->frame,_T("Load audio"),&canceled,_T("Reading to Hard Disk cache"),0,num_samples); + progress->Show(); + + // Write to disk + int block = 4096; + data = new char[block * channels * bytes_per_sample]; + for (int64_t i=0;i num_samples) block = num_samples - i; + source->GetAudio(data,i,block); + file_cache.Write(data,block * channels * bytes_per_sample); + progress->SetProgress(i,num_samples); + } + file_cache.Seek(0); + + // Finish + if (canceled) { + file_cache.Close(); + delete[] data; + throw agi::UserCancelException("Audio loading cancelled by user"); + } + progress->Destroy(); +} + +/// @brief Destructor +/// +HDAudioProvider::~HDAudioProvider() { + file_cache.Close(); + wxRemoveFile(diskCacheFilename); + delete[] data; +} + +/// @brief Get audio +/// @param buf +/// @param start +/// @param count +/// +void HDAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) const { + // Requested beyond the length of audio + if (start+count > num_samples) { + int64_t oldcount = count; + count = num_samples-start; + if (count < 0) count = 0; + + // Fill beyond with zero + if (bytes_per_sample == 1) { + char *temp = (char *) buf; + for (int i=count;iGetString()); + if (path == _T("default")) return StandardPaths::DecodePath(_T("?temp/")); + + // Specified + return DecodeRelativePath(path,StandardPaths::DecodePath(_T("?user/"))); +} + +/// @brief Get disk cache filename +/// +wxString HDAudioProvider::DiskCacheName() { + // Get pattern + wxString pattern = lagi_wxString(OPT_GET("Audio/Cache/HD/Name")->GetString()); + if (pattern.Find(_T("%02i")) == wxNOT_FOUND) pattern = _T("audio%02i.tmp"); + + // Try from 00 to 99 + for (int i=0;i<100;i++) { + // File exists? + wxString curStringTry = DiskCachePath() + wxString::Format(pattern.c_str(),i); + if (!wxFile::Exists(curStringTry)) return curStringTry; + } + return L""; +} diff --git a/aegisub/libmedia/cache/audio_hd.h b/aegisub/libmedia/cache/audio_hd.h new file mode 100644 index 000000000..f62af2396 --- /dev/null +++ b/aegisub/libmedia/cache/audio_hd.h @@ -0,0 +1,75 @@ +// Copyright (c) 2006, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file audio_provider_hd.h +/// @see audio_provider_hd.cpp +/// @ingroup audio_input +/// + +#ifndef AGI_PRE +#include +#include +#endif + +#include "include/aegisub/audio_provider.h" + +/// DOCME +/// @class HDAudioProvider +/// @brief DOCME +/// +/// DOCME +class HDAudioProvider : public AudioProvider { + /// DOCME + mutable wxMutex diskmutex; + + /// DOCME + mutable wxFile file_cache; + + /// DOCME + wxString diskCacheFilename; + + /// DOCME + bool samples_native_endian; + + /// DOCME + char *data; + + static wxString DiskCachePath(); + static wxString DiskCacheName(); + +public: + HDAudioProvider(AudioProvider *source); + ~HDAudioProvider(); + + bool AreSamplesNativeEndian() const { return samples_native_endian; } + + void GetAudio(void *buf, int64_t start, int64_t count) const; +}; diff --git a/aegisub/libmedia/cache/audio_ram.cpp b/aegisub/libmedia/cache/audio_ram.cpp new file mode 100644 index 000000000..dc2986850 --- /dev/null +++ b/aegisub/libmedia/cache/audio_ram.cpp @@ -0,0 +1,175 @@ +// Copyright (c) 2005-2006, Rodrigo Braz Monteiro, Fredrik Mellbin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file audio_provider_ram.cpp +/// @brief Caching audio provider using heap memory for backing +/// @ingroup audio_input +/// + +#include "config.h" + +#include "audio_provider_ram.h" +#include "dialog_progress.h" +#include "frame_main.h" +#include "main.h" +#include "utils.h" + + +/// DOCME +#define CacheBits ((22)) + +/// DOCME +#define CacheBlockSize ((1 << CacheBits)) + +/// @brief Constructor +/// @param source +/// +RAMAudioProvider::RAMAudioProvider(AudioProvider *src) { + std::auto_ptr source(src); + // Init + blockcache = NULL; + blockcount = 0; + samples_native_endian = source->AreSamplesNativeEndian(); + + // Allocate cache + int64_t ssize = source->GetNumSamples() * source->GetBytesPerSample(); + blockcount = (ssize + CacheBlockSize - 1) >> CacheBits; + blockcache = new char*[blockcount]; + for (int i = 0; i < blockcount; i++) { + blockcache[i] = NULL; + } + + // Allocate cache blocks + try { + for (int i = 0; i < blockcount; i++) { + blockcache[i] = new char[std::min(CacheBlockSize,ssize-i*CacheBlockSize)]; + } + } + catch (...) { + Clear(); + throw AudioOpenError("Couldn't open audio, not enough ram available."); + } + + // Copy parameters + bytes_per_sample = source->GetBytesPerSample(); + num_samples = source->GetNumSamples(); + channels = source->GetChannels(); + sample_rate = source->GetSampleRate(); + filename = source->GetFilename(); + + // Start progress + volatile bool canceled = false; + DialogProgress *progress = new DialogProgress(AegisubApp::Get()->frame,_("Load audio"),&canceled,_("Reading into RAM"),0,source->GetNumSamples()); + progress->Show(); + progress->SetProgress(0,1); + + // Read cache + int readsize = CacheBlockSize / source->GetBytesPerSample(); + for (int i=0;iGetAudio((char*)blockcache[i],i*readsize, i == blockcount-1 ? (source->GetNumSamples() - i*readsize) : readsize); + progress->SetProgress(i,blockcount-1); + } + + // Clean up progress + if (canceled) { + Clear(); + throw agi::UserCancelException("Audio loading cancelled by user"); + } + progress->Destroy(); +} + +/// @brief Destructor +/// +RAMAudioProvider::~RAMAudioProvider() { + Clear(); +} + +/// @brief Clear +/// +void RAMAudioProvider::Clear() { + // Free ram cache + if (blockcache) { + for (int i = 0; i < blockcount; i++) { + delete [] blockcache[i]; + } + delete [] blockcache; + } + blockcache = NULL; + blockcount = 0; +} + +/// @brief Get audio +/// @param buf +/// @param start +/// @param count +/// +void RAMAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) const { + // Requested beyond the length of audio + if (start+count > num_samples) { + int64_t oldcount = count; + count = num_samples-start; + if (count < 0) count = 0; + + // Fill beyond with zero + if (bytes_per_sample == 1) { + char *temp = (char *) buf; + for (int i=count;i> CacheBits; + int start_offset = (start*bytes_per_sample) & (CacheBlockSize-1); + int64_t bytesremaining = count*bytes_per_sample; + + // Copy + while (bytesremaining) { + int readsize = std::min(bytesremaining, CacheBlockSize - start_offset); + + memcpy(charbuf,(char *)(blockcache[i++]+start_offset),readsize); + + charbuf+=readsize; + + start_offset=0; + bytesremaining-=readsize; + } + } +} diff --git a/aegisub/libmedia/cache/audio_ram.h b/aegisub/libmedia/cache/audio_ram.h new file mode 100644 index 000000000..efba843ae --- /dev/null +++ b/aegisub/libmedia/cache/audio_ram.h @@ -0,0 +1,62 @@ +// Copyright (c) 2006, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file audio_provider_ram.h +/// @see audio_provider_ram.cpp +/// @ingroup audio_input +/// + +#include "include/aegisub/audio_provider.h" + +/// DOCME +/// @class RAMAudioProvider +/// @brief DOCME +/// +/// DOCME +class RAMAudioProvider : public AudioProvider { + /// DOCME + char** blockcache; + + /// DOCME + int blockcount; + + /// DOCME + bool samples_native_endian; + + void Clear(); + +public: + RAMAudioProvider(AudioProvider *source); + ~RAMAudioProvider(); + + bool AreSamplesNativeEndian() const { return samples_native_endian; } + void GetAudio(void *buf, int64_t start, int64_t count) const; +}; diff --git a/aegisub/libmedia/cache/video_cache.cpp b/aegisub/libmedia/cache/video_cache.cpp new file mode 100644 index 000000000..ab7749b74 --- /dev/null +++ b/aegisub/libmedia/cache/video_cache.cpp @@ -0,0 +1,125 @@ +// Copyright (c) 2008, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file video_provider_cache.cpp +/// @brief Aggregate video provider caching previously requested frames +/// @ingroup video_input +/// + +#include "config.h" +#include "main.h" + +#include "video_provider_cache.h" + +/// DOCME +/// @class CachedFrame +/// @brief DOCME +/// +/// DOCME +struct CachedFrame { + /// DOCME + AegiVideoFrame frame; + + /// DOCME + int n; +}; + +/// @brief Constructor +/// @param parent +/// +VideoProviderCache::VideoProviderCache(VideoProvider *parent) +: master(parent) +, cacheMax(OPT_GET("Provider/Video/Cache/Size")->GetInt() << 20) // convert MB to bytes +{ +} + +/// @brief Destructor +/// +VideoProviderCache::~VideoProviderCache() { + while (cache.size()) { + cache.front().frame.Clear(); + cache.pop_front(); + } +} + +/// @brief Get frame +/// @param n +/// @return +/// +const AegiVideoFrame VideoProviderCache::GetFrame(int n) { + // See if frame is cached + CachedFrame cached; + for (std::list::iterator cur=cache.begin();cur!=cache.end();cur++) { + cached = *cur; + if (cached.n == n) { + cache.erase(cur); + cache.push_back(cached); + return cached.frame; + } + } + + // Not cached, retrieve it + const AegiVideoFrame frame = master->GetFrame(n); + const AegiVideoFrame *srcFrame = &frame; + + // Cache frame + Cache(n,*srcFrame); + return *srcFrame; +} + +/// @brief Add to cache +/// @param n +/// @param frame +void VideoProviderCache::Cache(int n,const AegiVideoFrame frame) { + // Cache full, use frame at front + if (GetCurCacheSize() >= cacheMax) { + cache.push_back(cache.front()); + cache.pop_front(); + } + + // Cache not full, insert new one + else { + cache.push_back(CachedFrame()); + } + + // Cache + cache.back().n = n; + cache.back().frame.CopyFrom(frame); +} + +/// @brief Get the current size of the cache +/// @return Returns the size in bytes +unsigned VideoProviderCache::GetCurCacheSize() { + int sz = 0; + for (std::list::iterator i = cache.begin(); i != cache.end(); i++) + sz += i->frame.memSize; + return sz; +} diff --git a/aegisub/libmedia/cache/video_cache.h b/aegisub/libmedia/cache/video_cache.h new file mode 100644 index 000000000..98962cd33 --- /dev/null +++ b/aegisub/libmedia/cache/video_cache.h @@ -0,0 +1,82 @@ +// Copyright (c) 2008, Rodrigo Braz Monteiro, Fredrik Mellbin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file video_provider_cache.h +/// @see video_provider_cache.cpp +/// @ingroup video_input +/// + +#ifndef AGI_PRE +#include +#include +#endif + +#include "include/aegisub/video_provider.h" + +struct CachedFrame; + +/// DOCME +/// @class VideoProviderCache +/// @brief DOCME +/// +/// DOCME +class VideoProviderCache : public VideoProvider { + /// DOCME + std::auto_ptr master; + + /// DOCME + unsigned int cacheMax; + + /// DOCME + std::list cache; + + void Cache(int n,const AegiVideoFrame frame); + AegiVideoFrame GetCachedFrame(int n); + + // Cache functions + unsigned GetCurCacheSize(); + +public: + // Base methods + const AegiVideoFrame GetFrame(int n); + VideoProviderCache(VideoProvider *master); + virtual ~VideoProviderCache(); + + // Override the following methods: + virtual int GetPosition() const { return master->GetPosition(); } + virtual int GetFrameCount() const { return master->GetFrameCount(); } + virtual int GetWidth() const { return master->GetWidth(); } + virtual int GetHeight() const { return master->GetHeight(); } + virtual agi::vfr::Framerate GetFPS() const { return master->GetFPS(); } + virtual std::vector GetKeyFrames() const { return master->GetKeyFrames(); } + virtual wxString GetWarning() const { return master->GetWarning(); } + virtual wxString GetDecoderName() const { return master->GetDecoderName(); } +}; diff --git a/aegisub/libmedia/common/audio_manager.cpp b/aegisub/libmedia/common/audio_manager.cpp new file mode 100644 index 000000000..4c230293c --- /dev/null +++ b/aegisub/libmedia/common/audio_manager.cpp @@ -0,0 +1,184 @@ +// Copyright (c) 2005-2006, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file audio_provider.cpp +/// @brief Baseclass for audio providers +/// @ingroup audio_input +/// + + +#include "config.h" + +#ifndef AGI_PRE +#include +#endif + +#ifdef WITH_AVISYNTH +#include "audio_provider_avs.h" +#endif +#include "audio_provider_convert.h" +#ifdef WITH_FFMPEGSOURCE +#include "audio_provider_ffmpegsource.h" +#endif +#include "audio_provider_hd.h" +#include "audio_provider_pcm.h" +#include "audio_provider_ram.h" +#include "compat.h" +#include "main.h" + +/// @brief Constructor +/// +AudioProvider::AudioProvider() : raw(NULL) { +} + +/// @brief Destructor +/// +AudioProvider::~AudioProvider() { + delete[] raw; +} + +/// @brief Get audio with volume +/// @param buf +/// @param start +/// @param count +/// @param volume +/// @return +/// +void AudioProvider::GetAudioWithVolume(void *buf, int64_t start, int64_t count, double volume) const { + try { + GetAudio(buf,start,count); + } + catch (...) { + // FIXME: Poor error handling though better than none, to patch issue #800. + // Just return blank audio if real provider fails. + memset(buf, 0, count*bytes_per_sample); + return; + } + + if (volume == 1.0) return; + + if (bytes_per_sample == 2) { + // Read raw samples + short *buffer = (short*) buf; + int value; + + // Modify + for (int64_t i=0;i 0x7FFF) value = 0x7FFF; + buffer[i] = value; + } + } +} + +/// @brief Get provider +/// @param filename +/// @param cache +/// @return +/// +AudioProvider *AudioProviderFactory::GetProvider(wxString filename, int cache) { + AudioProvider *provider = NULL; + bool found = false; + std::string msg; + + if (!OPT_GET("Provider/Audio/PCM/Disable")->GetBool()) { + // Try a PCM provider first + try { + provider = CreatePCMAudioProvider(filename); + } + catch (agi::FileNotFoundError const& err) { + msg = "PCM audio provider: " + err.GetMessage() + " not found.\n"; + } + catch (AudioOpenError const& err) { + found = true; + msg += err.GetMessage(); + } + } + if (!provider) { + std::vector list = GetClasses(OPT_GET("Audio/Provider")->GetString()); + if (list.empty()) throw AudioOpenError("No audio providers are available."); + + for (unsigned int i=0;iNeedsCache(); + + // Give it a converter if needed + if (provider->GetBytesPerSample() != 2 || provider->GetSampleRate() < 32000 || provider->GetChannels() != 1) + provider = CreateConvertAudioProvider(provider); + + // Change provider to RAM/HD cache if needed + if (cache == -1) cache = OPT_GET("Audio/Cache/Type")->GetInt(); + if (!cache || !needsCache) { + return provider; + } + + // Convert to RAM + if (cache == 1) return new RAMAudioProvider(provider); + + // Convert to HD + if (cache == 2) return new HDAudioProvider(provider); + + throw AudioOpenError("Unknown caching method"); +} + +/// @brief Register all providers +/// +void AudioProviderFactory::RegisterProviders() { +#ifdef WITH_AVISYNTH + Register("Avisynth"); +#endif +#ifdef WITH_FFMPEGSOURCE + Register("FFmpegSource"); +#endif +} + +template<> AudioProviderFactory::map *FactoryBase::classes = NULL; diff --git a/aegisub/libmedia/common/subtitle.cpp b/aegisub/libmedia/common/subtitle.cpp new file mode 100644 index 000000000..11ba3d8d7 --- /dev/null +++ b/aegisub/libmedia/common/subtitle.cpp @@ -0,0 +1,90 @@ +// Copyright (c) 2007, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file subtitles_provider.cpp +/// @brief Base class for subtitle renderers +/// @ingroup subtitle_rendering +/// + +#include "config.h" + +#include "compat.h" +#include "main.h" +#ifdef WITH_CSRI +#include "subtitles_provider_csri.h" +#endif +#ifdef WITH_LIBASS +#include "subtitles_provider_libass.h" +#endif +#if !defined(WITH_CSRI) && !defined(WITH_LIBASS) +#include "include/aegisub/subtitles_provider.h" +#endif + +/// @brief Get provider +/// @return +/// +SubtitlesProvider* SubtitlesProviderFactory::GetProvider() { + std::vector list = GetClasses(OPT_GET("Subtitle/Provider")->GetString()); + if (list.empty()) throw _T("No subtitle providers are available."); + + // Get provider + wxString error; + for (unsigned int i=0;i("CSRI", false, CSRISubtitlesProvider::GetSubTypes()); +#endif +#ifdef WITH_LIBASS + Register("libass"); + LibassSubtitlesProvider::CacheFonts(); +#endif +} + +template<> SubtitlesProviderFactory::map *FactoryBase::classes = NULL; diff --git a/aegisub/libmedia/common/video_manager.cpp b/aegisub/libmedia/common/video_manager.cpp new file mode 100644 index 000000000..04e644bcb --- /dev/null +++ b/aegisub/libmedia/common/video_manager.cpp @@ -0,0 +1,123 @@ +// Copyright (c) 2006, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file video_provider_manager.cpp +/// @brief Keep track of installed video providers +/// @ingroup video_input +/// + +#include "config.h" + +#include + +#include "compat.h" +#include "main.h" + +#ifdef WITH_AVISYNTH +#include "video_provider_avs.h" +#endif +#include "video_provider_cache.h" +#include "video_provider_dummy.h" +#ifdef WITH_FFMPEGSOURCE +#include "video_provider_ffmpegsource.h" +#endif +#include "video_provider_manager.h" +#include "video_provider_yuv4mpeg.h" + + +/// @brief Get provider +/// @param video +/// @return +/// +VideoProvider *VideoProviderFactory::GetProvider(wxString video) { + std::vector list = GetClasses(OPT_GET("Video/Provider")->GetString()); + if (video.StartsWith("?dummy")) list.insert(list.begin(), "Dummy"); + list.insert(list.begin(), "YUV4MPEG"); + + bool fileFound = false; + bool fileSupported = false; + std::string errors; + errors.reserve(1024); + for (int i = 0; i < (signed)list.size(); ++i) { + std::string err; + try { + VideoProvider *provider = Create(list[i], video); + LOG_I("manager/video/provider") << list[i] << ": opened " << STD_STR(video); + if (provider->WantsCaching()) { + return new VideoProviderCache(provider); + } + return provider; + } + catch (agi::FileNotFoundError const&) { + err = list[i] + ": file not found."; + // Keep trying other providers as this one may just not be able to + // open a valid path + } + catch (VideoNotSupported const&) { + fileFound = true; + err = list[i] + ": video is not in a supported format."; + } + catch (VideoOpenError const& ex) { + fileSupported = true; + err = list[i] + ": " + ex.GetMessage(); + } + catch (agi::vfr::Error const& ex) { + fileSupported = true; + err = list[i] + ": " + ex.GetMessage(); + } + errors += err; + errors += "\n"; + LOG_D("manager/video/provider") << err; + } + + // No provider could open the file + LOG_E("manager/video/provider") << "Could not open " << STD_STR(video); + std::string msg = "Could not open " + STD_STR(video) + ":\n" + errors; + + if (!fileFound) throw agi::FileNotFoundError(STD_STR(video)); + if (!fileSupported) throw VideoNotSupported(msg); + throw VideoOpenError(msg); +} + +/// @brief Register all providers +/// +void VideoProviderFactory::RegisterProviders() { +#ifdef WITH_AVISYNTH + Register("Avisynth"); +#endif +#ifdef WITH_FFMPEGSOURCE + Register("FFmpegSource"); +#endif + Register("Dummy", true); + Register("YUV4MPEG", true); +} + +template<> VideoProviderFactory::map *FactoryBase::classes = NULL; diff --git a/aegisub/libmedia/common/video_manager.h b/aegisub/libmedia/common/video_manager.h new file mode 100644 index 000000000..774856e73 --- /dev/null +++ b/aegisub/libmedia/common/video_manager.h @@ -0,0 +1,44 @@ +// Copyright (c) 2006-2008, Rodrigo Braz Monteiro, Fredrik Mellbin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file video_provider_manager.h +/// @see video_provider_manager.cpp +/// @ingroup video_input +/// + +#include "factory_manager.h" +#include "include/aegisub/video_provider.h" + +class VideoProviderFactory : public Factory1 { +public: + static VideoProvider *GetProvider(wxString video); + static void RegisterProviders(); +}; diff --git a/aegisub/libmedia/subtitle/csri.cpp b/aegisub/libmedia/subtitle/csri.cpp new file mode 100644 index 000000000..47a0482ce --- /dev/null +++ b/aegisub/libmedia/subtitle/csri.cpp @@ -0,0 +1,147 @@ +// Copyright (c) 2007, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file subtitles_provider_csri.cpp +/// @brief Wrapper for CSRI-based subtitle renderers +/// @ingroup subtitle_rendering +/// + +#include "config.h" + +#ifdef WITH_CSRI + +#include "ass_file.h" +#include "subtitles_provider_csri.h" +#include "text_file_writer.h" +#include "video_context.h" +#include "video_frame.h" + +/// @brief Constructor +/// @param type +/// +CSRISubtitlesProvider::CSRISubtitlesProvider(std::string type) : subType(type) { +} + +/// @brief Destructor +/// +CSRISubtitlesProvider::~CSRISubtitlesProvider() { + if (!tempfile.empty()) wxRemoveFile(tempfile); +} + +/// @brief Load subtitles +/// @param subs +/// +void CSRISubtitlesProvider::LoadSubtitles(AssFile *subs) { + // CSRI variables + csri_rend *cur,*renderer=NULL; + + // Select renderer + bool canOpenMem = true; + for (cur = csri_renderer_default();cur;cur=csri_renderer_next(cur)) { + std::string name(csri_renderer_info(cur)->name); + if (name == subType) { + renderer = cur; + if (name.find("vsfilter") != name.npos) canOpenMem = false; + break; + } + } + + // Matching renderer not found, fallback to default + if (!renderer) { + renderer = csri_renderer_default(); + if (!renderer) { + throw _T("No CSRI renderer available, cannot show subtitles. Try installing one or switch to another subtitle provider."); + } + } + + // Open from memory + if (canOpenMem) { + std::vector data; + subs->SaveMemory(data,wxSTRING_ENCODING); + instance.reset(csri_open_mem(renderer,&data[0],data.size(),NULL), &csri_close); + } + + // Open from disk + else { + if (tempfile.empty()) { + tempfile = wxFileName::CreateTempFileName(_T("aegisub")); + wxRemoveFile(tempfile); + tempfile += L".ass"; + } + subs->Save(tempfile,false,false,wxSTRING_ENCODING); + instance.reset(csri_open_file(renderer,tempfile.utf8_str(),NULL), &csri_close); + } +} + +/// @brief Draw subtitles +/// @param dst +/// @param time +/// @return +/// +void CSRISubtitlesProvider::DrawSubtitles(AegiVideoFrame &dst,double time) { + // Check if CSRI loaded properly + if (!instance.get()) return; + + // Load data into frame + csri_frame frame; + if (dst.flipped) { + frame.planes[0] = dst.data + (dst.h-1) * dst.pitch; + frame.strides[0] = -(signed)dst.pitch; + } + else { + frame.planes[0] = dst.data; + frame.strides[0] = dst.pitch; + } + frame.pixfmt = CSRI_F_BGR_; + + // Set format + csri_fmt format; + format.width = dst.w; + format.height = dst.h; + format.pixfmt = frame.pixfmt; + int error = csri_request_fmt(instance.get(),&format); + if (error) return; + + // Render + csri_render(instance.get(),&frame,time); +} + +/// @brief Get CSRI subtypes +/// +std::vector CSRISubtitlesProvider::GetSubTypes() { + std::vector final; + for (csri_rend *cur = csri_renderer_default();cur;cur = csri_renderer_next(cur)) { + final.push_back(csri_renderer_info(cur)->name); + } + return final; +} + +#endif // WITH_CSRI diff --git a/aegisub/libmedia/subtitle/csri.h b/aegisub/libmedia/subtitle/csri.h new file mode 100644 index 000000000..8800d5046 --- /dev/null +++ b/aegisub/libmedia/subtitle/csri.h @@ -0,0 +1,74 @@ +// Copyright (c) 2007, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file subtitles_provider_csri.h +/// @see subtitles_provider_csri.cpp +/// @ingroup subtitle_rendering +/// + +#ifdef WITH_CSRI + +#ifndef AGI_PRE +#include +#include +#endif + +#include "include/aegisub/subtitles_provider.h" +#ifdef WIN32 +#define CSRIAPI +#include "../../contrib/csri/include/csri/csri.h" +#else +#include +#endif + +/// DOCME +/// @class CSRISubtitlesProvider +/// @brief DOCME +/// +/// DOCME +class CSRISubtitlesProvider : public SubtitlesProvider { + /// DOCME + std::string subType; + + /// DOCME + std::tr1::shared_ptr instance; + + wxString tempfile; +public: + CSRISubtitlesProvider(std::string subType); + ~CSRISubtitlesProvider(); + + void LoadSubtitles(AssFile *subs); + void DrawSubtitles(AegiVideoFrame &dst,double time); + + static std::vector GetSubTypes(); +}; +#endif diff --git a/aegisub/libmedia/subtitle/libass.cpp b/aegisub/libmedia/subtitle/libass.cpp new file mode 100644 index 000000000..c01208b25 --- /dev/null +++ b/aegisub/libmedia/subtitle/libass.cpp @@ -0,0 +1,252 @@ +// Copyright (c) 2006-2007, Rodrigo Braz Monteiro, Evgeniy Stepanov +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file subtitles_provider_libass.cpp +/// @brief libass-based subtitle renderer +/// @ingroup subtitle_rendering +/// + +#include "config.h" + +#ifdef WITH_LIBASS + +#ifndef AGI_PRE +#include +#include +#endif + +#ifdef __APPLE__ +#include +#include +#endif + +#include + +#include "ass_file.h" +#include "dialog_progress.h" +#include "frame_main.h" +#include "main.h" +#include "standard_paths.h" +#include "subtitles_provider_libass.h" +#include "utils.h" +#include "video_context.h" +#include "video_frame.h" + + +/// @brief Handle libass messages +/// +static void msg_callback(int level, const char *fmt, va_list args, void *) { + if (level >= 7) return; + char buf[1024]; +#ifdef _WIN32 + vsprintf_s(buf, sizeof(buf), fmt, args); +#else + vsnprintf(buf, sizeof(buf), fmt, args); +#endif + + if (level < 2) // warning/error + LOG_I("subtitle/provider/libass") << buf; + else // verbose + LOG_D("subtitle/provider/libass") << buf; +} + +class FontConfigCacheThread : public wxThread { + ASS_Library *ass_library; + ASS_Renderer *ass_renderer; + FontConfigCacheThread** thisPtr; + ExitCode Entry() { +#ifdef __APPLE__ + char config_path[MAXPATHLEN]; + char *config_dir; + + config_dir = agi::util::OSX_GetBundleResourcesDirectory(); + snprintf(config_path, MAXPATHLEN, "%s/etc/fonts/fonts.conf", config_dir); + free(config_dir); +#else + const char *config_path = NULL; +#endif + + if (ass_library) ass_renderer = ass_renderer_init(ass_library); + ass_set_fonts(ass_renderer, NULL, "Sans", 1, config_path, true); + if (ass_library) ass_renderer_done(ass_renderer); + *thisPtr = NULL; + return EXIT_SUCCESS; + } +public: + FontConfigCacheThread(ASS_Library *ass_library, FontConfigCacheThread **thisPtr) + : ass_library(ass_library) + , ass_renderer(NULL) + , thisPtr(thisPtr) + { + *thisPtr = this; + Create(); + Run(); + } + FontConfigCacheThread(ASS_Renderer *ass_renderer, FontConfigCacheThread **thisPtr) + : ass_library(NULL) + , ass_renderer(ass_renderer) + , thisPtr(thisPtr) + { + *thisPtr = this; + Create(); + Run(); + } +}; + +static void wait_for_cache_thread(FontConfigCacheThread const * const * const cache_worker) { + if (!*cache_worker) return; + + bool canceled; + DialogProgress *progress = new DialogProgress(AegisubApp::Get()->frame, L"", &canceled, L"Caching fonts", 0, 1); + progress->Show(); + while (*cache_worker) { + if (canceled) throw agi::UserCancelException("Font caching cancelled"); + progress->Pulse(); + wxYield(); + wxMilliSleep(100); + } + progress->Destroy(); +} + +/// @brief Constructor +/// +LibassSubtitlesProvider::LibassSubtitlesProvider(std::string) { + wait_for_cache_thread(&cache_worker); + + // Initialize renderer + ass_track = NULL; + ass_renderer = ass_renderer_init(ass_library); + if (!ass_renderer) throw _T("ass_renderer_init failed"); + ass_set_font_scale(ass_renderer, 1.); + new FontConfigCacheThread(ass_renderer, &cache_worker); + wait_for_cache_thread(&cache_worker); +} + +/// @brief Destructor +/// +LibassSubtitlesProvider::~LibassSubtitlesProvider() { + if (ass_track) ass_free_track(ass_track); + if (ass_renderer) ass_renderer_done(ass_renderer); +} + +/// @brief Load subtitles +/// @param subs +/// +void LibassSubtitlesProvider::LoadSubtitles(AssFile *subs) { + // Prepare subtitles + std::vector data; + subs->SaveMemory(data,_T("UTF-8")); + + // Load file + if (ass_track) ass_free_track(ass_track); + ass_track = ass_read_memory(ass_library, &data[0], data.size(),(char *)"UTF-8"); + if (!ass_track) throw _T("libass failed to load subtitles."); +} + +/// DOCME +#define _r(c) ((c)>>24) + +/// DOCME +#define _g(c) (((c)>>16)&0xFF) + +/// DOCME +#define _b(c) (((c)>>8)&0xFF) + +/// DOCME +#define _a(c) ((c)&0xFF) + + +/// @brief Draw subtitles +/// @param frame +/// @param time +/// @return +/// +void LibassSubtitlesProvider::DrawSubtitles(AegiVideoFrame &frame,double time) { + // Set size + ass_set_frame_size(ass_renderer, frame.w, frame.h); + + // Get frame + ASS_Image* img = ass_render_frame(ass_renderer, ass_track, int(time * 1000), NULL); + + // libass actually returns several alpha-masked monochrome images. + // Here, we loop through their linked list, get the colour of the current, and blend into the frame. + // This is repeated for all of them. + while (img) { + // Get colours + unsigned int opacity = 255 - ((unsigned int)_a(img->color)); + unsigned int r = (unsigned int)_r(img->color); + unsigned int g = (unsigned int)_g(img->color); + unsigned int b = (unsigned int) _b(img->color); + + // Prepare copy + int src_stride = img->stride; + int dst_stride = frame.pitch; + int dst_delta = dst_stride - img->w*4; + //int stride = std::min(src_stride,dst_stride); + const unsigned char *src = img->bitmap; + unsigned char *dst = frame.data + (img->dst_y * dst_stride + img->dst_x * 4); + unsigned int k,ck,t; + + // Copy image to destination frame + for (int y=0;yh;y++) { + //memcpy(dst,src,stride); + for (int x = 0; x < img->w; ++x) { + k = ((unsigned)src[x]) * opacity / 255; + ck = 255 - k; + t = *dst; + *dst++ = (k*b + ck*t) / 255; + t = *dst; + *dst++ = (k*g + ck*t) / 255; + t = *dst; + *dst++ = (k*r + ck*t) / 255; + dst++; + } + + dst += dst_delta; + src += src_stride; + } + + // Next image + img = img->next; + } +} + +void LibassSubtitlesProvider::CacheFonts() { + ass_library = ass_library_init(); + ass_set_message_cb(ass_library, msg_callback, NULL); + new FontConfigCacheThread(ass_library, &cache_worker); +} + +/// DOCME +ASS_Library* LibassSubtitlesProvider::ass_library; +FontConfigCacheThread* LibassSubtitlesProvider::cache_worker = NULL; + +#endif // WITH_LIBASS diff --git a/aegisub/libmedia/subtitle/libass.h b/aegisub/libmedia/subtitle/libass.h new file mode 100644 index 000000000..d093bea59 --- /dev/null +++ b/aegisub/libmedia/subtitle/libass.h @@ -0,0 +1,76 @@ +// Copyright (c) 2006-2007, Rodrigo Braz Monteiro, Evgeniy Stepanov +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file subtitles_provider_libass.h +/// @see subtitles_provider_libass.cpp +/// @ingroup subtitle_rendering +/// + +#ifdef WITH_LIBASS + +#include "include/aegisub/subtitles_provider.h" +extern "C" { +#ifdef __VISUALC__ +#include "stdint.h" +#endif + +#include "../libass/ass.h" +} + +class FontConfigCacheThread; + +/// DOCME +/// @class LibassSubtitlesProvider +/// @brief DOCME +/// +/// DOCME +class LibassSubtitlesProvider : public SubtitlesProvider { + /// DOCME + static ASS_Library* ass_library; + + /// DOCME + ASS_Renderer* ass_renderer; + + /// DOCME + ASS_Track* ass_track; + + static FontConfigCacheThread *cache_worker; + +public: + LibassSubtitlesProvider(std::string); + ~LibassSubtitlesProvider(); + + void LoadSubtitles(AssFile *subs); + void DrawSubtitles(AegiVideoFrame &dst,double time); + + static void CacheFonts(); +}; +#endif diff --git a/aegisub/libmedia/video/avs_video.cpp b/aegisub/libmedia/video/avs_video.cpp new file mode 100644 index 000000000..7d35288ee --- /dev/null +++ b/aegisub/libmedia/video/avs_video.cpp @@ -0,0 +1,291 @@ +// Copyright (c) 2006, Fredrik Mellbin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file video_provider_avs.cpp +/// @brief Avisynth-based video provider +/// @ingroup video_input +/// + +#include "config.h" + +#ifdef WITH_AVISYNTH +#ifndef AGI_PRE +#include +#include +#endif + +#include "charset_conv.h" +#include "compat.h" +#include "gl_wrap.h" +#include +#include "mkv_wrap.h" +#include "standard_paths.h" +#include "vfw_wrap.h" +#include "video_context.h" +#include "video_provider_avs.h" + +/// @brief Constructor +/// @param _filename +/// +AvisynthVideoProvider::AvisynthVideoProvider(wxString filename) try +: usedDirectShow(false) +, decoderName(_("Unknown")) +, num_frames(0) +, last_fnum(-1) +, RGB32Video(NULL) +{ + RGB32Video = OpenVideo(filename); + + vi = RGB32Video->GetVideoInfo(); +} +catch (AvisynthError const& err) { + throw VideoOpenError("Avisynth error: " + std::string(err.msg)); +} + +/// @brief Destructor +AvisynthVideoProvider::~AvisynthVideoProvider() { + iframe.Clear(); +} + +AVSValue AvisynthVideoProvider::Open(wxFileName const& fname, wxString const& extension) { + char *videoFilename = env->SaveString(fname.GetShortPath().mb_str(csConvLocal)); + + // Avisynth file, just import it + if (extension == L".avs") { + LOG_I("avisynth/video") << "Opening .avs file with Import"; + decoderName = L"Import"; + return env->Invoke("Import", videoFilename); + } + + // Open avi file with AviSource + if (extension == L".avi") { + LOG_I("avisynth/video") << "Opening .avi file with AviSource"; + try { + const char *argnames[2] = { 0, "audio" }; + AVSValue args[2] = { videoFilename, false }; + decoderName = L"AviSource"; + return env->Invoke("AviSource", AVSValue(args,2), argnames); + } + + // On Failure, fallback to DSS + catch (AvisynthError &) { + LOG_I("avisynth/video") << "Failed to open .avi file with AviSource, switching to DirectShowSource"; + } + } + + // Open d2v with mpeg2dec3 + if (extension == L".d2v" && env->FunctionExists("Mpeg2Dec3_Mpeg2Source")) { + LOG_I("avisynth/video") << "Opening .d2v file with Mpeg2Dec3_Mpeg2Source"; + AVSValue script = env->Invoke("Mpeg2Dec3_Mpeg2Source", videoFilename); + decoderName = L"Mpeg2Dec3_Mpeg2Source"; + + //if avisynth is 2.5.7 beta 2 or newer old mpeg2decs will crash without this + if (env->FunctionExists("SetPlanarLegacyAlignment")) { + AVSValue args[2] = { script, true }; + script = env->Invoke("SetPlanarLegacyAlignment", AVSValue(args,2)); + } + return script; + } + + // If that fails, try opening it with DGDecode + if (extension == L".d2v" && env->FunctionExists("DGDecode_Mpeg2Source")) { + LOG_I("avisynth/video") << "Opening .d2v file with DGDecode_Mpeg2Source"; + decoderName = L"DGDecode_Mpeg2Source"; + return env->Invoke("Mpeg2Source", videoFilename); + + //note that DGDecode will also have issues like if the version is too ancient but no sane person + //would use that anyway + } + + if (extension == L".d2v" && env->FunctionExists("Mpeg2Source")) { + LOG_I("avisynth/video") << "Opening .d2v file with other Mpeg2Source"; + AVSValue script = env->Invoke("Mpeg2Source", videoFilename); + decoderName = L"Mpeg2Source"; + + //if avisynth is 2.5.7 beta 2 or newer old mpeg2decs will crash without this + if (env->FunctionExists("SetPlanarLegacyAlignment")) + script = env->Invoke("SetPlanarLegacyAlignment", script); + + return script; + } + + // Try loading DirectShowSource2 + if (!env->FunctionExists("dss2")) { + wxFileName dss2path(StandardPaths::DecodePath(_T("?data/avss.dll"))); + if (dss2path.FileExists()) { + env->Invoke("LoadPlugin",env->SaveString(dss2path.GetFullPath().mb_str(csConvLocal))); + } + } + + // If DSS2 loaded properly, try using it + if (env->FunctionExists("dss2")) { + LOG_I("avisynth/video") << "Opening file with DSS2"; + decoderName = L"DSS2"; + return env->Invoke("DSS2", videoFilename); + } + + // Try DirectShowSource + // Load DirectShowSource.dll from app dir if it exists + wxFileName dsspath(StandardPaths::DecodePath(_T("?data/DirectShowSource.dll"))); + if (dsspath.FileExists()) { + env->Invoke("LoadPlugin",env->SaveString(dsspath.GetFullPath().mb_str(csConvLocal))); + } + + // Then try using DSS + if (env->FunctionExists("DirectShowSource")) { + const char *argnames[3] = { 0, "video", "audio" }; + AVSValue args[3] = { videoFilename, true, false }; + usedDirectShow = true; + decoderName = L"DirectShowSource"; + LOG_I("avisynth/video") << "Opening file with DirectShowSource"; + return env->Invoke("DirectShowSource", AVSValue(args,3), argnames); + } + + // Failed to find a suitable function + LOG_E("avisynth/video") << "DSS function not found"; + throw VideoNotSupported("No function suitable for opening the video found"); +} + +/// @brief Actually open the video into Avisynth +/// @param _filename +/// @return +/// +PClip AvisynthVideoProvider::OpenVideo(wxString filename) { + wxMutexLocker lock(AviSynthMutex); + + wxFileName fname(filename); + if (!fname.FileExists()) + throw agi::FileNotFoundError(STD_STR(filename)); + + AVSValue script; + wxString extension = filename.Right(4).Lower(); + try { + script = Open(fname, extension); + } + catch (AvisynthError const& err) { + throw VideoOpenError("Avisynth error: " + std::string(err.msg)); + } + + // Check if video was loaded properly + if (!script.IsClip() || !script.AsClip()->GetVideoInfo().HasVideo()) { + throw VideoNotSupported("No usable video found"); + } + + // Read keyframes and timecodes from MKV file + bool mkvOpen = MatroskaWrapper::wrapper.IsOpen(); + KeyFrames.clear(); + if (extension == L".mkv" || mkvOpen) { + // Parse mkv + if (!mkvOpen) MatroskaWrapper::wrapper.Open(filename); + + // Get keyframes + KeyFrames = MatroskaWrapper::wrapper.GetKeyFrames(); + + MatroskaWrapper::wrapper.SetToTimecodes(vfr_fps); + + // Close mkv + MatroskaWrapper::wrapper.Close(); + } +// check if we have windows, if so we can load keyframes from AVI files using VFW +#ifdef __WINDOWS__ + else if (extension == L".avi") { + KeyFrames.clear(); + KeyFrames = VFWWrapper::GetKeyFrames(filename); + } +#endif /* __WINDOWS__ */ + + // Check if the file is all keyframes + bool isAllKeyFrames = true; + for (unsigned int i=1; iInvoke("ConvertToRGB32", script); + + // Cache + return (env->Invoke("Cache", script)).AsClip(); +} + +/// @brief Actually get a frame +/// @param _n +/// @return +/// +const AegiVideoFrame AvisynthVideoProvider::GetFrame(int n) { + if (vfr_fps.IsLoaded()) { + n = real_fps.FrameAtTime(vfr_fps.TimeAtFrame(n)); + } + // Get avs frame + wxMutexLocker lock(AviSynthMutex); + PVideoFrame frame = RGB32Video->GetFrame(n,env); + int Bpp = vi.BitsPerPixel() / 8; + + // Aegisub's video frame + AegiVideoFrame &final = iframe; + final.flipped = true; + final.invertChannels = true; + + // Set size properties + final.pitch = frame->GetPitch(); + final.w = frame->GetRowSize() / Bpp; + final.h = frame->GetHeight(); + + // Allocate + final.Allocate(); + + // Copy + memcpy(final.data,frame->GetReadPtr(),final.pitch * final.h); + + // Set last number + last_fnum = n; + return final; +} + +/// @brief Get warning +/// +wxString AvisynthVideoProvider::GetWarning() const { + if (usedDirectShow) return L"Warning! The file is being opened using Avisynth's DirectShowSource, which has unreliable seeking. Frame numbers might not match the real number. PROCEED AT YOUR OWN RISK!"; + else return L""; +} + +#endif diff --git a/aegisub/libmedia/video/avs_video.h b/aegisub/libmedia/video/avs_video.h new file mode 100644 index 000000000..b6a024fca --- /dev/null +++ b/aegisub/libmedia/video/avs_video.h @@ -0,0 +1,99 @@ +// Copyright (c) 2006, Fredrik Mellbin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file video_provider_avs.h +/// @see video_provider_avs.cpp +/// @ingroup video_input +/// + +#ifdef WITH_AVISYNTH +#include "avisynth_wrap.h" +#include "include/aegisub/video_provider.h" + +/// DOCME +/// @class AvisynthVideoProvider +/// @brief DOCME +/// +/// DOCME +class AvisynthVideoProvider: public VideoProvider, AviSynthWrapper { + /// DOCME + VideoInfo vi; + + /// DOCME + AegiVideoFrame iframe; + + + /// DOCME + bool usedDirectShow; + + /// DOCME + wxString rendererCallString; + + /// DOCME + wxString decoderName; + + + /// DOCME + int num_frames; + + /// DOCME + int last_fnum; + + + /// DOCME + agi::vfr::Framerate real_fps; + agi::vfr::Framerate vfr_fps; + + /// DOCME + std::vector KeyFrames; + + /// DOCME + PClip RGB32Video; + + PClip OpenVideo(wxString filename); + AVSValue Open(wxFileName const& fname, wxString const& extension); + +public: + AvisynthVideoProvider(wxString filename); + ~AvisynthVideoProvider(); + + const AegiVideoFrame GetFrame(int n); + + int GetPosition() const { return last_fnum; }; + int GetFrameCount() const { return num_frames? num_frames: vi.num_frames; }; + agi::vfr::Framerate GetFPS() const { return vfr_fps.IsLoaded() ? vfr_fps : real_fps; }; + int GetWidth() const { return vi.width; }; + int GetHeight() const { return vi.height; }; + std::vector GetKeyFrames() const { return KeyFrames; }; + wxString GetWarning() const; + wxString GetDecoderName() const { return wxString(L"Avisynth/") + decoderName; } +}; +#endif diff --git a/aegisub/libmedia/video/dummy_video.cpp b/aegisub/libmedia/video/dummy_video.cpp new file mode 100644 index 000000000..c36c8c869 --- /dev/null +++ b/aegisub/libmedia/video/dummy_video.cpp @@ -0,0 +1,218 @@ +// Copyright (c) 2007, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file video_provider_dummy.cpp +/// @brief Video provider returning a constant frame +/// @ingroup video_input +/// + +#include "config.h" + +#ifndef AGI_PRE +#include +#endif + +#include "colorspace.h" +#include "video_provider_dummy.h" + +/// @brief Constructor +/// @param _fps +/// @param frames +/// @param _width +/// @param _height +/// @param colour +/// @param pattern +/// +void DummyVideoProvider::Create(double _fps, int frames, int _width, int _height, const wxColour &colour, bool pattern) { + lastFrame = -1; + framecount = frames; + fps = _fps; + width = _width; + height = _height; + + frame = AegiVideoFrame(width,height); + unsigned char *dst = frame.data; + unsigned char r = colour.Red(), g = colour.Green(), b = colour.Blue(); + + unsigned char h, s, l, lr, lg, lb; // light variants + rgb_to_hsl(r, g, b, &h, &s, &l); + l += 24; + if (l < 24) l -= 48; + hsl_to_rgb(h, s, l, &lr, &lg, &lb); + + if (pattern) { + int ppitch = frame.pitch / frame.GetBpp(); + for (unsigned int y = 0; y < frame.h; ++y) { + if ((y / 8) & 1) { + for (int x = 0; x < ppitch; ++x) { + if ((x / 8) & 1) { + *dst++ = b; + *dst++ = g; + *dst++ = r; + *dst++ = 0; + } + else { + *dst++ = lb; + *dst++ = lg; + *dst++ = lr; + *dst++ = 0; + } + } + } + else { + for (int x = 0; x < ppitch; ++x) { + if ((x / 8) & 1) { + *dst++ = lb; + *dst++ = lg; + *dst++ = lr; + *dst++ = 0; + } + else { + *dst++ = b; + *dst++ = g; + *dst++ = r; + *dst++ = 0; + } + } + } + } + } + else { + for (int i=frame.pitch*frame.h/frame.GetBpp();--i>=0;) { + *dst++ = b; + *dst++ = g; + *dst++ = r; + *dst++ = 0; + } + } +} + +/// @brief Parsing constructor +/// @param filename +/// +DummyVideoProvider::DummyVideoProvider(wxString filename) +{ + wxString params; + if (!filename.StartsWith(_T("?dummy:"), ¶ms)) { + throw agi::FileNotFoundError("Attempted creating dummy video provider with non-dummy filename"); + } + + wxStringTokenizer t(params, _T(":")); + if (t.CountTokens() < 7) { + throw VideoOpenError("Too few fields in dummy video parameter list"); + } + + double fps; + long _frames, _width, _height, red, green, blue; + bool pattern = false; + + wxString field = t.GetNextToken(); + if (!field.ToDouble(&fps)) { + throw VideoOpenError("Unable to parse fps field in dummy video parameter list"); + } + + field = t.GetNextToken(); + if (!field.ToLong(&_frames)) { + throw VideoOpenError("Unable to parse framecount field in dummy video parameter list"); + } + + field = t.GetNextToken(); + if (!field.ToLong(&_width)) { + throw VideoOpenError("Unable to parse width field in dummy video parameter list"); + } + + field = t.GetNextToken(); + if (!field.ToLong(&_height)) { + throw VideoOpenError("Unable to parse height field in dummy video parameter list"); + } + + field = t.GetNextToken(); + if (!field.ToLong(&red)) { + throw VideoOpenError("Unable to parse red colour field in dummy video parameter list"); + } + + field = t.GetNextToken(); + if (!field.ToLong(&green)) { + throw VideoOpenError("Unable to parse green colour field in dummy video parameter list"); + } + + field = t.GetNextToken(); + if (!field.ToLong(&blue)) { + throw VideoOpenError("Unable to parse blue colour field in dummy video parameter list"); + } + + field = t.GetNextToken(); + if (field == _T("c")) { + pattern = true; + } + + Create(fps, _frames, _width, _height, wxColour(red, green, blue), pattern); +} + +/// @brief Direct constructor +/// @param _fps +/// @param frames +/// @param _width +/// @param _height +/// @param colour +/// @param pattern +/// +DummyVideoProvider::DummyVideoProvider(double _fps, int frames, int _width, int _height, const wxColour &colour, bool pattern) { + Create(_fps, frames, _width, _height, colour, pattern); +} + +/// @brief Destructor +/// +DummyVideoProvider::~DummyVideoProvider() { + frame.Clear(); +} + +/// @brief Construct a fake filename describing the video +/// @param fps +/// @param frames +/// @param _width +/// @param _height +/// @param colour +/// @param pattern +/// @return +/// +wxString DummyVideoProvider::MakeFilename(double fps, int frames, int _width, int _height, const wxColour &colour, bool pattern) { + return wxString::Format(_T("?dummy:%f:%d:%d:%d:%d:%d:%d:%s"), fps, frames, _width, _height, colour.Red(), colour.Green(), colour.Blue(), pattern?_T("c"):_T("")); +} + +/// @brief Get frame +/// @param n +/// @return +/// +const AegiVideoFrame DummyVideoProvider::GetFrame(int n) { + lastFrame = n; + return frame; +} diff --git a/aegisub/libmedia/video/dummy_video.h b/aegisub/libmedia/video/dummy_video.h new file mode 100644 index 000000000..d57b42a92 --- /dev/null +++ b/aegisub/libmedia/video/dummy_video.h @@ -0,0 +1,86 @@ +// Copyright (c) 2007, Niels Martin Hansen +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file video_provider_dummy.h +/// @see video_provider_dummy.cpp +/// @ingroup video_input +/// + +// The dummy video provider needs a header, since it needs to be created directly as a special case + +#ifndef AGI_PRE +#include +#endif + +#include "include/aegisub/video_provider.h" + +/// DOCME +/// @class DummyVideoProvider +/// @brief DOCME +/// +/// DOCME +class DummyVideoProvider : public VideoProvider { + /// DOCME + int lastFrame; + + /// DOCME + int framecount; + + /// DOCME + agi::vfr::Framerate fps; + + /// DOCME + int width; + + /// DOCME + int height; + + /// DOCME + AegiVideoFrame frame; + + void Create(double fps, int frames, int _width, int _height, const wxColour &colour, bool pattern); + +public: + DummyVideoProvider(wxString filename); + DummyVideoProvider(double fps, int frames, int _width, int _height, const wxColour &colour, bool pattern); + ~DummyVideoProvider(); + + const AegiVideoFrame GetFrame(int n); + static wxString MakeFilename(double fps, int frames, int _width, int _height, const wxColour &colour, bool pattern); + + int GetPosition() const { return lastFrame; } + int GetFrameCount() const { return framecount; } + int GetWidth() const { return width; } + int GetHeight() const { return height; } + agi::vfr::Framerate GetFPS() const { return fps; } + std::vector GetKeyFrames() const { return std::vector(); }; + wxString GetDecoderName() const { return L"Dummy Video Provider"; } +}; diff --git a/aegisub/libmedia/video/yuv4mpeg.cpp b/aegisub/libmedia/video/yuv4mpeg.cpp new file mode 100644 index 000000000..d7d032fd0 --- /dev/null +++ b/aegisub/libmedia/video/yuv4mpeg.cpp @@ -0,0 +1,421 @@ +// Copyright (c) 2009, Karl Blomster +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file video_provider_yuv4mpeg.cpp +/// @brief Video provider reading YUV4MPEG files directly without depending on external libraries +/// @ingroup video_input +/// + +#include "config.h" + +#include + +#include "compat.h" +#include "utils.h" +#include "video_provider_yuv4mpeg.h" + +// All of this cstdio bogus is because of one reason and one reason only: +// MICROSOFT'S IMPLEMENTATION OF STD::FSTREAM DOES NOT SUPPORT FILES LARGER THAN 2 GB. +// (yes, really) +// With cstdio it's at least possible to work around the problem... +#ifdef _MSC_VER +#define fseeko _fseeki64 +#define ftello _ftelli64 +#endif + +/// @brief Constructor +/// @param filename The filename to open +YUV4MPEGVideoProvider::YUV4MPEGVideoProvider(wxString fname) +: sf(NULL) +, inited(false) +, w (0) +, h (0) +, num_frames(-1) +, cur_fn(-1) +, pixfmt(Y4M_PIXFMT_NONE) +, imode(Y4M_ILACE_NOTSET) +{ + fps_rat.num = -1; + fps_rat.den = 1; + + try { + wxString filename = wxFileName(fname).GetShortPath(); + +#ifdef WIN32 + sf = _wfopen(filename.wc_str(), L"rb"); +#else + sf = fopen(filename.utf8_str(), "rb"); +#endif + + if (sf == NULL) throw agi::FileNotFoundError(STD_STR(fname)); + + CheckFileFormat(); + + ParseFileHeader(ReadHeader(0, false)); + + if (w <= 0 || h <= 0) + throw VideoOpenError("Invalid resolution"); + if (fps_rat.num <= 0 || fps_rat.den <= 0) { + fps_rat.num = 25; + fps_rat.den = 1; + LOG_D("provider/video/yuv4mpeg") << "framerate info unavailable, assuming 25fps"; + } + if (pixfmt == Y4M_PIXFMT_NONE) + pixfmt = Y4M_PIXFMT_420JPEG; + if (imode == Y4M_ILACE_NOTSET) + imode = Y4M_ILACE_UNKNOWN; + + luma_sz = w * h; + switch (pixfmt) { + case Y4M_PIXFMT_420JPEG: + case Y4M_PIXFMT_420MPEG2: + case Y4M_PIXFMT_420PALDV: + chroma_sz = (w * h) >> 2; break; + case Y4M_PIXFMT_422: + chroma_sz = (w * h) >> 1; break; + /// @todo add support for more pixel formats + default: + throw VideoOpenError("Unsupported pixel format"); + } + frame_sz = luma_sz + chroma_sz*2; + + num_frames = IndexFile(); + if (num_frames <= 0 || seek_table.empty()) + throw VideoOpenError("Unable to determine file length"); + cur_fn = 0; + + fseeko(sf, 0, SEEK_SET); + } + catch (...) { + if (sf) fclose(sf); + throw; + } +} + + +/// @brief Destructor +YUV4MPEGVideoProvider::~YUV4MPEGVideoProvider() { + if (sf) fclose(sf); +} + +/// @brief Checks if the file is an YUV4MPEG file or not +/// Note that it reports the error by throwing an exception, +/// not by returning a false value. +void YUV4MPEGVideoProvider::CheckFileFormat() { + char buf[10]; + if (fread(buf, 10, 1, sf) != 1) + throw VideoNotSupported("CheckFileFormat: Failed reading header"); + if (strncmp("YUV4MPEG2 ", buf, 10)) + throw VideoNotSupported("CheckFileFormat: File is not a YUV4MPEG file (bad magic)"); + + fseeko(sf, 0, SEEK_SET); +} + +/// @brief Read a frame or file header at a given file position +/// @param startpos The byte offset at where to start reading +/// @param reset_pos If true, the function will reset the file position to what it was before the function call before returning +/// @return A list of parameters +std::vector YUV4MPEGVideoProvider::ReadHeader(int64_t startpos, bool reset_pos) { + int64_t oldpos = ftello(sf); + std::vector tags; + wxString curtag; + int bytesread = 0; + int buf; + + if (fseeko(sf, startpos, SEEK_SET)) + throw VideoOpenError(STD_STR(wxString::Format(L"YUV4MPEG video provider: ReadHeader: failed seeking to position %d", startpos))); + + // read header until terminating newline (0x0A) is found + while ((buf = fgetc(sf)) != 0x0A) { + if (ferror(sf)) + throw VideoOpenError("ReadHeader: Failed to read from file"); + if (feof(sf)) { + // you know, this is one of the places where it would be really nice + // to be able to throw an exception object that tells the caller that EOF was reached + LOG_D("provider/video/yuv4mpeg") << "ReadHeader: Reached EOF, returning"; + break; + } + + // some basic low-effort sanity checking + if (buf == 0x00) + throw VideoOpenError("ReadHeader: Malformed header (unexpected NUL)"); + if (++bytesread >= YUV4MPEG_HEADER_MAXLEN) + throw VideoOpenError("ReadHeader: Malformed header (no terminating newline found)"); + + // found a new tag + if (buf == 0x20) { + tags.push_back(curtag); + curtag.Clear(); + } + else + curtag.Append(static_cast(buf)); + } + // if only one tag with no trailing space was found (possible in the + // FRAME header case), make sure we get it + if (!curtag.IsEmpty()) { + tags.push_back(curtag); + curtag.Clear(); + } + + if (reset_pos) + fseeko(sf, oldpos, SEEK_SET); + + return tags; +} + +/// @brief Parses a list of parameters and sets reader state accordingly +/// @param tags The list of parameters to parse +void YUV4MPEGVideoProvider::ParseFileHeader(const std::vector& tags) { + if (tags.size() <= 1) + throw VideoOpenError("ParseFileHeader: contentless header"); + if (tags.front().Cmp("YUV4MPEG2")) + throw VideoOpenError("ParseFileHeader: malformed header (bad magic)"); + + // temporary stuff + int t_w = -1; + int t_h = -1; + int t_fps_num = -1; + int t_fps_den = -1; + Y4M_InterlacingMode t_imode = Y4M_ILACE_NOTSET; + Y4M_PixelFormat t_pixfmt = Y4M_PIXFMT_NONE; + + for (unsigned i = 1; i < tags.size(); i++) { + wxString tag; + long tmp_long1 = 0; + long tmp_long2 = 0; + + if (tags[i].StartsWith("W", &tag)) { + if (!tag.ToLong(&tmp_long1)) + throw VideoOpenError("ParseFileHeader: invalid width"); + t_w = (int)tmp_long1; + } + else if (tags[i].StartsWith("H", &tag)) { + if (!tag.ToLong(&tmp_long1)) + throw VideoOpenError("ParseFileHeader: invalid height"); + t_h = (int)tmp_long1; + } + else if (tags[i].StartsWith("F", &tag)) { + if (!(tag.BeforeFirst(':')).ToLong(&tmp_long1) && tag.AfterFirst(':').ToLong(&tmp_long2)) + throw VideoOpenError("ParseFileHeader: invalid framerate"); + t_fps_num = (int)tmp_long1; + t_fps_den = (int)tmp_long2; + } + else if (tags[i].StartsWith("C", &tag)) { + // technically this should probably be case sensitive, + // but being liberal in what you accept doesn't hurt + tag.MakeLower(); + if (tag == "420") t_pixfmt = Y4M_PIXFMT_420JPEG; // is this really correct? + else if (tag == "420jpeg") t_pixfmt = Y4M_PIXFMT_420JPEG; + else if (tag == "420mpeg2") t_pixfmt = Y4M_PIXFMT_420MPEG2; + else if (tag == "420paldv") t_pixfmt = Y4M_PIXFMT_420PALDV; + else if (tag == "411") t_pixfmt = Y4M_PIXFMT_411; + else if (tag == "422") t_pixfmt = Y4M_PIXFMT_422; + else if (tag == "444") t_pixfmt = Y4M_PIXFMT_444; + else if (tag == "444alpha") t_pixfmt = Y4M_PIXFMT_444ALPHA; + else if (tag == "mono") t_pixfmt = Y4M_PIXFMT_MONO; + else + throw VideoOpenError("ParseFileHeader: invalid or unknown colorspace"); + } + else if (tags[i].StartsWith("I", &tag)) { + tag.MakeLower(); + if (tag == "p") t_imode = Y4M_ILACE_PROGRESSIVE; + else if (tag == "t") t_imode = Y4M_ILACE_TFF; + else if (tag == "b") t_imode = Y4M_ILACE_BFF; + else if (tag == "m") t_imode = Y4M_ILACE_MIXED; + else if (tag == "?") t_imode = Y4M_ILACE_UNKNOWN; + else + throw VideoOpenError("ParseFileHeader: invalid or unknown interlacing mode"); + } + else + LOG_D("provider/video/yuv4mpeg") << "Unparsed tag: " << tags[i].c_str(); + } + + // The point of all this is to allow multiple YUV4MPEG2 headers in a single file + // (can happen if you concat several files) as long as they have identical + // header flags. The spec doesn't explicitly say you have to allow this, + // but the "reference implementation" (mjpegtools) does, so I'm doing it too. + if (inited) { + if (t_w > 0 && t_w != w) + throw VideoOpenError("ParseFileHeader: illegal width change"); + if (t_h > 0 && t_h != h) + throw VideoOpenError("ParseFileHeader: illegal height change"); + if ((t_fps_num > 0 && t_fps_den > 0) && (t_fps_num != fps_rat.num || t_fps_den != fps_rat.den)) + throw VideoOpenError("ParseFileHeader: illegal framerate change"); + if (t_pixfmt != Y4M_PIXFMT_NONE && t_pixfmt != pixfmt) + throw VideoOpenError("ParseFileHeader: illegal colorspace change"); + if (t_imode != Y4M_ILACE_NOTSET && t_imode != imode) + throw VideoOpenError("ParseFileHeader: illegal interlacing mode change"); + } + else { + w = t_w; + h = t_h; + fps_rat.num = t_fps_num; + fps_rat.den = t_fps_den; + pixfmt = t_pixfmt != Y4M_PIXFMT_NONE ? t_pixfmt : Y4M_PIXFMT_420JPEG; + imode = t_imode != Y4M_ILACE_NOTSET ? t_imode : Y4M_ILACE_UNKNOWN; + fps = double(fps_rat.num) / fps_rat.den; + inited = true; + } +} + +/// @brief Parses a frame header +/// @param tags The list of parameters to parse +/// @return The flags set, as a binary mask +/// This function is currently unimplemented (it will always return Y4M_FFLAG_NONE). +YUV4MPEGVideoProvider::Y4M_FrameFlags YUV4MPEGVideoProvider::ParseFrameHeader(const std::vector& tags) { + if (tags.front().Cmp(_("FRAME"))) + throw VideoOpenError("ParseFrameHeader: malformed frame header (bad magic)"); + + /// @todo implement parsing of frame flags + + return Y4M_FFLAG_NONE; +} + +/// @brief Indexes the file +/// @return The number of frames found in the file +/// This function goes through the file, finds and parses all file and frame headers, +/// and creates a seek table that lists the byte positions of all frames so seeking +/// can easily be done. +int YUV4MPEGVideoProvider::IndexFile() { + int framecount = 0; + int64_t curpos = ftello(sf); + + // the ParseFileHeader() call in LoadVideo() will already have read + // the file header for us and set the seek position correctly + while (true) { + curpos = ftello(sf); // update position + // continue reading headers until no more are found + std::vector tags = ReadHeader(curpos, false); + curpos = ftello(sf); + + if (tags.empty()) + break; // no more headers + + Y4M_FrameFlags flags = Y4M_FFLAG_NOTSET; + if (!tags.front().Cmp("YUV4MPEG2")) { + ParseFileHeader(tags); + continue; + } + else if (!tags.front().Cmp("FRAME")) + flags = ParseFrameHeader(tags); + + if (flags == Y4M_FFLAG_NONE) { + framecount++; + seek_table.push_back(curpos); + // seek to next frame header start position + if (fseeko(sf, frame_sz, SEEK_CUR)) + throw VideoOpenError(STD_STR(wxString::Format("IndexFile: failed seeking to position %d", curpos + frame_sz))); + } + else { + /// @todo implement rff flags etc + } + } + + return framecount; +} + +// http://bob.allegronetwork.com/prog/tricks.html#clamp +static FORCEINLINE int clamp(int x) { + x &= (~x) >> 31; + x -= 255; + x &= x >> 31; + x += 255; + return x; +} + +/// @brief Gets a given frame +/// @param n The frame number to return +/// @return The video frame +const AegiVideoFrame YUV4MPEGVideoProvider::GetFrame(int n) { + cur_fn = mid(0, n, num_frames - 1); + + int uv_width = w / 2; + switch (pixfmt) { + case Y4M_PIXFMT_420JPEG: + case Y4M_PIXFMT_420MPEG2: + case Y4M_PIXFMT_420PALDV: + break; + /// @todo add support for more pixel formats + default: + throw "YUV4MPEG video provider: GetFrame: Unsupported source colorspace"; + } + + std::vector planes[3]; + planes[0].resize(luma_sz); + planes[1].resize(chroma_sz); + planes[2].resize(chroma_sz); + + fseeko(sf, seek_table[n], SEEK_SET); + size_t ret; + ret = fread(&planes[0][0], luma_sz, 1, sf); + if (ret != 1 || feof(sf) || ferror(sf)) + throw "YUV4MPEG video provider: GetFrame: failed to read luma plane"; + for (int i = 1; i <= 2; i++) { + ret = fread(&planes[i][0], chroma_sz, 1, sf); + if (ret != 1 || feof(sf) || ferror(sf)) + throw "YUV4MPEG video provider: GetFrame: failed to read chroma planes"; + } + + AegiVideoFrame dst_frame; + dst_frame.invertChannels = true; + dst_frame.w = w; + dst_frame.h = h; + dst_frame.pitch = w * 4; + dst_frame.Allocate(); + + const unsigned char *src_y = &planes[0][0]; + const unsigned char *src_u = &planes[1][0]; + const unsigned char *src_v = &planes[2][0]; + unsigned char *dst = dst_frame.data; + + for (int py = 0; py < h; ++py) { + for (int px = 0; px < w / 2; ++px) { + const int u = *src_u++ - 128; + const int v = *src_v++ - 128; + for (unsigned int i = 0; i < 2; ++i) { + const int y = (*src_y++ - 16) * 298; + + *dst++ = clamp((y + 516 * u + 128) >> 8); // Blue + *dst++ = clamp((y - 100 * u - 208 * v + 128) >> 8); // Green + *dst++ = clamp((y + 409 * v + 128) >> 8); // Red + *dst++ = 0; // Alpha + } + } + + // Roll back u/v on even lines + if (!(py & 1)) { + src_u -= uv_width; + src_v -= uv_width; + } + } + + return dst_frame; +} diff --git a/aegisub/libmedia/video/yuv4mpeg.h b/aegisub/libmedia/video/yuv4mpeg.h new file mode 100644 index 000000000..87031f4bb --- /dev/null +++ b/aegisub/libmedia/video/yuv4mpeg.h @@ -0,0 +1,154 @@ +// Copyright (c) 2009, Karl Blomster +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. +// +// Aegisub Project http://www.aegisub.org/ +// +// $Id$ + +/// @file video_provider_yuv4mpeg.h +/// @see video_provider_yuv4mpeg.cpp +/// @ingroup video_input +/// + +#include "include/aegisub/video_provider.h" +#ifndef AGI_PRE +#include + +#include + +#include +#include +#endif + +/// the maximum allowed header length, in bytes +#define YUV4MPEG_HEADER_MAXLEN 128 + +/// @class YUV4MPEGVideoProvider +/// @brief Implements reading of YUV4MPEG uncompressed video files +class YUV4MPEGVideoProvider : public VideoProvider { + /// Pixel formats + enum Y4M_PixelFormat { + Y4M_PIXFMT_NONE = -1, /// not set/unknown + + /// 4:2:0 sampling variants. + /// afaict the only difference between these three + /// is the chroma sample location, and nobody cares about that. + Y4M_PIXFMT_420JPEG, /// 4:2:0, H/V centered, for JPEG/MPEG-1 + Y4M_PIXFMT_420MPEG2, /// 4:2:0, H cosited, for MPEG-2 + Y4M_PIXFMT_420PALDV, /// 4:2:0, alternating Cb/Cr, for PAL-DV + + Y4M_PIXFMT_411, /// 4:1:1, H cosited + Y4M_PIXFMT_422, /// 4:2:2, H cosited + Y4M_PIXFMT_444, /// 4:4:4, i.e. no chroma subsampling + Y4M_PIXFMT_444ALPHA, /// 4:4:4 plus alpha channel + + Y4M_PIXFMT_MONO, /// luma only (grayscale) + }; + + + /// Interlacing mode for an entire stream + enum Y4M_InterlacingMode { + Y4M_ILACE_NOTSET = -1, /// undefined + Y4M_ILACE_PROGRESSIVE, /// progressive (no interlacing) + + Y4M_ILACE_TFF, /// interlaced, top field first + Y4M_ILACE_BFF, /// interlaced, bottom field first + + Y4M_ILACE_MIXED, /// mixed interlaced/progressive, possibly with RFF flags + Y4M_ILACE_UNKNOWN, /// unknown interlacing mode (not the same as undefined) + }; + + + /// Frame information flags + enum Y4M_FrameFlags { + Y4M_FFLAG_NOTSET = -1, /// undefined + Y4M_FFLAG_NONE = 0x0000, /// no flags set + + /// field order/repeat field flags + Y4M_FFLAG_R_TFF = 0x0001, /// top field first + Y4M_FFLAG_R_TFF_R = 0x0002, /// top field first, and repeat that field + Y4M_FFLAG_R_BFF = 0x0004, /// bottom field first + Y4M_FFLAG_R_BFF_R = 0x0008, /// bottom field first, and repeat that field + Y4M_FFLAG_R_P = 0x0010, /// progressive + Y4M_FFLAG_R_P_R = 0x0020, /// progressive, and repeat frame once + Y4M_FFLAG_R_P_RR = 0x0040, /// progressive, and repeat frame twice + + /// temporal sampling flags + Y4M_FFLAG_T_P = 0x0080, /// progressive (fields sampled at the same time) + Y4M_FFLAG_T_I = 0x0100, /// interlaced (fields sampled at different times) + + /// chroma subsampling flags + Y4M_FFLAG_C_P = 0x0200, /// progressive (whole frame subsampled) + Y4M_FFLAG_C_I = 0x0400, /// interlaced (fields subsampled independently) + Y4M_FFLAG_C_UNKNOWN = 0x0800, /// unknown (only allowed for non-4:2:0 sampling) + }; + + + FILE *sf; /// source file + bool inited; /// initialization state + + int w, h; /// frame width/height + int num_frames; /// length of file in frames + int frame_sz; /// size of each frame in bytes + int luma_sz; /// size of the luma plane of each frame, in bytes + int chroma_sz; /// size of one of the two chroma planes of each frame, in bytes + int cur_fn; /// current frame number + + Y4M_PixelFormat pixfmt; /// colorspace/pixel format + Y4M_InterlacingMode imode; /// interlacing mode (for the entire stream) + struct { + int num; /// numerator + int den; /// denominator + } fps_rat; /// framerate + + agi::vfr::Framerate fps; + + /// a list of byte positions detailing where in the file + /// each frame header can be found + std::vector seek_table; + + void CheckFileFormat(); + void ParseFileHeader(const std::vector& tags); + Y4M_FrameFlags ParseFrameHeader(const std::vector& tags); + std::vector ReadHeader(int64_t startpos, bool reset_pos=false); + int IndexFile(); + +public: + YUV4MPEGVideoProvider(wxString filename); + ~YUV4MPEGVideoProvider(); + + const AegiVideoFrame GetFrame(int n); + + int GetPosition() const { return cur_fn; } + int GetFrameCount() const { return num_frames; } + int GetWidth() const { return w; } + int GetHeight() const { return h; } + agi::vfr::Framerate GetFPS() const { return fps; } + std::vector GetKeyFrames() const { return std::vector(); }; + wxString GetDecoderName() const { return L"YU4MPEG"; }; + bool WantsCaching() const { return true; }; +};