Wwise中的流管理系统

Stream Manager In Wwise

Posted by 李AA on August 7, 2019

前言

  • Q: 本文讨论初衷?

  • 刚接触Wwise的文件和数据流管理系统时,一般无法理清各模块的作用和关系。虽然不妨碍使用(Wwise提供了一些实现文件),但是对于后期在文件管理的自定义或者优化方面会有阻碍。

  • Q: 本文讨论主题?

  • 流管理器(Stream Manager)的底层和高层模块,以及他们之间的协调使用,本文不讨论底层模块的重写实现。

结构

  • 在游戏引擎中的架构

  • StreamManager UML

  • Q: 有几组公共接口?
  • Wwise在结构上定义了两组。底层接口IAkLowLevelIO(LowLevelO)以及高层接口IAkStreamMgr(WwiseIO)。但是可以完全替换为自己的流管理架构。

  • Q: LowLevelIO模块作用?
    1. 解析文件在磁盘中的位置
    2. Stream Manager用到的调度操作定义
  • Q: LowLevelIO模块有哪几部分?
    1. IO Hook
    2. File Location Resolver
    3. Stream Manager
  • Q: WwiseIO系统的创建步骤?
    1. 实现IAkLowLevelHook(IAkIOHookBlocking或者IAkIOHookDefereed),并创建IOHook对象
    2. 实现接口文件IAkFileLocationResolver,创建唯一的File Location Resolver对象。
    3. 创建Stream Manager对象, 注册File Location Resolver对象
    4. 创建LowLevelIO
    5. 创建Streaming Device并与LowLevelIO挂钩,在Stream Manager中注册对象
    6. Stream Manager创建标准流或者自动流, StreamingDevice播放

IO Hook

  • Q:模块作用?
  • 平台IO API对接,作为Stream Manager和平台IO的中间层,提供文件数据的传输。如果游戏引擎已经有了IO模块,推荐用引擎的IO来实现IO Hook。

  • Q:相关文件有哪些?
  • 接口文件
    • “SDK\include\AK\SoundEngine\Common\AkStreamMgrModule.h”
  • IOHook实现(平台相关WwiseIO文件)
    • “samples\SoundEngine{Platform name}\AkDefaultIOHookBlocking.h”
    • “samples\SoundEngine{Platform name}\AkDefaultIOHookBlocking.cpp”
    • “samples\SoundEngine{Platform name}\AkDefaultIOHookDeferred.h”
    • “samples\SoundEngine{Platform name}\AkDefaultIOHookDeferred.cpp”
  • 多设备IOHook实现
    • “samples\SoundEngine\Common\AkDefaultLowLevelIODispatcher.h”
    • “samples\SoundEngine\Common\AkDefaultLowLevelIODispatcher.cpp”
  • FilePackage模板(增加对.pck支持的LowLevelIO,模板类)
    • “samples\SoundEngine\Common\AkFilePackageLowLevelIO.h”
    • “samples\SoundEngine\Common\AkFilePackageLowLevelIO.inl”
    • “samples\SoundEngine\Common\AkFilePackage.h”
    • “samples\SoundEngine\Common\AkFilePackage.cpp”
    • “samples\SoundEngine\Common\AkFilePackageLUT.h”
    • “samples\SoundEngine\Common\AkFilePackageLUT.cpp”
  • FilePackage实现(平台相关包处理类, 用FilePackage模板类和平台相关WwiseIO来实例化模板)
    • “samples\SoundEngine{Platform name}\AkFilePackageLowLevelIOBlocking.h”
    • “samples\SoundEngine{Platform name}\AkFilePackageLowLevelIODeferred.h”
  • 重要接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Reads data from a file (synchronous).
virtual AKRESULT Read(
		AkFileDesc& in_fileDesc,    
		const AkIoHeuristics& in_heuristics,
		void* out_pBuffer, 
		AkIOTransferInfo& in_transferInfo)

// Writes data to a file (synchronous). 
virtual AKRESULT Write(
		AkFileDesc& in_fileDesc,
		const AkIoHeuristics& in_heuristics,
		void* in_pData,
		AkIOTransferInfo& io_transferInfo)

File Location Resolver

  • 模块作用?
  • 负责将文件名称和文件ID映射到文件描述符,填充AkFileDesc结构体
  • Q:相关文件有哪些?
  • 接口文件
    • “SDK\include\AK\SoundEngine\Common\AkStreamMgrModule.h”
  • FileLocationResolver实现
    • “samples\SoundEngine\Common\AkFileLocationBase.h”
    • “samples\SoundEngine\Common\AkFileLocationBase.cpp”
  • 重要接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Returns a file descriptor for a given file name (string).
virtual AKRESULT Open(
		const AkOSChar*	in_pszFileName,
		AkOpenMode in_eOpenMode,
		AkFileSystemFlags* in_pFlags,
		bool& io_bSyncOpen,
		AkFileDesc& io_fileDesc)

// Returns a file descriptor for a given file ID.
virtual AKRESULT Open(
		AkFileID in_fileID,
		AkOpenMode in_eOpenMode,
		AkFileSystemFlags* in_pFlags,
		bool& io_bSyncOpen,
		AkFileDesc& io_fileDesc)

Stream Manager

  • Q: StreamManager模块作用
    1. 独立于平台,将平台底层操作系统硬件中的文件或其他对象的句柄封装成流文件,可以理解为流创建器
    2. 声音引擎通过StreamManager获取SoundBank和流音频文件,甚至可以用作游戏引擎的IO模块
    3. 若需要定义自己的StreamManager,重写IAkStreamMgr里面的接口便可以
  • Q: 相关文件有哪些?
  • 接口文件
    • “SDK\include\AK\SoundEngine\Common\IAkStreamMgr.h”
  • Q: StreamManager创建的流是什么?
  • 流创建在底层调用的是IAkFileLocationResolver::Open(),然后通过一些流参数设置来控制读写的方式,主要分为标准流和自动流两种。
    1. 标准流
      • 调用AK::IAkStreamMgr::CreateStd()将创建标准流对象
      • 调用流对象的AK::IAkStdStream::Read()AK::IAkStdStream::Write()将读写请求传入请求队列。
      • 调用AK::IAkAutoStream::GetPosition()获取流指针位置,AK::IAkAutoStream::SetPosition()设置流指针位置。
    1. 自由流
      • 仅用于输入,无需显式调用接口,调用 AK::IAkStreamMgr::CreateAuto() 将创建一个自动流对象。
      • 调用流对象AK::IAkAutoStream::Start()时开始将执行 I/O 请求的自动调度。调用AK::IAkAutoStream::Stop()停止流。
      • 调用AK::IAkAutoStream::GetBuffer()
  • Q: 常用的StreamManager操作?

  • 下面wrap了一个流管理器的例子,涉及一些常用操作

  • 标准流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
//                              这里包装了一个标准流和一个自动流类型

//--------------------------------------------标准流-------------------------------------
class StreamStd
{
public:

	StreamStd(const AkOSChar* in_pszFileName, int bufferSize) :m_pszFileName(in_pszFileName), m_pStream(nullptr)
	{
		m_pStreamMgr = AK::IAkStreamMgr::Get();
	}

	//  流创建
	AKRESULT CreateStreamStd();
	//  Get流对象
	AK::IAkStdStream* GetStream() { if (m_pStream) return m_pStream; }
	//  同步读取
	AKRESULT ReadSync(void* pBuffer, AkUInt32 out_uSize);
	//  异步读取
	AKRESULT ReadAsync(void* pBuffer, AkUInt32 out_uSize);
	//  设置指针位置
	AKRESULT SetPosition(AkInt64 iRealOffset);
	//  写入
	AKRESULT Write(void* pBuffer, AkUInt32 out_uSize);
	//  数据校验
	void CheckDataValidate(AkUInt32 in_uSize);
	//  返回数据流状态
	bool IsSuccess() { return m_bSuccess; }
	//  关闭流
	void Close();

private:
	AK::IAkStreamMgr* m_pStreamMgr;
	AK::IAkStdStream* m_pStream;
	const AkOSChar* m_pszFileName;
	bool m_bSuccess = false;
	AkUInt64 m_CurPosition = 0;
	const AkUInt64 POSITION_BEGIN = 0;
};

//---------------------------------------------------------------------------------
AKRESULT StreamStd::CreateStreamStd()
{
	if (!m_pStreamMgr)
		return AK_Fail;

	AKRESULT res = m_pStreamMgr->CreateStd(
		m_pszFileName,		//文件名
		NULL,				//文件位置解析逻辑,完整路径的话无需
		AK_OpenModeRead,	//创建设置:打开模式
		m_pStream,			//返回的流句柄
		true);				//需要同步打开文件

	return res;
}

AKRESULT StreamStd::ReadSync(void* pBuffer, AkUInt32 out_uSize)
{
	if (!m_pStream)
		return AK_Fail;

	AKRESULT res = m_pStream->Read(
		pBuffer,			//读取buffer地址
		BUFFER_SIZE,		//需要读取的buffer大小
		true,				//同步阻塞处理
		AK_DEFAULT_PRIORITY,//处理优先级
		0,					//请求延迟时间:现在处理
		out_uSize);			//返回实际读取buffer大小

	return res;
}

AKRESULT StreamStd::ReadAsync(void* pBuffer, AkUInt32 out_uSize)
{
	if (!m_pStream)
		return AK_Fail;

	AKRESULT res = m_pStream->Read(
		pBuffer,			//读取buffer地址
		BUFFER_SIZE,		//需要读取的buffer大小
		false,				//非阻塞处理
		AK_DEFAULT_PRIORITY,//处理优先级
		0,					//请求延迟时间:现在处理
		out_uSize);			//返回实际读取buffer大小

	return res;
}

AKRESULT StreamStd::SetPosition(AkInt64 iRealOffset)
{
	if (!m_pStream)
		return AK_Fail;

	AKRESULT res = m_pStream->SetPosition(
		ABS_POSITION,  //偏置量(offset)
		AK_MoveBegin,  //偏置模式:从头计算
		&iRealOffset   //返回实际偏置量
	);

	return res;
}

AKRESULT StreamStd::Write(void* pBuffer, AkUInt32 out_uSize)
{
	if (!m_pStream)
		return AK_Fail;

	AKRESULT res = m_pStream->Write(
		pBuffer,			//写入buffer地址
		BUFFER_SIZE,		//写入buffer大小
		true,				//阻塞处理
		AK_DEFAULT_PRIORITY,//默认优先级
		0,					//请求延迟时间:现在处理
		out_uSize			//返回实际写入buffer大小
	);

	return res;
}

void StreamStd::CheckDataValidate(AkUInt32 in_uSize)
{
	bool bEOF;

	if (!m_pStream)
		return;

	m_pStream->GetPosition(&bEOF);
	if (!bEOF && in_uSize != BUFFER_SIZE)
	{
		m_bSuccess = false;
		std::cerr << "Transfer Size Not Match Data Size" << std::endl;
		Close();
	}
	else if (bEOF)
	{
		m_bSuccess = false;
		std::cerr << "Buffer Overflow" << std::endl;
		Close();
	}
	else
		m_bSuccess = true;
}

void StreamStd::Close()
{
	if (m_pStream)
	{
		m_pStream->Destroy;
		return;
	}
	return;
}
  • 自动流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
//----------------------------------------------自动流------------------------------------
class StreamAuto
{
public:

	StreamAuto(const AkOSChar* in_pszFileName, int bufferSize, AkAutoStmHeuristics heuristics) :m_pszFileName(in_pszFileName), m_pStream(nullptr), heuristics(heuristics)
	{
		m_pStreamMgr = AK::IAkStreamMgr::Get();
	}

	//  流创建
	AKRESULT CreateStreamAuto();
	//  读取
	AKRESULT GetBuffer(void* pBuffer, AkUInt32 out_uSize);
	//  释放
	AKRESULT ReleaseBuffer();
	//  开始自动流
	AKRESULT Start();
	//  停止自动流
	AKRESULT Stop();
	//  Get流对象
	AK::IAkAutoStream* GetStream() { if (m_pStream) return m_pStream; }
	//  数据校验
	void CheckDataValidate(AkUInt32 uLoopEnd);
	//  返回数据流状态
	bool IsSuccess() { return m_bSuccess; }
	//  关闭流
	void Close();

private:
	AK::IAkStreamMgr* m_pStreamMgr;
	AK::IAkAutoStream* m_pStream;
	const AkOSChar* m_pszFileName;
	AkAutoStmHeuristics heuristics;
	bool m_bSuccess = false;
	AkUInt64 m_CurPosition = 0;
	const AkUInt64 POSITION_BEGIN = 0;
};

//---------------------------------------------------------------------------------
AKRESULT StreamAuto::CreateStreamAuto()
{
	if (!m_pStreamMgr)
		return AK_Fail;

	AKRESULT res = m_pStreamMgr->CreateAuto(
		m_pszFileName,		//文件名
		NULL,				//文件位置解析逻辑,完整路径的话无需
		heuristics,			//自动算法设置
		NULL,				//无缓冲限制
		m_pStream,			//返回的流句柄
		true);				//需要同步打开文件

	return res;
}

AKRESULT StreamAuto::Start()
{
	if (!m_pStream)
		return AK_Fail;

	AKRESULT res = m_pStream->Start();
	return res;
}

AKRESULT StreamAuto::Stop()
{
	if (!m_pStream)
		return AK_Fail;

	AKRESULT res = m_pStream->Stop();
	return res;
}

AKRESULT StreamAuto::GetBuffer(void* out_pBuffer, AkUInt32 out_uSize)
{
	if (!m_pStream)
		return AK_Fail;

	AKRESULT res = m_pStream->GetBuffer(
		out_pBuffer,  //返回数据地址
		out_uSize,	  //返回数据大小
		true		  //阻塞操作
	);

	if (res != AK_DataReady && res != AK_NoMoreData)
	{
		m_bSuccess = false;
		std::cerr << "Read Fail" << std::endl;
		Close();
	}
	else
		return res;
}

AKRESULT StreamAuto::ReleaseBuffer()
{
	if (!m_pStream)
		return AK_Success;

	AKRESULT res = m_pStream->ReleaseBuffer();

	if (res != AK_Success)
	{
		m_bSuccess = false;
		std::cerr << "Release Buffer Fail" << std::endl;
		Close();
	}
	else
		return res;
}

void StreamAuto::CheckDataValidate(AkUInt32 uLoopEnd)
{
	AkStreamInfo streamInfo;
	if (m_pStream)
		m_pStream->GetInfo(streamInfo);
	//文件结尾应该大于loop结尾
	if ((AkUInt32)streamInfo.uSize < uLoopEnd)
	{
		m_bSuccess = false;
		std::cerr << "File Size Small Than Loop Range" << std::endl;
		Close();
		return;
	}
	m_bSuccess = true;
	return;
}

void StreamAuto::Close()
{
	if (m_pStream)
	{
		m_pStream->Destroy;
		return;
	}
	return;
}
  • 业务函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
int main()
{
	//Buffer设置
	const int BUFFER_SIZE = 8192;
	unsigned char* pBuffer = new unsigned char[BUFFER_SIZE];
	//初始指针位置
	AkInt64 ABS_POSITION = 12000;
	//文件名
	const AkOSChar* fileName_read = L"file_read.txt";
	const AkOSChar* fileName_write = L"file_write.txt";

	//---------------------------------------------------------------------------------
	//创建标准流管理类对象
	StreamStd streamStd(fileName_read, BUFFER_SIZE);
	//创建一个标准流
	AKRESULT res = streamStd.CreateStreamStd();

	//流创建成功,执行同步读取操作
	AkUInt32 out_uSize = 0;
	if (res == AK_Success)
	{
		//  同步读取
		AKRESULT res = streamStd.ReadSync(pBuffer, out_uSize);
		//  检查数据有效性
		if (res != AK_Fail)
		{
			streamStd.CheckDataValidate(out_uSize);
		}
	}

	if (streamStd.IsSuccess())
	{
		//使用读取到的数据pBuffer
	}
	//  使用结束关闭流
	streamStd.Close();

	//---------------------------------------------------------------------------------
		//重新创建一个标准流来执行异步读取
	AKRESULT res = streamStd.CreateStreamStd();
	//流创建成功,执行同步读取操作
	AkUInt32 out_uSize = 0;
	if (res == AK_Success)
	{
		//  同步读取
		AKRESULT res = streamStd.ReadAsync(pBuffer, out_uSize);
		//  轮询状态
		AkStmStatus status = streamStd.GetStream()->GetStatus();
		while (status != AK_StmStatusCompleted && status != AK_StmStatusError)
		{
			//执行其它操作
			AKPLATFORM::AkSleep(1);
			status = streamStd.GetStream()->GetStatus();
		}
	}

	if (streamStd.IsSuccess())
	{
		//使用读取到的数据pBuffer
	}
	//  使用结束关闭流
	streamStd.Close();

	//-----------------------------------------------------------------------------------
	//重新创建一个标准流管理类对象来执行写入
	StreamStd streamStd(fileName_write, BUFFER_SIZE);
	//创建标准流
	AKRESULT res = streamStd.CreateStreamStd();
	AkUInt32 out_uSize = 0;
	if (res == AK_Success)
	{
		AKRESULT res = streamStd.Write(pBuffer, out_uSize);
		//  检查数据有效性
		if (res != AK_Fail)
		{
			streamStd.CheckDataValidate(out_uSize);
		}
	}

	//  使用结束关闭流
	streamStd.Close();

	//-------------------------------------------------------------------------------------
	//设置自动流处理的算法设置
	AkAutoStmHeuristics heuristics;
	//吞吐量1M/s
	heuristics.fThroughput = 1048576;
	//假设数据起点在1000偏置量位置
	heuristics.uLoopStart = 1000;
	//假设数据结束点在2000偏置量位置
	heuristics.uLoopEnd = 2000;
	//优先级设定
	heuristics.priority = AK_DEFAULT_PRIORITY;

	//创建自动流管理类对象
	StreamAuto streamAuto(fileName_read, BUFFER_SIZE, heuristics);
	//创建自动流对象
	AKRESULT res = streamAuto.CreateStreamAuto();
	//  启动自动流
	streamAuto.GetStream()->Start();

	//读取第一个buffer
	AkUInt32 out_uSize = 0;
	AKRESULT res = streamAuto.GetBuffer(pBuffer, out_uSize);
	if (res == AK_DataReady || AK_NoMoreData)
	{
		streamAuto.CheckDataValidate(heuristics.uLoopEnd);
	}

	if (streamAuto.IsSuccess())
	{
		//使用buffer数据
	}

	//释放流对象
	streamAuto.ReleaseBuffer();
	//关闭流对象
	streamAuto.Close();

	return 0;
}