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.
This commit is contained in:
parent
8cb1e62831
commit
bec8951534
6 changed files with 283 additions and 11 deletions
|
@ -50,6 +50,9 @@
|
||||||
#ifdef WITH_FFMPEGSOURCE
|
#ifdef WITH_FFMPEGSOURCE
|
||||||
#include "audio_provider_ffmpegsource.h"
|
#include "audio_provider_ffmpegsource.h"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WITH_QUICKTIME
|
||||||
|
#include "audio_provider_quicktime.h"
|
||||||
|
#endif
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "audio_display.h"
|
#include "audio_display.h"
|
||||||
|
|
||||||
|
@ -279,6 +282,9 @@ void AudioProviderFactoryManager::RegisterProviders() {
|
||||||
#ifdef WITH_FFMPEGSOURCE
|
#ifdef WITH_FFMPEGSOURCE
|
||||||
RegisterFactory(new FFmpegSourceAudioProviderFactory(),_T("FFmpegSource"));
|
RegisterFactory(new FFmpegSourceAudioProviderFactory(),_T("FFmpegSource"));
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WITH_QUICKTIME
|
||||||
|
RegisterFactory(new QuickTimeAudioProviderFactory(), _T("QuickTime"));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
180
aegisub/src/audio_provider_quicktime.cpp
Normal file
180
aegisub/src/audio_provider_quicktime.cpp
Normal file
|
@ -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 */
|
78
aegisub/src/audio_provider_quicktime.h
Normal file
78
aegisub/src/audio_provider_quicktime.h
Normal file
|
@ -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 <wx/wxprec.h>
|
||||||
|
#include <wx/log.h>
|
||||||
|
#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 */
|
|
@ -39,7 +39,8 @@
|
||||||
#ifdef WITH_QUICKTIME
|
#ifdef WITH_QUICKTIME
|
||||||
#include <wx/wxprec.h>
|
#include <wx/wxprec.h>
|
||||||
|
|
||||||
// static fun
|
|
||||||
|
// static init fun
|
||||||
int QuickTimeProvider::qt_initcount = 0;
|
int QuickTimeProvider::qt_initcount = 0;
|
||||||
GWorldPtr QuickTimeProvider::default_gworld = NULL;
|
GWorldPtr QuickTimeProvider::default_gworld = NULL;
|
||||||
|
|
||||||
|
@ -52,12 +53,12 @@ void QuickTimeProvider::InitQuickTime() {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
qt_err = EnterMovies();
|
qt_err = EnterMovies();
|
||||||
QTCheckError(qt_err, wxString(_T("EnterMovies() failed")));
|
QTCheckError(qt_err, wxString(_T("EnterMovies failed")));
|
||||||
|
|
||||||
// have we been inited before?
|
// have we been inited before?
|
||||||
if (qt_initcount <= 0) {
|
if (qt_initcount <= 0) {
|
||||||
// we haven't, allocate an offscreen graphics world
|
// We haven't, allocate an offscreen render target.
|
||||||
// we need to do this before we actually open anything, or quicktime may crash (heh)
|
// We need to do this before we actually open anything, or quicktime may crash. (heh)
|
||||||
Rect def_box;
|
Rect def_box;
|
||||||
def_box.top = 0;
|
def_box.top = 0;
|
||||||
def_box.left = 0;
|
def_box.left = 0;
|
||||||
|
@ -75,6 +76,7 @@ void QuickTimeProvider::InitQuickTime() {
|
||||||
|
|
||||||
void QuickTimeProvider::DeInitQuickTime() {
|
void QuickTimeProvider::DeInitQuickTime() {
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
|
// calls to InitializeQTML() must be balanced with an equal number of calls to TerminateQTML()
|
||||||
TerminateQTML();
|
TerminateQTML();
|
||||||
#endif
|
#endif
|
||||||
qt_initcount--;
|
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) {
|
void QuickTimeProvider::wxStringToDataRef(const wxString &string, Handle *dataref, OSType *dataref_type) {
|
||||||
// convert filename, first to a CFStringRef...
|
// convert filename, first to a CFStringRef...
|
||||||
wxString wx_filename = wxFileName(string).GetShortPath();
|
wxString wx_filename = wxFileName(string).GetShortPath();
|
||||||
|
@ -104,8 +107,13 @@ void QuickTimeProvider::QTCheckError(OSErr err, wxString errmsg) {
|
||||||
throw errmsg;
|
throw errmsg;
|
||||||
/* CheckError(err, errmsg.c_str()); // I wonder if this actually works on Mac, and if so, what it does */
|
/* 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) {
|
bool QuickTimeProvider::CanOpen(const Handle& dataref, const OSType dataref_type) {
|
||||||
Boolean can_open;
|
Boolean can_open;
|
||||||
Boolean prefer_img;
|
Boolean prefer_img;
|
||||||
|
|
|
@ -43,12 +43,10 @@
|
||||||
#include <wx/thread.h>
|
#include <wx/thread.h>
|
||||||
#include "include/aegisub/aegisub.h"
|
#include "include/aegisub/aegisub.h"
|
||||||
|
|
||||||
// qt stuff
|
// QT stuff
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
// avoid conflicts between MSVC's stdint.h and QT's stdint.h
|
#define _STDINT_H // avoid conflicts between MSVC's stdint.h and QT's stdint.h
|
||||||
#define _STDINT_H
|
#pragma warning(disable: 4004) // get MSVC to shut up about a macro redefinition in QT's ConditionalMacros.h
|
||||||
// get MSVC to shut up about a macro redefinition in QT's ConditionalMacros.h
|
|
||||||
#pragma warning(disable: 4004)
|
|
||||||
#endif
|
#endif
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
|
@ -68,7 +66,10 @@ public:
|
||||||
void DeInitQuickTime();
|
void DeInitQuickTime();
|
||||||
void wxStringToDataRef(const wxString &string, Handle *dataref, OSType *dataref_type);
|
void wxStringToDataRef(const wxString &string, Handle *dataref, OSType *dataref_type);
|
||||||
bool CanOpen(const Handle& dataref, const OSType dataref_type);
|
bool CanOpen(const Handle& dataref, const OSType dataref_type);
|
||||||
|
|
||||||
|
|
||||||
void QTCheckError(OSErr err, wxString errmsg);
|
void QTCheckError(OSErr err, wxString errmsg);
|
||||||
|
void QTCheckError(OSStatus err, wxString errmsg);
|
||||||
|
|
||||||
static int qt_initcount;
|
static int qt_initcount;
|
||||||
static GWorldPtr default_gworld;
|
static GWorldPtr default_gworld;
|
||||||
|
|
|
@ -140,8 +140,7 @@ void QuickTimeVideoProvider::LoadVideo(const wxString _filename) {
|
||||||
h = m_box.bottom;
|
h = m_box.bottom;
|
||||||
// allocate a new offscreen rendering buffer with the correct dimensions
|
// allocate a new offscreen rendering buffer with the correct dimensions
|
||||||
QDErr qd_err = NewGWorld(&gw, 32, &m_box, NULL, NULL, keepLocal);
|
QDErr qd_err = NewGWorld(&gw, 32, &m_box, NULL, NULL, keepLocal);
|
||||||
if (qd_err != noErr)
|
QTCheckError(qd_err, wxString(_T("Failed to initialize offscreen drawing buffer")));
|
||||||
throw wxString(_T("Failed to initialize offscreen drawing buffer"));
|
|
||||||
|
|
||||||
// select our new offscreen render target
|
// select our new offscreen render target
|
||||||
SetMovieGWorld(movie, gw, NULL);
|
SetMovieGWorld(movie, gw, NULL);
|
||||||
|
|
Loading…
Reference in a new issue