Skip to content

Commit 50ba331

Browse files
committed
[LINUX][ZH] Add OpenAL stream implementations
1 parent eb62dd9 commit 50ba331

File tree

9 files changed

+305
-61
lines changed

9 files changed

+305
-61
lines changed

GeneralsMD/Code/GameEngineDevice/CMakeLists.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,12 +254,14 @@ if(RTS_BUILD_OPTION_OPENAL)
254254

255255
if(OpenAL_FOUND)
256256
target_sources(z_gameenginedevice PRIVATE
257+
Include/OpenALDevice/FFmpegOpenALAudioStream.h
257258
Include/OpenALDevice/OpenALAudioManager.h
258-
# Include/OpenALDevice/OpenALAudioStream.h
259+
Include/OpenALDevice/OpenALAudioStream.h
260+
Source/OpenALDevice/FFmpegOpenALAudioStream.cpp
259261
Source/OpenALDevice/OpenALAudioFileCache.h
260262
Source/OpenALDevice/OpenALAudioFileCache.cpp
261263
Source/OpenALDevice/OpenALAudioManager.cpp
262-
# Source/OpenALDevice/OpenALAudioStream.cpp
264+
Source/OpenALDevice/OpenALAudioStream.cpp
263265
)
264266

265267
target_link_libraries(z_gameenginedevice PRIVATE OpenAL::OpenAL)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
** Command & Conquer Generals Zero Hour(tm)
3+
** Copyright 2025 Electronic Arts Inc.
4+
**
5+
** This program is free software: you can redistribute it and/or modify
6+
** it under the terms of the GNU General Public License as published by
7+
** the Free Software Foundation, either version 3 of the License, or
8+
** (at your option) any later version.
9+
**
10+
** This program is distributed in the hope that it will be useful,
11+
** but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
** GNU General Public License for more details.
14+
**
15+
** You should have received a copy of the GNU General Public License
16+
** along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
// FILE: FFmpegOpenALAudioStream.h //////////////////////////////////////////////////////////////////////////
20+
// FFmpegOpenALAudioStream implementation
21+
// Author: Stephan Vedder, May 2025
22+
#pragma once
23+
24+
#include "OpenALAudioManager.h"
25+
#include "OpenALAudioStream.h"
26+
27+
class FFmpegFile;
28+
class FFmpegOpenALAudioStream : public OpenALAudioStream
29+
{
30+
public:
31+
FFmpegOpenALAudioStream(FFmpegFile* file);
32+
~FFmpegOpenALAudioStream();
33+
34+
35+
protected:
36+
FFmpegFile* m_ffmpegFile;
37+
};
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
** Command & Conquer Generals Zero Hour(tm)
3+
** Copyright 2025 Electronic Arts Inc.
4+
**
5+
** This program is free software: you can redistribute it and/or modify
6+
** it under the terms of the GNU General Public License as published by
7+
** the Free Software Foundation, either version 3 of the License, or
8+
** (at your option) any later version.
9+
**
10+
** This program is distributed in the hope that it will be useful,
11+
** but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
** GNU General Public License for more details.
14+
**
15+
** You should have received a copy of the GNU General Public License
16+
** along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
// FILE: OpenALAudioStream.h //////////////////////////////////////////////////////////////////////////
20+
// OpenALAudioStream implementation
21+
// Author: Stephan Vedder, May 2025
22+
#pragma once
23+
24+
#include "always.h"
25+
#include <AL/al.h>
26+
#include <stdint.h>
27+
#include <functional>
28+
29+
#define AL_STREAM_BUFFER_COUNT 32
30+
31+
class OpenALAudioStream
32+
{
33+
public:
34+
OpenALAudioStream();
35+
virtual ~OpenALAudioStream();
36+
37+
void setRequireDataCallback(std::function<void()> callback) { m_requireDataCallback = callback; }
38+
ALuint getSource() const { return m_source; }
39+
40+
bool bufferData(uint8_t *data, size_t data_size, ALenum format, int samplerate);
41+
bool isPlaying();
42+
void update();
43+
void reset();
44+
45+
void play() { alSourcePlay(m_source); }
46+
void pause() { alSourcePause(m_source); }
47+
void stop() { alSourceStop(m_source); }
48+
49+
void setVolume(float vol) { alSourcef(m_source, AL_GAIN, vol); }
50+
51+
protected:
52+
std::function<void()> m_requireDataCallback = nullptr;
53+
ALuint m_source = 0;
54+
ALuint m_buffers[AL_STREAM_BUFFER_COUNT] = {};
55+
unsigned int m_currentBufferIndex = 0;
56+
};

GeneralsMD/Code/GameEngineDevice/Include/Win32Device/Common/Win32GameEngine.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@
3838
#include "Common/GameEngine.h"
3939
#include "GameLogic/GameLogic.h"
4040
#include "GameNetwork/NetworkInterface.h"
41-
#include "MilesAudioDevice/MilesAudioManager.h"
4241
#ifdef RTS_HAS_OPENAL
4342
#include "OpenALDevice/OpenALAudioManager.h"
43+
#else
44+
#include "MilesAudioDevice/MilesAudioManager.h"
4445
#endif
4546
#include "Win32Device/Common/Win32BIGFileSystem.h"
4647
#include "Win32Device/Common/Win32LocalFileSystem.h"
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
** Command & Conquer Generals Zero Hour(tm)
3+
** Copyright 2025 Electronic Arts Inc.
4+
**
5+
** This program is free software: you can redistribute it and/or modify
6+
** it under the terms of the GNU General Public License as published by
7+
** the Free Software Foundation, either version 3 of the License, or
8+
** (at your option) any later version.
9+
**
10+
** This program is distributed in the hope that it will be useful,
11+
** but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
** GNU General Public License for more details.
14+
**
15+
** You should have received a copy of the GNU General Public License
16+
** along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
// FILE: FFmpegOpenALAudioStream.cpp //////////////////////////////////////////////////////////////////////////
20+
// FFmpegOpenALAudioStream implementation
21+
// Author: Stephan Vedder, May 2025
22+
#include "OpenALDevice/FFmpegOpenALAudioStream.h"
23+
24+
FFmpegOpenALAudioStream::FFmpegOpenALAudioStream(FFmpegFile* file) :
25+
OpenALAudioStream(),
26+
m_ffmpegFile(file)
27+
{
28+
}
29+
30+
FFmpegOpenALAudioStream::~FFmpegOpenALAudioStream()
31+
{
32+
}

GeneralsMD/Code/GameEngineDevice/Source/OpenALDevice/OpenALAudioManager.cpp

Lines changed: 51 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141

4242
#include "Lib/BaseType.h"
4343
#include "OpenALDevice/OpenALAudioManager.h"
44-
//#include "OpenALAudioDevice/OpenALAudioStream.h"
44+
#include "OpenALDevice/OpenALAudioStream.h"
4545
#include "OpenALAudioFileCache.h"
4646

4747
#include "Common/AudioAffect.h"
@@ -55,7 +55,6 @@
5555
#include "Common/GameSounds.h"
5656
#include "Common/CRCDebug.h"
5757
#include "Common/GlobalData.h"
58-
#include "Common/ScopedMutex.h"
5958

6059
#include "GameClient/DebugDisplay.h"
6160
#include "GameClient/Drawable.h"
@@ -632,7 +631,7 @@ void OpenALAudioManager::pauseAudio(AudioAffect which)
632631
AudioRequest *req = (*ait);
633632
if( req && req->m_request == AR_Play )
634633
{
635-
req->deleteInstance();
634+
deleteInstance(req);
636635
ait = m_audioRequests.erase(ait);
637636
}
638637
else
@@ -767,54 +766,54 @@ void OpenALAudioManager::playAudioEvent(AudioEventRTS* event)
767766

768767
OpenALAudioStream* stream;
769768
if (!handleToKill || foundSoundToReplace) {
770-
//stream = new OpenALAudioStream;
771-
//// When we need more data ask FFmpeg for more data.
772-
//stream->setRequireDataCallback([ffmpegFile, stream]() {
773-
// ffmpegFile->decodePacket();
774-
// });
775-
//
776-
//// When we receive a frame from FFmpeg, send it to OpenAL.
777-
//ffmpegFile->setFrameCallback([stream](AVFrame* frame, int stream_idx, int stream_type, void* user_data) {
778-
// if (stream_type != AVMEDIA_TYPE_AUDIO) {
779-
// return;
780-
// }
781-
782-
// DEBUG_LOG(("Received audio frame\n"));
783-
784-
// AVSampleFormat sampleFmt = static_cast<AVSampleFormat>(frame->format);
785-
// const int bytesPerSample = av_get_bytes_per_sample(sampleFmt);
786-
// ALenum format = OpenALAudioManager::getALFormat(frame->ch_layout.nb_channels, bytesPerSample * 8);
787-
// const int frameSize =
788-
// av_samples_get_buffer_size(NULL, frame->ch_layout.nb_channels, frame->nb_samples, sampleFmt, 1);
789-
// uint8_t* frameData = frame->data[0];
790-
791-
// // We need to interleave the samples if the format is planar
792-
// if (av_sample_fmt_is_planar(static_cast<AVSampleFormat>(frame->format))) {
793-
// uint8_t* audioBuffer = static_cast<uint8_t*>(av_malloc(frameSize));
794-
795-
// // Write the samples into our audio buffer
796-
// for (int sample_idx = 0; sample_idx < frame->nb_samples; sample_idx++)
797-
// {
798-
// int byte_offset = sample_idx * bytesPerSample;
799-
// for (int channel_idx = 0; channel_idx < frame->ch_layout.nb_channels; channel_idx++)
800-
// {
801-
// uint8_t* dst = &audioBuffer[byte_offset * frame->ch_layout.nb_channels + channel_idx * bytesPerSample];
802-
// uint8_t* src = &frame->data[channel_idx][byte_offset];
803-
// memcpy(dst, src, bytesPerSample);
804-
// }
805-
// }
806-
// stream->bufferData(audioBuffer, frameSize, format, frame->sample_rate);
807-
// av_freep(&audioBuffer);
808-
// }
809-
// else
810-
// stream->bufferData(frameData, frameSize, format, frame->sample_rate);
811-
// });
769+
stream = new OpenALAudioStream;
770+
// When we need more data ask FFmpeg for more data.
771+
stream->setRequireDataCallback([ffmpegFile, stream]() {
772+
ffmpegFile->decodePacket();
773+
});
774+
775+
// When we receive a frame from FFmpeg, send it to OpenAL.
776+
ffmpegFile->setFrameCallback([stream](AVFrame* frame, int stream_idx, int stream_type, void* user_data) {
777+
if (stream_type != AVMEDIA_TYPE_AUDIO) {
778+
return;
779+
}
780+
781+
DEBUG_LOG(("Received audio frame\n"));
782+
783+
AVSampleFormat sampleFmt = static_cast<AVSampleFormat>(frame->format);
784+
const int bytesPerSample = av_get_bytes_per_sample(sampleFmt);
785+
ALenum format = OpenALAudioManager::getALFormat(frame->ch_layout.nb_channels, bytesPerSample * 8);
786+
const int frameSize =
787+
av_samples_get_buffer_size(NULL, frame->ch_layout.nb_channels, frame->nb_samples, sampleFmt, 1);
788+
uint8_t* frameData = frame->data[0];
789+
790+
// We need to interleave the samples if the format is planar
791+
if (av_sample_fmt_is_planar(static_cast<AVSampleFormat>(frame->format))) {
792+
uint8_t* audioBuffer = static_cast<uint8_t*>(av_malloc(frameSize));
793+
794+
// Write the samples into our audio buffer
795+
for (int sample_idx = 0; sample_idx < frame->nb_samples; sample_idx++)
796+
{
797+
int byte_offset = sample_idx * bytesPerSample;
798+
for (int channel_idx = 0; channel_idx < frame->ch_layout.nb_channels; channel_idx++)
799+
{
800+
uint8_t* dst = &audioBuffer[byte_offset * frame->ch_layout.nb_channels + channel_idx * bytesPerSample];
801+
uint8_t* src = &frame->data[channel_idx][byte_offset];
802+
memcpy(dst, src, bytesPerSample);
803+
}
804+
}
805+
stream->bufferData(audioBuffer, frameSize, format, frame->sample_rate);
806+
av_freep(&audioBuffer);
807+
}
808+
else
809+
stream->bufferData(frameData, frameSize, format, frame->sample_rate);
810+
});
812811

813812
// Decode packets before starting the stream.
814-
//for (int i = 0; i < AL_STREAM_BUFFER_COUNT; i++) {
815-
// if (!ffmpegFile->decodePacket())
816-
// break;
817-
//}
813+
for (int i = 0; i < AL_STREAM_BUFFER_COUNT; i++) {
814+
if (!ffmpegFile->decodePacket())
815+
break;
816+
}
818817
}
819818
else {
820819
stream = NULL;
@@ -1061,7 +1060,7 @@ void OpenALAudioManager::killAudioEventImmediately(AudioHandle audioEvent)
10611060
AudioRequest *req = (*ait);
10621061
if( req && req->m_request == AR_Play && req->m_handleToInteractOn == audioEvent )
10631062
{
1064-
req->deleteInstance();
1063+
deleteInstance(req);
10651064
ait = m_audioRequests.erase(ait);
10661065
return;
10671066
}
@@ -2305,7 +2304,7 @@ void OpenALAudioManager::processRequestList(void)
23052304
if (!req->m_requiresCheckForSample || checkForSample(req)) {
23062305
processRequest(req);
23072306
}
2308-
req->deleteInstance();
2307+
deleteInstance(req);
23092308
it = m_audioRequests.erase(it);
23102309
}
23112310
}
@@ -2945,7 +2944,7 @@ void* OpenALAudioManager::getHandleForBink(void)
29452944
{
29462945
if (!m_binkAudio) {
29472946
DEBUG_LOG(("Creating Bink audio stream\n"));
2948-
//m_binkAudio = NEW OpenALAudioStream;
2947+
m_binkAudio = NEW OpenALAudioStream;
29492948
}
29502949
return m_binkAudio;
29512950
}

0 commit comments

Comments
 (0)