首先说明,这是一个非常hack的做法,非常有可能因引擎版本的升级等原因带来不稳定性,并且项目打包后可能无法得到预期结果,但在编辑器中播放是可以的。读者应把本文的重点放在解决这个问题的过程以及一些开发中用到的具体技术。我做这件事情的原因是为了获得角色播放的语音并实现一些语音可视化的功能。本文内容基于UE 4.14版本。

1、找到语音在代码中的位置

使语音播放的地方是我通过给源码加断点找到的。应用播放声音,在底层都需要引擎帮其拿到Audio Device,所以在LevelActor.cpp文件第1322行加上断点,然后调试带有序列器的应用,在断点处程序暂停,通过调用堆栈就可以看到程序是怎样一步步到达底层声音播放的地方。此调用堆栈如下所示:

> UE4Editor-Engine.dll!UWorld::GetAudioDevice() 行 1322    C++UE4Editor-MovieSceneTracks.dll!FMovieSceneAudioTrackInstance::Update(EMovieSceneUpdateData & UpdateData, const TArray<TWeakObjectPtr<UObject,FWeakObjectPtr>,FDefaultAllocator> & RuntimeObjects, IMovieScenePlayer & Player, FMovieSceneSequenceInstance & SequenceInstance) 行 145    C++UE4Editor-MovieScene.dll!FMovieSceneSequenceInstance::UpdatePassSingle(EMovieSceneUpdateData & UpdateData, IMovieScenePlayer & Player) 行 255   C++UE4Editor-MovieScene.dll!FMovieSceneSequenceInstance::Update(EMovieSceneUpdateData & UpdateData, IMovieScenePlayer & Player) 行 184 C++UE4Editor-LevelSequence.dll!ULevelSequencePlayer::UpdateMovieSceneInstance(float CurrentPosition, float PreviousPosition) 行 619    C++UE4Editor-Engine.dll!AActor::TickActor(float DeltaSeconds, ELevelTick TickType, FActorTickFunction & ThisTickFunction) 行 834   C++UE4Editor-Engine.dll!FActorTickFunction::ExecuteTick(float DeltaTime, ELevelTick TickType, ENamedThreads::Type CurrentThread, const TRefCountPtr<FGraphEvent> & MyCompletionGraphEvent) 行 113    C++UE4Editor-Engine.dll!FTickFunctionTask::DoTask(ENamedThreads::Type CurrentThread, const TRefCountPtr<FGraphEvent> & MyCompletionGraphEvent) 行 256    C++UE4Editor-Engine.dll!TGraphTask<FTickFunctionTask>::ExecuteTask(TArray<FBaseGraphTask *,FDefaultAllocator> & NewTasks, ENamedThreads::Type CurrentThread) 行 868    C++UE4Editor-Core.dll!FNamedTaskThread::ProcessTasksNamedThread(int QueueIndex, bool bAllowStall) 行 932   C++UE4Editor-Core.dll!FNamedTaskThread::ProcessTasksUntilQuit(int QueueIndex) 行 679   C++UE4Editor-Core.dll!FTaskGraphImplementation::WaitUntilTasksComplete(const TArray<TRefCountPtr<FGraphEvent>,TInlineAllocator<4,FDefaultAllocator> > & Tasks, ENamedThreads::Type CurrentThreadIfKnown) 行 1776 C++UE4Editor-Engine.dll!FTickTaskSequencer::ReleaseTickGroup(ETickingGroup WorldTickGroup, bool bBlockTillComplete) 行 525 C++UE4Editor-Engine.dll!FTickTaskManager::RunTickGroup(ETickingGroup Group, bool bBlockTillComplete) 行 1437   C++UE4Editor-Engine.dll!UWorld::RunTickGroup(ETickingGroup Group, bool bBlockTillComplete) 行 730  C++UE4Editor-Engine.dll!UWorld::Tick(ELevelTick TickType, float DeltaSeconds) 行 1340  C++UE4Editor-UnrealEd.dll!UEditorEngine::Tick(float DeltaSeconds, bool bIdleMode) 行 1422  C++UE4Editor-UnrealEd.dll!UUnrealEdEngine::Tick(float DeltaSeconds, bool bIdleMode) 行 371 C++UE4Editor.exe!FEngineLoop::Tick() 行 2859   C++

从调用堆栈可以看出,序列器上和语音直接相关的类就是FMovieSceneAudioTrackInstance。从源码上看其主要成员如下:

private:/** Plays the sound of the given section at the given time */void PlaySound(class UMovieSceneAudioSection* AudioSection, TWeakObjectPtr<UAudioComponent> Component, float Time);/** Stops all components for the given row index */void StopSound(int32 RowIndex);/** Stops all components from playing */void StopAllSounds();/** Gets the audio component component for the actor and row index, creating it if necessary */TWeakObjectPtr<UAudioComponent> GetAudioComponent(IMovieScenePlayer& Player, AActor* Actor, int32 RowIndex);/** Utility function for getting actors from objects array */TArray<AActor*> GetRuntimeActors(const TArray<TWeakObjectPtr<UObject>>& RuntimeObjects) const;private:/** Track that is being instanced */UMovieSceneAudioTrack* AudioTrack;/** Audio components to play our audio tracks with, one per row per actor */TArray< TMap<AActor*, TWeakObjectPtr<UAudioComponent> > > PlaybackAudioComponents;

而UMovieSceneAudioTrack的成员如下:

public:/** Adds a new sound cue to the audio */virtual void AddNewSound(USoundBase* Sound, float Time);/** @return The audio sections on this track */const TArray<UMovieSceneSection*>& GetAudioSections() const{return AudioSections;}/** @return true if this is a master audio track */bool IsAMasterTrack() const;public:// UMovieSceneTrack interfacevirtual TSharedPtr<IMovieSceneTrackInstance> CreateInstance() override;virtual void RemoveAllAnimationData() override;virtual bool HasSection(const UMovieSceneSection& Section) const override;virtual void AddSection(UMovieSceneSection& Section) override;virtual void RemoveSection(UMovieSceneSection& Section) override;virtual bool IsEmpty() const override;virtual TRange<float> GetSectionBoundaries() const override;virtual const TArray<UMovieSceneSection*>& GetAllSections() const override;virtual bool SupportsMultipleRows() const override;private:/** List of all master audio sections */UPROPERTY()TArray<UMovieSceneSection*> AudioSections;

经调试发现,正是私有成员AudioSections保存了语音信息。

2、获取语音

首先,我们可以获取世界中所有的ALevelSequenceActor,而从ALevelSequenceActor我们可以得到它正在运行的实例FMovieSceneSequenceInstance,这个过程都能够用相关类的公有函数实现,代码基本形式如下:

for (TActorIterator<ALevelSequenceActor> iter(GetWorld()); iter; ++iter){ALevelSequenceActor* seq = *iter;ULevelSequencePlayer* player = seq->SequencePlayer;if (player == nullptr){continue;}if (!player->IsPlaying()){continue;}TSharedRef<FMovieSceneSequenceInstance> movieSceneInstance = ((IMovieScenePlayer*)player)->GetRootMovieSceneSequenceInstance();
}

那么如何从movieSceneInstance拿到AudioSections呢?这里实际上有这样一个调用的过程(所有的调试读者若有兴趣,可自行尝试一下):

(1) movieSceneInstance → TMap<FGuid, FMovieSceneObjectBindingInstance> ObjectBindingInstances

ObjectBindingInstances就是此序列器上所有Object的实例,如下图的序列器,其ObjectBindingInstances是BP_BulletStable、C02S01_BP和Guide Cine各自的轨道实例。最上面的 Shots是被嵌套的另一个序列器,在此序列器实例上是其成员变量MasterTrackInstances。

(2) FMovieSceneObjectBindingInstance ObjectBindingInstance → FMovieSceneInstanceMap TrackInstances

TSharedPtr<IMovieSceneTrackInstance> TrackInstance → UMovieSceneAudioTrack* AudioTrack

TrackInstances是相应Object下面的子轨道,比如在上面的序列器中,C02S01_BP有三条子轨道,分别是生成的、Transform和Cine Command。

(3) AudioTrack → TArray<UMovieSceneSection*> AudioSections

现在我们能够从movieSceneInstance找到AudioSections,但问题是,在上面的过程中ObjectBindingInstances和AudioTrack都是相应类的私有成员,并不能直接得到。那么这里就用到了比较hack的方法。

C++编译器对类编译时,如果类成员结构不变,成员变量相对类实例的地址偏移是固定的。因此我们可以通过实例地址计算成员变量的地址,然后将其取出。地址偏移是调试时通过添加监视得到的。下面是得到ObjectBindingInstances的代码:

    uint8_t* p = reinterpret_cast<uint8_t*>(&MovieSceneSequenceInstance);TMap<FGuid, FMovieSceneObjectBindingInstanceMock>* ObjectBindingInstances = (TMap<FGuid, FMovieSceneObjectBindingInstanceMock>*)(p + 0x78);

FMovieSceneObjectBindingInstanceMock的定义实际上和FMovieSceneObjectBindingInstance相同,重新定义是因为FMovieSceneObjectBindingInstance是一个私有结构体类型,不能在外部使用。这是第二个hack的地方,即成员相同但名称不同的类型可以相互转换。

3、判断某段语音是否在播放

要判断当前是否在播放语音,需要拿到AudioComponent,它在FMovieSceneAudioTrackInstance类中,即PlaybackAudioComponents,也是私有成员。

而在第二部分内容中,我们看到AudioSections是一个Array,即一个轨道上可能有多段语音,那么需要判断哪段语音在播放。每个AudioSection都有它的起始时间和结束时间,同时ULevelSequencePlayer里含有TimeCursorPosition私有成员,即当前序列器播放的时间。从而我们能得到正在播放的那段语音。ULevelSequencePlayer的部分成员如下:

private:/** The level sequence to play. */UPROPERTY(transient)ULevelSequence* LevelSequence;/** The level sequence player. */UPROPERTY(transient)ULevelSequencePlayer* CurrentPlayer;/** Whether we're currently playing. If false, then sequence playback is paused or was never started. */UPROPERTY()bool bIsPlaying;/** Whether we're currently playing in reverse. */UPROPERTY()bool bReversePlayback;/** True where we're waiting for the first update of the sequence after calling StartPlayingNextTick. */bool bPendingFirstUpdate;/** The current time cursor position within the sequence (in seconds) */UPROPERTY()float TimeCursorPosition;/** The time cursor position in the previous update. */float LastCursorPosition;/** Time time at which to start playing the sequence (defaults to the lower bound of the sequence's play range) */float StartTime;/** Time time at which to end playing the sequence (defaults to the upper bound of the sequence's play range) */float EndTime;/** Specific playback settings for the animation. */UPROPERTY()FLevelSequencePlaybackSettings PlaybackSettings;/** The number of times we have looped in the current playback */int32 CurrentNumLoops;/** Whether this player has cleaned up the level sequence after it has stopped playing or not */bool bHasCleanedUpSequence;

此功能的完整代码如下:

USoundWave* AGuideCine::FindSound(TSharedPtr<FMovieSceneSequenceInstance> MovieSceneSequenceInstancePtr, ULevelSequencePlayer* SequencePlayer)
{uint8_t* p = reinterpret_cast<uint8_t*>((uint64_t*)SequencePlayer);float SequenceStartTime = *(float*)(p + 0x74);uint8_t* p2 = reinterpret_cast<uint8_t*>(&MovieSceneSequenceInstancePtr);p2 = reinterpret_cast<uint8_t*>(*((uint64_t*)p2));TMap<FGuid, FMovieSceneObjectBindingInstanceMock>* ObjectBindingInstances = (TMap<FGuid, FMovieSceneObjectBindingInstanceMock>*)(p2 + 0x78);for (auto& ObjectBindingInstance : *ObjectBindingInstances){TArray<TWeakObjectPtr<UObject>> RuntimeObjects = ObjectBindingInstance.Value.RuntimeObjects;//Find the binding instance of the specific objectif (RuntimeObjects.Num() == 1 && RuntimeObjects[0]->GetName().Contains(ObjectNameInSequencer)){FMovieSceneInstanceMap TrackInstances = ObjectBindingInstance.Value.TrackInstances;for (auto& TrackInstance : TrackInstances){const FString MovieSceneAudioTrackStr = "MovieSceneAudioTrack";//Find the audio track of the specific objectif (TrackInstance.Key->GetName().Contains(MovieSceneAudioTrackStr)){uint8_t* p3 = reinterpret_cast<uint8_t*>(&TrackInstance.Value);p3 = reinterpret_cast<uint8_t*>(*((uint64_t*)p3));UMovieSceneAudioTrack* AudioTrack = *(UMovieSceneAudioTrack**)(p3 + 0x8);TArray<UMovieSceneSection*> AudioSections = AudioTrack->GetAllSections();TArray<TMap<AActor*, TWeakObjectPtr<UAudioComponent>>> AudioMapArray = *((TArray<TMap<AActor*, TWeakObjectPtr<UAudioComponent>>>*)(p3 + 0x10));for (int32 i = 0; i < AudioMapArray.Num(); i++){TMap<AActor*, TWeakObjectPtr<UAudioComponent>> AudioMap = AudioMapArray[i];for (auto Audio : AudioMap){TWeakObjectPtr<UAudioComponent> AudioComponent = Audio.Value;if (AudioComponent->IsPlaying()){float TimeCursorPosition = *(float*)(p + 0x6c);for (int32 j = 0; j < AudioSections.Num(); j++){UMovieSceneAudioSection* AudioSection = (UMovieSceneAudioSection*)AudioSections[j];//Find the audio section which is being playedif (AudioSection->GetStartTime() <= TimeCursorPosition + SequenceStartTime&& AudioSection->GetEndTime() >= TimeCursorPosition + SequenceStartTime){USoundBase* SoundBase = AudioSection->GetSound();USoundWave* Sound = Cast<USoundWave>(SoundBase);if (!Sound)     //Sound played in sequencer is of class USoundCue.{USoundCue* SoundCue = Cast<USoundCue>(SoundBase);USoundNodeWavePlayer* SoundNodeWavePlayer = Cast<USoundNodeWavePlayer>(SoundCue->FirstNode);Sound = SoundNodeWavePlayer->GetSoundWave();}return Sound;}}}}}break;}}break;}}return nullptr;
}

Unreal 4 C++开发,获取序列器上物体播放的自身音轨上的语音相关推荐

  1. python 月球上物体的体重,1.重量计算。月球上物体体重是在地球上的16.5%,假如你在地球上每年增长0.5KG,输出未来十年你在地球和月球上的体重状况...

    [判断题]正常产后 12 小时内应让产妇排尿. [多选题]下列哪些著作不属于古代本草著作( ) [单选题]融化巧克力正确的方法是? [单选题]急性湿疹在何种情况下可使病情恶化加重 [单选题]The a ...

  2. Android音乐播放器开发(5)—播放界面(播放、暂停、上一首、下一首,顺序播放、随机播放、拖拽进度条…)

    1. 说明 源码已同步到Gitee仓库,Github仓库,觉得还不错的话帮忙点个"star"吧,非常感谢. Android播放器专栏其它文章: 服务端:Android音乐播放器开发 ...

  3. Spring Boot(5) web开发(3)拦截器、文件上传、异常处理

    Spring Boot(5) web开发(3)拦截器.文件上传.异常处理 学习视频: https://www.bilibili.com/video/BV19K4y1L7MT?p=49&spm_ ...

  4. UG\NX二次开发 获取曲线上某个位置的点坐标、切线矢量、主法线矢量、副法线矢量 UF_MODL_ask_curve_props

    文章作者:里海 来源网站:https://blog.csdn.net/WangPaiFeiXingYuan 简介: UG\NX二次开发 获取曲线上某个位置的点坐标.切线矢量.主法线矢量.副法线矢量 U ...

  5. 获取Android手机上音乐播放器状态

    最近在公司做锁屏项目,需要在锁屏界面上显示音乐播放器的状态,类似小米的锁屏界面,网上也有类似的功能介绍,我在这里做个总结吧. 首先说下,这个数据信息是音乐播放器中service(com.android ...

  6. oracle获取序列跳号,Oracle sequence跳号知多少

    Sequence是oracle中的一个非常常用的功能,开发经常会频繁使用.但是在生产环境中经常有应用反馈通过sequence生成的自增主键会出现不连续跳号的现象,而且是几十个几十个地跳,为了弄清楚se ...

  7. oracle获取序列并赋值,Oracle中序列的使用

    数据库设计的三大范式第一条就是独立的表结构中必须有唯一主键来标识表中数据.在以往微软的SQL Server(duo版本)平台上.手动编码实现表中主键.并设定为自增列是极其简单.编码如下: typeid ...

  8. Android开发之视频播放器

    Android开发之视频播放器 一.效果图 二.build.gradle中导入依赖 三.主布局文件(activity_video) 四.布局文件(video_item) 五.布局文件(activity ...

  9. 搭建webassembly网页播放器(五)---网页播放器开发

    在前面的章节中,我们解决emcc环境以及使用emcc来编译ffmpeg得到网页开发中可以使用的js库,本章节,我们就来实现一个简单的播放器. 视频课程以及源码下载: https://edu.csdn. ...

最新文章

  1. Class.isAssignableFrom(Class clz)与instanceof与Class.isInstance(Object obj) 的区别和联系
  2. Oracle 的原理: 索引
  3. centos7输入systemctl status network.service出现Unit network.service could not be found的解决办法
  4. C++ set的一些用法
  5. 漫画:学习中台,看这篇就够了
  6. 《树莓派开发实战(第2版)》——2.9 利用RDP远程控制树莓派
  7. Python_Python处理JSON文件
  8. Solr分析器IK-analyzer配置及错误java.lang.AbstractMethodError解决
  9. 4G5G学习过程中整理的专业名词的符号简称
  10. 高斯滤波 python
  11. springboot图片验证码
  12. Python实现异方差检验(statsmodels)
  13. youtube上下载vr立体声视频及其处理
  14. 【备忘】尚学堂白贺翔java互联网架构师视频教程下载
  15. 关于国产数据库,不得不谈一下“数据库四小龙”
  16. XXljob 使用教程(springboot)
  17. 【python数据类型】
  18. python的argc与argv
  19. 网页引用优酷视频并添加封面自动播放
  20. 【ArcGIS风暴】ArcGIS tif转jpg:JPEG压缩仅支持8位或16位无符号数据(具有一个或三个波段,且没有色彩映射表)解决方案!

热门文章

  1. django中bulk_create返回id的三种实现
  2. SEO建站优化教程,可视化SEO建站优化
  3. Reat-router路由传参
  4. 控制台五子棋java源代码_两套 五子棋小游戏源码(控制台+JavaSWing)
  5. HTML5 CSS3 下拉菜单效果
  6. 那些散落在风中的密码
  7. Siemens PPI协议分析
  8. Dimension finie有限元-ECPKn
  9. 混淆问题(常见问题汇总)
  10. python中整数四舍五入的方法