From bec89515340f0981f3ccc725dc3dd2a17fe706fc Mon Sep 17 00:00:00 2001 From: Karl Blomster Date: Fri, 24 Jul 2009 06:04:19 +0000 Subject: [PATCH] Implement a basic QuickTime audio provider. Currently a bit limited in functionality (it will convert everything to 16-bit mono), but seems to work fine with the one .mov test file I have. Also does a lot of ugly downcasting, I'll try to fix that later. Originally committed to SVN as r3254. --- aegisub/src/audio_provider.cpp | 6 + aegisub/src/audio_provider_quicktime.cpp | 180 +++++++++++++++++++++++ aegisub/src/audio_provider_quicktime.h | 78 ++++++++++ aegisub/src/quicktime_common.cpp | 16 +- aegisub/src/quicktime_common.h | 11 +- aegisub/src/video_provider_quicktime.cpp | 3 +- 6 files changed, 283 insertions(+), 11 deletions(-) create mode 100644 aegisub/src/audio_provider_quicktime.cpp create mode 100644 aegisub/src/audio_provider_quicktime.h diff --git a/aegisub/src/audio_provider.cpp b/aegisub/src/audio_provider.cpp index 728979229..91e657a97 100644 --- a/aegisub/src/audio_provider.cpp +++ b/aegisub/src/audio_provider.cpp @@ -50,6 +50,9 @@ #ifdef WITH_FFMPEGSOURCE #include "audio_provider_ffmpegsource.h" #endif +#ifdef WITH_QUICKTIME +#include "audio_provider_quicktime.h" +#endif #include "options.h" #include "audio_display.h" @@ -279,6 +282,9 @@ void AudioProviderFactoryManager::RegisterProviders() { #ifdef WITH_FFMPEGSOURCE RegisterFactory(new FFmpegSourceAudioProviderFactory(),_T("FFmpegSource")); #endif +#ifdef WITH_QUICKTIME + RegisterFactory(new QuickTimeAudioProviderFactory(), _T("QuickTime")); +#endif } diff --git a/aegisub/src/audio_provider_quicktime.cpp b/aegisub/src/audio_provider_quicktime.cpp new file mode 100644 index 000000000..f0dfe2e26 --- /dev/null +++ b/aegisub/src/audio_provider_quicktime.cpp @@ -0,0 +1,180 @@ +// 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 +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + + +#include "audio_provider_quicktime.h" + +#ifdef WITH_QUICKTIME + +QuickTimeAudioProvider::QuickTimeAudioProvider(wxString filename) { + movie = NULL; + in_dataref = NULL; + extract_ref = NULL; + inited = false; + qt_err = noErr; + qt_status = noErr; + errmsg = _T("QuickTime audio provider: "); + + // try to init quicktime + try { + InitQuickTime(); + } + catch (wxString temp) { + errmsg.Append(temp); + throw errmsg; + } + catch (...) { + throw; + } + + // try to load audio + try { + LoadAudio(filename); + } + catch (wxString temp) { + errmsg.Append(temp); + throw errmsg; + } + catch (...) { + throw; + } +} + + +QuickTimeAudioProvider::~QuickTimeAudioProvider() { + Close(); + DeInitQuickTime(); +} + + +void QuickTimeAudioProvider::Close() { + if (movie) + DisposeMovie(movie); + movie = NULL; + if (in_dataref) + DisposeHandle(in_dataref); + in_dataref = NULL; + if (inited) + MovieAudioExtractionEnd(extract_ref); + inited = false; +} + + +void QuickTimeAudioProvider::LoadAudio(wxString filename) { + OSType in_dataref_type; + wxStringToDataRef(filename, &in_dataref, &in_dataref_type); + + // verify that file is openable + if (!CanOpen(in_dataref, in_dataref_type)) + throw wxString(_T("QuickTime cannot open file as audio")); + + // actually open file + short res_id = 0; + qt_err = NewMovieFromDataRef(&movie, 0, &res_id, in_dataref, in_dataref_type); + QTCheckError(qt_err, wxString(_T("Failed to open file"))); + + // disable automagic screen rendering just to be safe + qt_err = SetMovieVisualContext(movie, NULL); + QTCheckError(qt_err, wxString(_T("Failed to disable visual context"))); + + qt_status = MovieAudioExtractionBegin(movie, 0, &extract_ref); + QTCheckError(qt_status, wxString(_T("Failed to initialize audio extraction"))); + inited = true; + + // and here I thought I knew what "verbose" meant... + AudioStreamBasicDescription asbd; + qt_status = MovieAudioExtractionGetProperty(extract_ref, kQTPropertyClass_MovieAudioExtraction_Audio, + kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription, sizeof(asbd), &asbd, NULL); + QTCheckError(qt_status, wxString(_T("Failed to retreive audio properties"))); + + sample_rate = (int)asbd.mSampleRate; + channels = 1; // FIXME: allow more than one channel + bytes_per_sample = 2; + + // lazy hack: set the movie time scale to same as the sample rate, to allow for easy seeking + SetMovieTimeScale(movie, (TimeScale)asbd.mSampleRate); + num_samples = GetMovieDuration(movie); + + asbd.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian; + asbd.mBitsPerChannel = sizeof(int16_t) * 8; + asbd.mBytesPerFrame = sizeof(int16_t); + asbd.mBytesPerPacket = asbd.mBytesPerFrame; + asbd.mChannelsPerFrame = 1; + + qt_status = MovieAudioExtractionSetProperty(extract_ref, kQTPropertyClass_MovieAudioExtraction_Audio, + kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription, sizeof(asbd), &asbd); + QTCheckError(qt_status, wxString(_T("Failed to set audio properties"))); + + AudioChannelLayout ch_layout; + ch_layout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono; + ch_layout.mChannelBitmap = 0; + ch_layout.mNumberChannelDescriptions = 0; + qt_status = MovieAudioExtractionSetProperty(extract_ref, kQTPropertyClass_MovieAudioExtraction_Audio, + kQTMovieAudioExtractionAudioPropertyID_AudioChannelLayout, sizeof(ch_layout), &ch_layout); + QTCheckError(qt_status, wxString(_T("Failed to set channel layout"))); +} + + +void QuickTimeAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) { + TimeRecord trec; + trec.scale = GetMovieTimeScale(movie); + trec.base = NULL; + trec.value.hi = (int32_t)(start >> 32); + trec.value.lo = (int32_t)((start & 0xFFFFFFFF00000000ULL) >> 32); + + qt_status = MovieAudioExtractionSetProperty(extract_ref, kQTPropertyClass_MovieAudioExtraction_Movie, + kQTMovieAudioExtractionMoviePropertyID_CurrentTime, sizeof(TimeRecord), &trec); + QTCheckError(qt_status, wxString(_T("QuickTime audio provider: Failed to seek in file"))); + + // FIXME: hack something up to actually handle very big counts correctly, + // maybe with multiple buffers? + AudioBufferList dst_buflist; + dst_buflist.mNumberBuffers = 1; + dst_buflist.mBuffers[0].mNumberChannels = 1; + dst_buflist.mBuffers[0].mDataByteSize = count * bytes_per_sample; + dst_buflist.mBuffers[0].mData = buf; + + UInt32 flags; + UInt32 decode_count = (UInt32)count; + qt_status = MovieAudioExtractionFillBuffer(extract_ref, &decode_count, &dst_buflist, &flags); + QTCheckError(qt_status, wxString(_T("QuickTime audio provider: Failed to decode audio"))); + + if (count != decode_count) + wxLogDebug(_T("QuickTime audio provider: GetAudio: Warning: decoded samplecount %d not same as requested count %d"), + decode_count, (uint32_t)count); +} + + +#endif /* WITH_QUICKTIME */ diff --git a/aegisub/src/audio_provider_quicktime.h b/aegisub/src/audio_provider_quicktime.h new file mode 100644 index 000000000..ebfd87224 --- /dev/null +++ b/aegisub/src/audio_provider_quicktime.h @@ -0,0 +1,78 @@ +// 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 +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + + +#pragma once + +#include "quicktime_common.h" + +#ifdef WITH_QUICKTIME +#include +#include +#include "include/aegisub/audio_provider.h" + + +class QuickTimeAudioProvider : public AudioProvider, QuickTimeProvider { +private: + Movie movie; // input file + Handle in_dataref; // input file handle + MovieAudioExtractionRef extract_ref; // extraction session object + + bool inited; + + OSErr qt_err; // quicktime error code + OSStatus qt_status; // another quicktime error code + wxString errmsg; // aegisub error messages + + void Close(); + void LoadAudio(wxString filename); + +public: + QuickTimeAudioProvider(wxString filename); + virtual ~QuickTimeAudioProvider(); + + bool AreSamplesNativeEndian() { return true; } + + virtual void GetAudio(void *buf, int64_t start, int64_t count); +}; + + +class QuickTimeAudioProviderFactory : public AudioProviderFactory { +public: + AudioProvider *CreateProvider(wxString file) { return new QuickTimeAudioProvider(file); } +}; + + +#endif /* WITH_QUICKTIME */ diff --git a/aegisub/src/quicktime_common.cpp b/aegisub/src/quicktime_common.cpp index 96bfd10f8..e14882e22 100644 --- a/aegisub/src/quicktime_common.cpp +++ b/aegisub/src/quicktime_common.cpp @@ -39,7 +39,8 @@ #ifdef WITH_QUICKTIME #include -// static fun + +// static init fun int QuickTimeProvider::qt_initcount = 0; GWorldPtr QuickTimeProvider::default_gworld = NULL; @@ -52,12 +53,12 @@ void QuickTimeProvider::InitQuickTime() { #endif qt_err = EnterMovies(); - QTCheckError(qt_err, wxString(_T("EnterMovies() failed"))); + QTCheckError(qt_err, wxString(_T("EnterMovies failed"))); // have we been inited before? if (qt_initcount <= 0) { - // we haven't, allocate an offscreen graphics world - // we need to do this before we actually open anything, or quicktime may crash (heh) + // We haven't, allocate an offscreen render target. + // We need to do this before we actually open anything, or quicktime may crash. (heh) Rect def_box; def_box.top = 0; def_box.left = 0; @@ -75,6 +76,7 @@ void QuickTimeProvider::InitQuickTime() { void QuickTimeProvider::DeInitQuickTime() { #ifdef WIN32 + // calls to InitializeQTML() must be balanced with an equal number of calls to TerminateQTML() TerminateQTML(); #endif qt_initcount--; @@ -86,6 +88,7 @@ void QuickTimeProvider::DeInitQuickTime() { } +// convert a wxstring containing a filename to a QT data reference void QuickTimeProvider::wxStringToDataRef(const wxString &string, Handle *dataref, OSType *dataref_type) { // convert filename, first to a CFStringRef... wxString wx_filename = wxFileName(string).GetShortPath(); @@ -104,8 +107,13 @@ void QuickTimeProvider::QTCheckError(OSErr err, wxString errmsg) { throw errmsg; /* CheckError(err, errmsg.c_str()); // I wonder if this actually works on Mac, and if so, what it does */ } +void QuickTimeProvider::QTCheckError(OSStatus err, wxString errmsg) { + if (err != noErr) + throw errmsg; +} +// return true if QT considers file openable bool QuickTimeProvider::CanOpen(const Handle& dataref, const OSType dataref_type) { Boolean can_open; Boolean prefer_img; diff --git a/aegisub/src/quicktime_common.h b/aegisub/src/quicktime_common.h index 77e89f4f1..6a00aca37 100644 --- a/aegisub/src/quicktime_common.h +++ b/aegisub/src/quicktime_common.h @@ -43,12 +43,10 @@ #include #include "include/aegisub/aegisub.h" -// qt stuff +// QT stuff #ifdef _MSC_VER -// avoid conflicts between MSVC's stdint.h and QT's stdint.h -#define _STDINT_H -// get MSVC to shut up about a macro redefinition in QT's ConditionalMacros.h -#pragma warning(disable: 4004) +#define _STDINT_H // avoid conflicts between MSVC's stdint.h and QT's stdint.h +#pragma warning(disable: 4004) // get MSVC to shut up about a macro redefinition in QT's ConditionalMacros.h #endif extern "C" { #ifdef WIN32 @@ -68,7 +66,10 @@ public: void DeInitQuickTime(); void wxStringToDataRef(const wxString &string, Handle *dataref, OSType *dataref_type); bool CanOpen(const Handle& dataref, const OSType dataref_type); + + void QTCheckError(OSErr err, wxString errmsg); + void QTCheckError(OSStatus err, wxString errmsg); static int qt_initcount; static GWorldPtr default_gworld; diff --git a/aegisub/src/video_provider_quicktime.cpp b/aegisub/src/video_provider_quicktime.cpp index 05f04fc85..1bcb302e3 100644 --- a/aegisub/src/video_provider_quicktime.cpp +++ b/aegisub/src/video_provider_quicktime.cpp @@ -140,8 +140,7 @@ void QuickTimeVideoProvider::LoadVideo(const wxString _filename) { h = m_box.bottom; // allocate a new offscreen rendering buffer with the correct dimensions QDErr qd_err = NewGWorld(&gw, 32, &m_box, NULL, NULL, keepLocal); - if (qd_err != noErr) - throw wxString(_T("Failed to initialize offscreen drawing buffer")); + QTCheckError(qd_err, wxString(_T("Failed to initialize offscreen drawing buffer"))); // select our new offscreen render target SetMovieGWorld(movie, gw, NULL);