/** * Static function used to create an OpenAL buffer and upload raw PCM data to. * * @param InWave USoundWave to use as template and wave source * @param AudioDevice audio device to attach created buffer to * @return FXAudio2SoundBuffer pointer if buffer creation succeeded, NULL otherwise */ FXAudio2SoundBuffer* FXAudio2SoundBuffer::CreatePreviewBuffer( FXAudio2Device* XAudio2Device, USoundWave* Wave, FXAudio2SoundBuffer* Buffer ) { FAudioDeviceManager* AudioDeviceManager = GEngine->GetAudioDeviceManager(); check(AudioDeviceManager != nullptr); if (Buffer) { AudioDeviceManager->FreeBufferResource(Buffer); } // Create new buffer. Buffer = new FXAudio2SoundBuffer( XAudio2Device, SoundFormat_PCMPreview ); // Take ownership the PCM data Buffer->PCM.PCMData = Wave->RawPCMData; Buffer->PCM.PCMDataSize = Wave->RawPCMDataSize; Wave->RawPCMData = NULL; // Copy over whether this data should be freed on delete Buffer->bDynamicResource = Wave->bDynamicResource; Buffer->InitWaveFormatEx( WAVE_FORMAT_PCM, Wave, true ); AudioDeviceManager->TrackResource(Wave, Buffer); return( Buffer ); }
/** * Static function used to create an OpenAL buffer and upload decompressed ogg vorbis data to. * * @param InWave USoundWave to use as template and wave source * @param AudioDevice audio device to attach created buffer to * @return FXAudio2SoundBuffer pointer if buffer creation succeeded, NULL otherwise */ FXAudio2SoundBuffer* FXAudio2SoundBuffer::CreateNativeBuffer( FXAudio2Device* XAudio2Device, USoundWave* Wave ) { // Check to see if thread has finished decompressing on the other thread if( Wave->AudioDecompressor != NULL ) { Wave->AudioDecompressor->EnsureCompletion(); // Remove the decompressor delete Wave->AudioDecompressor; Wave->AudioDecompressor = NULL; } // Create new buffer. FXAudio2SoundBuffer* Buffer = new FXAudio2SoundBuffer( XAudio2Device, SoundFormat_PCM ); // Take ownership the PCM data Buffer->PCM.PCMData = Wave->RawPCMData; Buffer->PCM.PCMDataSize = Wave->RawPCMDataSize; Wave->RawPCMData = NULL; // Keep track of associated resource name. Buffer->InitWaveFormatEx( WAVE_FORMAT_PCM, Wave, true ); FAudioDeviceManager* AudioDeviceManager = GEngine->GetAudioDeviceManager(); check(AudioDeviceManager != nullptr); AudioDeviceManager->TrackResource(Wave, Buffer); Wave->RemoveAudioResource(); return( Buffer ); }
/** * Static function used to create an OpenAL buffer and upload raw PCM data to. * * @param InWave USoundWave to use as template and wave source * @param AudioDevice audio device to attach created buffer to * @return FCoreAudioSoundBuffer pointer if buffer creation succeeded, NULL otherwise */ FCoreAudioSoundBuffer* FCoreAudioSoundBuffer::CreatePreviewBuffer( FCoreAudioDevice* CoreAudioDevice, USoundWave* Wave, FCoreAudioSoundBuffer* Buffer ) { FAudioDeviceManager* AudioDeviceManager = GEngine->GetAudioDeviceManager(); check(AudioDeviceManager != nullptr); if (Buffer) { AudioDeviceManager->FreeBufferResource( Buffer ); } // Create new buffer. Buffer = new FCoreAudioSoundBuffer( CoreAudioDevice, SoundFormat_PCMPreview ); // Take ownership the PCM data Buffer->PCMData = Wave->RawPCMData; Buffer->PCMDataSize = Wave->RawPCMDataSize; Wave->RawPCMData = NULL; // Copy over whether this data should be freed on delete Buffer->bDynamicResource = Wave->bDynamicResource; Buffer->InitAudioStreamBasicDescription( kAudioFormatLinearPCM, Wave, true ); AudioDeviceManager->TrackResource( Wave, Buffer ); return( Buffer ); }
void USoundClass::PostLoad() { Super::PostLoad(); for (int32 ChildIndex = ChildClasses.Num()-1; ChildIndex >= 0; ChildIndex--) { if (ChildClasses[ChildIndex] != NULL && ChildClasses[ChildIndex]->GetLinkerUE4Version() < VER_UE4_SOUND_CLASS_GRAPH_EDITOR) { // first come, first served if (ChildClasses[ChildIndex]->ParentClass == nullptr) { ChildClasses[ChildIndex]->ParentClass = this; } // if already set, we can't be a parent of this child else if (ChildClasses[ChildIndex]->ParentClass != this) { UE_LOG(LogAudio, Warning, TEXT("SoundClass '%s' - '%s' removed from children as '%s' is its parent."), *GetName(), *ChildClasses[ChildIndex]->GetName(), *ChildClasses[ChildIndex]->ParentClass->GetName()); ChildClasses.RemoveAt(ChildIndex); } } } // Use the main/default audio device for storing and retrieving sound class properties FAudioDeviceManager* AudioDeviceManager = (GEngine ? GEngine->GetAudioDeviceManager() : nullptr); // Force the properties to be initialized for this SoundClass on all active audio devices if (AudioDeviceManager) { AudioDeviceManager->RegisterSoundClass(this); } }
FIOSAudioSoundBuffer* FIOSAudioSoundBuffer::CreateNativeBuffer(FIOSAudioDevice* IOSAudioDevice, USoundWave* InWave) { FWaveModInfo WaveInfo; InWave->InitAudioResource(IOSAudioDevice->GetRuntimeFormat(InWave)); if (!InWave->ResourceData || InWave->ResourceSize <= 0 || !WaveInfo.ReadWaveInfo(InWave->ResourceData, InWave->ResourceSize)) { InWave->RemoveAudioResource(); return NULL; } uint32 UncompressedBlockSize = 0; uint32 CompressedBlockSize = 0; const uint32 PreambleSize = 7; const uint32 BlockSize = *WaveInfo.pBlockAlign; switch (*WaveInfo.pFormatTag) { case SoundFormat_ADPCM: // (BlockSize - PreambleSize) * 2 (samples per byte) + 2 (preamble samples) UncompressedBlockSize = (2 + (BlockSize - PreambleSize) * 2) * sizeof(int16); CompressedBlockSize = BlockSize; if ((WaveInfo.SampleDataSize % CompressedBlockSize) != 0) { InWave->RemoveAudioResource(); return NULL; } break; case SoundFormat_LPCM: break; } // Create new buffer FIOSAudioSoundBuffer* Buffer = new FIOSAudioSoundBuffer(IOSAudioDevice, static_cast<ESoundFormat>(*WaveInfo.pFormatTag)); Buffer->NumChannels = InWave->NumChannels; Buffer->SampleRate = InWave->SampleRate; Buffer->UncompressedBlockSize = UncompressedBlockSize; Buffer->CompressedBlockSize = CompressedBlockSize; Buffer->BufferSize = WaveInfo.SampleDataSize; Buffer->SampleData = static_cast<int16*>(FMemory::Malloc(Buffer->BufferSize)); FMemory::Memcpy(Buffer->SampleData, WaveInfo.SampleDataStart, Buffer->BufferSize); FAudioDeviceManager* AudioDeviceManager = GEngine->GetAudioDeviceManager(); check(AudioDeviceManager != nullptr); AudioDeviceManager->TrackResource(InWave, Buffer); InWave->RemoveAudioResource(); return Buffer; }
void USoundWave::FreeResources() { check(IsInAudioThread()); // Housekeeping of stats DEC_FLOAT_STAT_BY( STAT_AudioBufferTime, Duration ); DEC_FLOAT_STAT_BY( STAT_AudioBufferTimeChannels, NumChannels * Duration ); // GEngine is NULL during script compilation and GEngine->Client and its audio device might be // destroyed first during the exit purge. if( GEngine && !GExitPurge ) { // Notify the audio device to free the bulk data associated with this wave. FAudioDeviceManager* AudioDeviceManager = GEngine->GetAudioDeviceManager(); if (AudioDeviceManager) { AudioDeviceManager->StopSoundsUsingResource(this); AudioDeviceManager->FreeResource(this); } } if (CachedRealtimeFirstBuffer) { FMemory::Free(CachedRealtimeFirstBuffer); CachedRealtimeFirstBuffer = nullptr; } // Just in case the data was created but never uploaded if (RawPCMData) { FMemory::Free(RawPCMData); RawPCMData = nullptr; } // Remove the compressed copy of the data RemoveAudioResource(); // Stat housekeeping DEC_DWORD_STAT_BY(STAT_AudioMemorySize, TrackedMemoryUsage); DEC_DWORD_STAT_BY(STAT_AudioMemory, TrackedMemoryUsage); TrackedMemoryUsage = 0; ResourceID = 0; bDynamicResource = false; DecompressionType = DTYPE_Setup; bDecompressedFromOgg = 0; USoundWave* SoundWave = this; FAudioThread::RunCommandOnGameThread([SoundWave]() { SoundWave->ResourceState = ESoundWaveResourceState::Freed; }, TStatId()); }
void USoundMix::BeginDestroy() { if (!GExitPurge && GEngine) { FAudioDeviceManager* AudioDeviceManager = GEngine->GetAudioDeviceManager(); if (AudioDeviceManager) { AudioDeviceManager->RemoveSoundMix(this); } } Super::BeginDestroy(); }
void FAudioDeviceManager::SetDebugSoloSoundCue(const TCHAR* SoundCue) { if (!IsInAudioThread()) { FAudioDeviceManager* AudioDeviceManager = this; FAudioThread::RunCommandOnAudioThread([AudioDeviceManager, SoundCue]() { AudioDeviceManager->SetDebugSoloSoundCue(SoundCue); }); return; } DebugNames.DebugSoloSoundCue = SoundCue; }
void FAudioDeviceManager::ToggleVisualize3dDebug() { if (!IsInAudioThread()) { FAudioDeviceManager* AudioDeviceManager = this; FAudioThread::RunCommandOnAudioThread([AudioDeviceManager]() { AudioDeviceManager->ToggleVisualize3dDebug(); }); return; } bVisualize3dDebug = !bVisualize3dDebug; }
void FAudioDeviceManager::TogglePlayAllDeviceAudio() { if (!IsInAudioThread()) { FAudioDeviceManager* AudioDeviceManager = this; FAudioThread::RunCommandOnAudioThread([AudioDeviceManager]() { AudioDeviceManager->TogglePlayAllDeviceAudio(); }); return; } bPlayAllDeviceAudio = !bPlayAllDeviceAudio; }
void FSoundSource::DrawDebugInfo() { // Draw 3d Debug information about this source, if enabled FAudioDeviceManager* DeviceManager = GEngine->GetAudioDeviceManager(); if (DeviceManager && DeviceManager->IsVisualizeDebug3dEnabled()) { const uint32 AudioComponentID = WaveInstance->ActiveSound->GetAudioComponentID(); if (AudioComponentID > 0) { DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.DrawSourceDebugInfo"), STAT_AudioDrawSourceDebugInfo, STATGROUP_TaskGraphTasks); USoundBase* Sound = WaveInstance->ActiveSound->GetSound(); const FVector Location = WaveInstance->Location; const bool bSpatialized = Buffer->NumChannels == 2 && WaveInstance->bUseSpatialization; const FVector LeftChannelSourceLoc = LeftChannelSourceLocation; const FVector RightChannelSourceLoc = RightChannelSourceLocation; FAudioThread::RunCommandOnGameThread([AudioComponentID, Sound, bSpatialized, Location, LeftChannelSourceLoc, RightChannelSourceLoc]() { UAudioComponent* AudioComponent = UAudioComponent::GetAudioComponentFromID(AudioComponentID); if (AudioComponent) { UWorld* SoundWorld = AudioComponent->GetWorld(); if (SoundWorld) { FRotator SoundRotation = AudioComponent->GetComponentRotation(); DrawDebugCrosshairs(SoundWorld, Location, SoundRotation, 20.0f, FColor::White, false, -1.0f, SDPG_Foreground); if (bSpatialized) { DrawDebugCrosshairs(SoundWorld, LeftChannelSourceLoc, SoundRotation, 20.0f, FColor::Red, false, -1.0f, SDPG_Foreground); DrawDebugCrosshairs(SoundWorld, RightChannelSourceLoc, SoundRotation, 20.0f, FColor::Green, false, -1.0f, SDPG_Foreground); } const FString Name = Sound->GetName(); DrawDebugString(SoundWorld, AudioComponent->GetComponentLocation() + FVector(0, 0, 32), *Name, nullptr, FColor::White, 0.033, false); } } }, GET_STATID(STAT_AudioDrawSourceDebugInfo)); } } }
void FAudioDeviceManager::RemoveSoundMix(USoundMix* SoundMix) { if (!IsInAudioThread()) { DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.RemoveSoundMix"), STAT_AudioRemoveSoundMix, STATGROUP_AudioThreadCommands); FAudioDeviceManager* AudioDeviceManager = this; FAudioThread::RunCommandOnAudioThread([AudioDeviceManager, SoundMix]() { AudioDeviceManager->RemoveSoundMix(SoundMix); }, GET_STATID(STAT_AudioRemoveSoundMix)); return; } for (FAudioDevice* AudioDevice : Devices) { if (AudioDevice) { AudioDevice->RemoveSoundMix(SoundMix); } } }
/** * Static function used to create a buffer. * * @param InWave USoundWave to use as template and wave source * @param AudioDevice audio device to attach created buffer to * @return FXAudio2SoundBuffer pointer if buffer creation succeeded, NULL otherwise */ FXAudio2SoundBuffer* FXAudio2SoundBuffer::Init( FAudioDevice* AudioDevice, USoundWave* Wave, bool bForceRealTime ) { // Can't create a buffer without any source data if( Wave == NULL || Wave->NumChannels == 0 ) { return( NULL ); } FAudioDeviceManager* AudioDeviceManager = GEngine->GetAudioDeviceManager(); FXAudio2Device* XAudio2Device = ( FXAudio2Device* )AudioDevice; FXAudio2SoundBuffer* Buffer = NULL; // Allow the precache to happen if necessary EDecompressionType DecompressionType = Wave->DecompressionType; if (bForceRealTime && DecompressionType != DTYPE_Setup && DecompressionType != DTYPE_Streaming) { DecompressionType = DTYPE_RealTime; } switch( DecompressionType ) { case DTYPE_Setup: // Has circumvented precache mechanism - precache now AudioDevice->Precache(Wave, true, false); // if it didn't change, we will recurse forever check(Wave->DecompressionType != DTYPE_Setup); // Recall this function with new decompression type return( Init( AudioDevice, Wave, bForceRealTime ) ); case DTYPE_Preview: // Find the existing buffer if any if( Wave->ResourceID ) { Buffer = (FXAudio2SoundBuffer*)AudioDeviceManager->GetSoundBufferForResourceID(Wave->ResourceID); } // Override with any new PCM data even if some already exists. if( Wave->RawPCMData ) { // Upload the preview PCM data to it Buffer = CreatePreviewBuffer( XAudio2Device, Wave, Buffer ); } break; case DTYPE_Procedural: // Always create a new buffer for streaming procedural data Buffer = CreateProceduralBuffer( XAudio2Device, Wave ); break; case DTYPE_RealTime: // Always create a new buffer for streaming ogg vorbis data Buffer = CreateQueuedBuffer( XAudio2Device, Wave ); break; case DTYPE_Native: case DTYPE_Xenon: // Upload entire wav to XAudio2 if( Wave->ResourceID ) { Buffer = (FXAudio2SoundBuffer*)AudioDeviceManager->GetSoundBufferForResourceID(Wave->ResourceID); } if( Buffer == NULL ) { Buffer = CreateNativeBuffer( XAudio2Device, Wave ); } break; case DTYPE_Streaming: // Always create a new buffer for streaming sounds Buffer = CreateStreamingBuffer( XAudio2Device, Wave ); break; case DTYPE_Invalid: default: // Invalid will be set if the wave cannot be played break; } return( Buffer ); }
UObject* UMP3SoundFactory::FactoryCreateBinary(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, const TCHAR* Type, const uint8*& Buffer, const uint8* BufferEnd, FFeedbackContext* Warn) { FEditorDelegates::OnAssetPreImport.Broadcast(this, Class, InParent, Name, Type); if (mpg123_init == nullptr) { Warn->Logf(ELogVerbosity::Error, TEXT("Function pointer was null. Was %s found?"), DLL_NAME); FEditorDelegates::OnAssetPostImport.Broadcast(this, nullptr); return nullptr; } // if the sound already exists, remember the user settings USoundWave* ExistingSound = FindObject<USoundWave>(InParent, *Name.ToString()); // stop playing the file, if it already exists (e.g. reimport) TArray<UAudioComponent*> ComponentsToRestart; FAudioDeviceManager* AudioDeviceManager = GEngine->GetAudioDeviceManager(); if (AudioDeviceManager && ExistingSound) { AudioDeviceManager->StopSoundsUsingResource(ExistingSound, ComponentsToRestart); } // Read the mp3 header and make sure we have valid data UMP3Decoder Decoder(Warn); Decoder.Init(Buffer, BufferEnd); if (Decoder.BitsPerSample != 16) { Warn->Logf(ELogVerbosity::Error, TEXT("Unreal only supports 16bit WAVE data (%s)."), *Name.ToString()); FEditorDelegates::OnAssetPostImport.Broadcast(this, nullptr); return nullptr; } if (Decoder.Channels != 1 && Decoder.Channels != 2) { Warn->Logf(ELogVerbosity::Error, TEXT("Unreal only supports 1-2 channel WAVE data (Mono/Stereo). (%s)."), *Name.ToString()); FEditorDelegates::OnAssetPostImport.Broadcast(this, nullptr); return nullptr; } //on reimport, reuse settings, wipe data. otherwise create new. (UE4 WAVE import has some more checks, maybe implement, too?) USoundWave* Sound; if (ExistingSound && bMP3SoundFactoryIsReimport) { Sound = ExistingSound; Sound->FreeResources(); Sound->InvalidateCompressedData(); } else { Sound = NewObject<USoundWave>(InParent, Name, Flags); } Sound->AssetImportData->Update(GetCurrentFilename()); TArray<uint8> RawWavBuffer; RawWavBuffer.Reserve((BufferEnd - Buffer) * 16); //actual decoding Decoder.Decode(RawWavBuffer); Sound->RawData.Lock(LOCK_READ_WRITE); void* LockedData = Sound->RawData.Realloc(RawWavBuffer.Num() * RawWavBuffer.GetTypeSize()); FMemory::Memcpy(LockedData, RawWavBuffer.GetData(), RawWavBuffer.Num() * RawWavBuffer.GetTypeSize()); Sound->RawData.Unlock(); RawWavBuffer.Empty(); // Calculate duration. Sound->Duration = (float)Decoder.SizeInBytes / Decoder.Samplerate / Decoder.Channels / (BITS_PER_SAMPLE / 8); Sound->SampleRate = Decoder.Samplerate; Sound->NumChannels = Decoder.Channels; Sound->RawPCMDataSize = Decoder.SizeInBytes; FEditorDelegates::OnAssetPostImport.Broadcast(this, Sound); if (ExistingSound) { Sound->PostEditChange(); } for (int32 ComponentIndex = 0; ComponentIndex < ComponentsToRestart.Num(); ++ComponentIndex) { ComponentsToRestart[ComponentIndex]->Play(); } return Sound; }
FALSoundBuffer* FALSoundBuffer::CreateNativeBuffer( FALAudioDevice* AudioDevice, USoundWave* Wave) { SCOPE_CYCLE_COUNTER( STAT_AudioResourceCreationTime ); // This code is not relevant for now on HTML5 but adding this for consistency with other platforms. // Check to see if thread has finished decompressing on the other thread if (Wave->AudioDecompressor != NULL) { Wave->AudioDecompressor->EnsureCompletion(); // Remove the decompressor delete Wave->AudioDecompressor; Wave->AudioDecompressor = NULL; } // Can't create a buffer without any source data if( Wave == NULL || Wave->NumChannels == 0 ) { return( NULL ); } Wave->InitAudioResource(AudioDevice->GetRuntimeFormat(Wave)); FALSoundBuffer* Buffer = NULL; FAudioDeviceManager* AudioDeviceManager = GEngine->GetAudioDeviceManager(); check(AudioDeviceManager != nullptr); // Find the existing buffer if any if( Wave->ResourceID ) { Buffer = static_cast<FALSoundBuffer*>(AudioDeviceManager->WaveBufferMap.FindRef(Wave->ResourceID)); } if( Buffer == NULL ) { // Create new buffer. Buffer = new FALSoundBuffer( AudioDevice ); alGenBuffers( 1, Buffer->BufferIds ); AudioDevice->alError( TEXT( "RegisterSound" ) ); AudioDeviceManager->TrackResource(Wave, Buffer); Buffer->InternalFormat = AudioDevice->GetInternalFormat( Wave->NumChannels ); Buffer->NumChannels = Wave->NumChannels; Buffer->SampleRate = Wave->SampleRate; if (Wave->RawPCMData) { // upload it Buffer->BufferSize = Wave->RawPCMDataSize; alBufferData( Buffer->BufferIds[0], Buffer->InternalFormat, Wave->RawPCMData, Wave->RawPCMDataSize, Buffer->SampleRate ); // Free up the data if necessary if( Wave->bDynamicResource ) { FMemory::Free( Wave->RawPCMData ); Wave->RawPCMData = NULL; Wave->bDynamicResource = false; } } else { // get the raw data uint8* SoundData = ( uint8* )Wave->RawData.Lock( LOCK_READ_ONLY ); // it's (possibly) a pointer to a wave file, so skip over the header int SoundDataSize = Wave->RawData.GetBulkDataSize(); // is there a wave header? FWaveModInfo WaveInfo; if (WaveInfo.ReadWaveInfo(SoundData, SoundDataSize)) { // if so, modify the location and size of the sound data based on header SoundData = WaveInfo.SampleDataStart; SoundDataSize = WaveInfo.SampleDataSize; } // let the Buffer know the final size Buffer->BufferSize = SoundDataSize; // upload it alBufferData( Buffer->BufferIds[0], Buffer->InternalFormat, SoundData, Buffer->BufferSize, Buffer->SampleRate ); // unload it Wave->RawData.Unlock(); } if( AudioDevice->alError( TEXT( "RegisterSound (buffer data)" ) ) || ( Buffer->BufferSize == 0 ) ) { Buffer->InternalFormat = 0; } if( Buffer->InternalFormat == 0 ) { UE_LOG ( LogAudio, Log,TEXT( "Audio: sound format not supported for '%s' (%d)" ), *Wave->GetName(), Wave->NumChannels ); delete Buffer; Buffer = NULL; } } return Buffer; }