Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Streaming music - WIP #127

Merged
merged 31 commits into from Aug 16, 2019
Merged
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
17314eb
Make vorbis decoder usable for streaming.
and3md Jul 31, 2019
912b122
Added TStreamedSoundFile and TStreamedSoundOggVorbis.
and3md Jul 31, 2019
d372e26
The first draft of streaming sound buffers.
and3md Jul 31, 2019
74dbeda
Draft 2 of streaming sound: Multiple playback at the same time, Don't…
and3md Aug 4, 2019
32a92f9
Fixed checking dangle pointer when setBuffer() with TOpenALSoundBuff…
and3md Aug 4, 2019
75e2895
Proper release stream resources.
and3md Aug 4, 2019
9bc7ab4
Rewind streamed sound file, this is necessary for looping.
and3md Aug 5, 2019
865e0af
Music streaming: Sound looping support
and3md Aug 5, 2019
8571620
Fixed typo Soc -> Sox.
and3md Aug 5, 2019
961aa9c
TSoundBufferType -> TSoundLoading.
and3md Aug 5, 2019
8ec769f
Backward compatibility version of LoadBuffer.
and3md Aug 6, 2019
80c2989
Added CASTLE_SUPPORTS_THREADING, when threads are not available, fall…
and3md Aug 6, 2019
365eec4
Streaming: Code cleaning and minor changes.
and3md Aug 6, 2019
2b66e08
Remove unneeded log.
and3md Aug 11, 2019
b77bd60
Fix LoadBuffer result not assigned, do not introduce new LoadBuffer d…
michaliskambi Aug 12, 2019
f310a85
Whitespace (only one empty line to separate)
michaliskambi Aug 12, 2019
129102d
Wrap OggVorbis loading in TOggVorbisStream
michaliskambi Aug 12, 2019
823af29
Allow testing streaming in examples/audio/play_sounds
michaliskambi Aug 12, 2019
2f08594
Past tense of "read" is "read", not "readed"
michaliskambi Aug 13, 2019
a999ad6
Fix raising exception
michaliskambi Aug 13, 2019
853c13a
Every sound format (OggVorbis, WAV) is now expressed as TSoundReadEvent
michaliskambi Aug 13, 2019
23cf2ba
Remove large and unused TSoundFile.DataStatistics implementation
michaliskambi Aug 13, 2019
d0b8504
Allow loading FMOD dynamically (so we know at runtime when FMOD is no…
michaliskambi Aug 15, 2019
2c965d0
FMOD supports streaming too, and some code cleanups
michaliskambi Aug 15, 2019
71f4b63
Improve comment - Pascal threads for streaming are required by OpenAL…
michaliskambi Aug 15, 2019
8429af5
Fix FMOD dynamic freeing, to work with any finalization order of units
michaliskambi Aug 16, 2019
8ee8042
Get correct Duration for OggVorbis sounds, even when streaming
michaliskambi Aug 16, 2019
5315ea5
Various code cleanups (no functional changes)
michaliskambi Aug 16, 2019
4167622
Small comment improvements and code cleanups
michaliskambi Aug 16, 2019
8a06c75
Fix playing short sounds with slStreaming, we may need less than 4 bu…
michaliskambi Aug 16, 2019
dbf7c90
Improve comments and tiny code cleanups
michaliskambi Aug 16, 2019
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

FMOD supports streaming too, and some code cleanups

Improve some comments,
use "const" for parameter when possible,
move TSoundBufferBackendFromSoundFile lower (reflect the order of declaration).
  • Loading branch information...
michaliskambi committed Aug 15, 2019
commit 2c965d0b194ca02c1d8bc0e5b974812237571dc4
@@ -65,9 +65,10 @@ TSoundBufferBackend = class
procedure ContextClose; virtual; abstract;
end;

{ TSoundBufferBackend descendant that can load TSoundFile instance.
{ TSoundBufferBackend descendant that loads sound files using TSoundFile.
Should be used by sound backends that cannot load sound files themselves,
and rely on TSoundFile to do it (right now, this applies to all backends except FMOD). }
and rely on TSoundFile to do it (right now, this applies to all backends
except FMOD). }
TSoundBufferBackendFromSoundFile = class(TSoundBufferBackend)
protected
{ Load from @link(SoundFile).
@@ -78,6 +79,7 @@ TSoundBufferBackendFromSoundFile = class(TSoundBufferBackend)
procedure ContextOpen(const AURL: String); override;
end;

{ TSoundBufferBackend descendant that loads sound files using TStreamedSoundFile. }
TSoundBufferBackendFromStreamedFile = class(TSoundBufferBackend)
protected
FStreamConfigRead: Boolean;
@@ -162,7 +164,7 @@ TSoundEngineBackend = class
procedure ContextClose; virtual; abstract;

{ Create suitable non-abstract TSoundBufferBackend descendant. }
function CreateBuffer(SoundLoading: TSoundLoading): TSoundBufferBackend; virtual; abstract;
function CreateBuffer(const SoundLoading: TSoundLoading): TSoundBufferBackend; virtual; abstract;

{ Create suitable non-abstract TSoundSourceBackend descendant. }
function CreateSource: TSoundSourceBackend; virtual; abstract;
@@ -185,112 +187,113 @@ implementation

uses SysUtils;

{ TSoundBufferBackendFromStreamedFile }
{ TSoundBufferBackend -------------------------------------------------------- }

procedure TSoundBufferBackendFromStreamedFile.ContextOpenFromStreamedFile(const AURL: String);
function TSoundBufferBackend.GetDuration: TFloatTime;
begin
Result := FDuration;
end;

function TSoundBufferBackend.GetDataFormat: TSoundDataFormat;
begin
Result := FDataFormat;
end;

function TSoundBufferBackendFromStreamedFile.GetDuration: TFloatTime;
function TSoundBufferBackend.GetFrequency: LongWord;
begin
Result := -1;
Result := FFrequency;
end;

function TSoundBufferBackendFromStreamedFile.GetDataFormat: TSoundDataFormat;
constructor TSoundBufferBackend.Create(const ASoundEngine: TSoundEngineBackend);
begin
if not FStreamConfigRead then
ReadStreamConfig;
Result := FDataFormat;
inherited Create;
FSoundEngine := ASoundEngine;
end;

function TSoundBufferBackendFromStreamedFile.GetFrequency: LongWord;
procedure TSoundBufferBackend.ContextOpen(const AURL: String);
begin
if not FStreamConfigRead then
ReadStreamConfig;
Result := FFrequency;
FURL := AURL;
end;

procedure TSoundBufferBackendFromStreamedFile.ReadStreamConfig(const StreamedSoundFile: TStreamedSoundFile);
{ TSoundBufferBackendFromSoundFile -------------------------------------------}

procedure TSoundBufferBackendFromSoundFile.ContextOpenFromSoundFile(const SoundFile: TSoundFile);
begin
FDuration := -1;
FDataFormat := StreamedSoundFile.DataFormat;
FFrequency := StreamedSoundFile.Frequency;
FStreamConfigRead := true;
end;

procedure TSoundBufferBackendFromStreamedFile.ReadStreamConfig;
procedure TSoundBufferBackendFromSoundFile.ContextOpen(const AURL: String);
var
F: TStreamedSoundFile;
F: TSoundFile;
begin
F := TStreamedSoundFile.Create(FURL);
inherited;

F := TSoundFile.Create(URL);
try
ReadStreamConfig(F);
finally
FreeAndNil(F);
end;
FDuration := F.Duration;
FDataFormat := F.DataFormat;
FFrequency := F.Frequency;
ContextOpenFromSoundFile(F);
finally FreeAndNil(F) end;
end;

{ TSoundBufferBackendFromStreamedFile ---------------------------------------- }

constructor TSoundBufferBackendFromStreamedFile.Create(const ASoundEngine: TSoundEngineBackend);
begin
inherited Create(ASoundEngine);
FStreamConfigRead := false;
end;

procedure TSoundBufferBackendFromStreamedFile.ContextOpen(const AURL: String);
procedure TSoundBufferBackendFromStreamedFile.ContextOpenFromStreamedFile(const AURL: String);
begin
inherited;

ContextOpenFromStreamedFile(AURL);
end;

{ TSoundBufferBackend -------------------------------------------------------- }

function TSoundBufferBackend.GetDuration: TFloatTime;
function TSoundBufferBackendFromStreamedFile.GetDuration: TFloatTime;
begin
Result := FDuration;
// This never returns Duration for now
Result := -1;
end;

function TSoundBufferBackend.GetDataFormat: TSoundDataFormat;
function TSoundBufferBackendFromStreamedFile.GetDataFormat: TSoundDataFormat;
begin
if not FStreamConfigRead then
ReadStreamConfig;
Result := FDataFormat;
end;

function TSoundBufferBackend.GetFrequency: LongWord;
function TSoundBufferBackendFromStreamedFile.GetFrequency: LongWord;
begin
if not FStreamConfigRead then
ReadStreamConfig;
Result := FFrequency;
end;

constructor TSoundBufferBackend.Create(const ASoundEngine: TSoundEngineBackend);
begin
inherited Create;
FSoundEngine := ASoundEngine;
end;

procedure TSoundBufferBackend.ContextOpen(const AURL: String);
procedure TSoundBufferBackendFromStreamedFile.ReadStreamConfig(const StreamedSoundFile: TStreamedSoundFile);
begin
FURL := AURL;
FDuration := -1;
FDataFormat := StreamedSoundFile.DataFormat;
FFrequency := StreamedSoundFile.Frequency;
FStreamConfigRead := true;
end;

{ TSoundBufferBackendFromSoundFile -------------------------------------------}

procedure TSoundBufferBackendFromSoundFile.ContextOpenFromSoundFile(const SoundFile: TSoundFile);
procedure TSoundBufferBackendFromStreamedFile.ReadStreamConfig;
var
F: TStreamedSoundFile;
begin
// TODO: Using this means that TStreamedSoundFile.Create may be created twice, which seems wasteful (each creation means we read the OggVorbis header, so it's a filesystem operation)
F := TStreamedSoundFile.Create(FURL);
try
ReadStreamConfig(F);
finally
FreeAndNil(F);
end;
end;

procedure TSoundBufferBackendFromSoundFile.ContextOpen(const AURL: String);
var
F: TSoundFile;
procedure TSoundBufferBackendFromStreamedFile.ContextOpen(const AURL: String);
begin
inherited;

F := TSoundFile.Create(URL);
try
FDuration := F.Duration;
FDataFormat := F.DataFormat;
FFrequency := F.Frequency;
ContextOpenFromSoundFile(F);
finally FreeAndNil(F) end;
ContextOpenFromStreamedFile(AURL);
end;

{ TSoundSourceBackend -------------------------------------------------------- }
@@ -80,7 +80,7 @@ TSoxSoundEngineBackend = class(TSoundEngineBackend)
public
function ContextOpen(const ADevice: String; out Information: String): Boolean; override;
procedure ContextClose; override;
function CreateBuffer(SoundLoading: TSoundLoading): TSoundBufferBackend; override;
function CreateBuffer(const SoundLoading: TSoundLoading): TSoundBufferBackend; override;
function CreateSource: TSoundSourceBackend; override;
procedure SetGain(const Value: Single); override;
procedure SetDistanceModel(const Value: TSoundDistanceModel); override;
@@ -258,14 +258,10 @@ procedure TSoxSoundEngineBackend.SetListener(const Position, Direction, Up: TVec
begin
end;

function TSoxSoundEngineBackend.CreateBuffer(SoundLoading: TSoundLoading): TSoundBufferBackend;
function TSoxSoundEngineBackend.CreateBuffer(const SoundLoading: TSoundLoading): TSoundBufferBackend;
begin
case SoundLoading of
slStreaming:
raise Exception.Create('Streaming is not supported in Sox backend.');
slComplete:
Result := TSoxSoundBufferBackend.Create(Self);
end;
// Ignore SoundLoading
Result := TSoxSoundBufferBackend.Create(Self);
end;

function TSoxSoundEngineBackend.CreateSource: TSoundSourceBackend;
@@ -45,9 +45,12 @@ implementation
type
TFMODSoundBufferBackend = class(TSoundBufferBackend)
private
FSoundLoading: TSoundLoading;
FMODSound: PFMOD_SOUND;
function FMODSystem: PFMOD_SYSTEM;
public
constructor Create(const ASoundEngine: TSoundEngineBackend;
const ASoundLoading: TSoundLoading);
procedure ContextOpen(const AURL: String); override;
procedure ContextClose; override;
end;
@@ -85,7 +88,7 @@ TFMODSoundEngineBackend = class(TSoundEngineBackend)
public
function ContextOpen(const ADevice: String; out Information: String): Boolean; override;
procedure ContextClose; override;
function CreateBuffer: TSoundBufferBackend; override;
function CreateBuffer(const SoundLoading: TSoundLoading): TSoundBufferBackend; override;
function CreateSource: TSoundSourceBackend; override;

procedure Update; override;
@@ -128,14 +131,20 @@ function SoundFormatToStr(const SoundFormat: TFMOD_SOUND_FORMAT): String;

{ TFMODSoundBufferBackend -------------------------------------------------- }

constructor TFMODSoundBufferBackend.Create(
const ASoundEngine: TSoundEngineBackend; const ASoundLoading: TSoundLoading);
begin
inherited Create(ASoundEngine);
FSoundLoading := ASoundLoading;
end;

function TFMODSoundBufferBackend.FMODSystem: PFMOD_SYSTEM;
begin
Result := (SoundEngine as TFMODSoundEngineBackend).FMODSystem;
end;

procedure TFMODSoundBufferBackend.ContextOpen(const AURL: String);
var
S: String;
TimeStart: TCastleProfilerTime;

procedure CalculateProperties;
@@ -188,15 +197,21 @@ procedure TFMODSoundBufferBackend.ContextOpen(const AURL: String);
]);
end;

var
Mode: TFMOD_MODE;
FmodName: String;
begin
inherited;
TimeStart := Profiler.Start('Loading "' + URIDisplay(AURL) + '" (TFMODSoundBufferBackend)');
try

S := ResolveCastleDataURL(URL); // resolve castle-data:/, as FMOD cannot understand it
if URIProtocol(S) = 'file' then
S := URIToFilenameSafe(S); // resolve file:/, as FMOD cannot understand it
CheckFMOD(FMOD_System_CreateSound(FMODSystem, PCharOrNil(S), FMOD_DEFAULT or FMOD_2D,
FmodName := ResolveCastleDataURL(URL); // resolve castle-data:/, as FMOD cannot understand it
if URIProtocol(FmodName) = 'file' then
FmodName := URIToFilenameSafe(FmodName); // resolve file:/, as FMOD cannot understand it
Mode := FMOD_DEFAULT or FMOD_2D;
if FSoundLoading = slStreaming then
Mode := Mode or FMOD_CREATESTREAM;
CheckFMOD(FMOD_System_CreateSound(FMODSystem, PCharOrNil(FmodName), Mode,
nil { @SoundInfo }, @FMODSound));

CalculateProperties;
@@ -356,9 +371,9 @@ procedure TFMODSoundSourceBackend.SetOffset(const Value: Single);

{ TFMODSoundEngineBackend -------------------------------------------------- }

function TFMODSoundEngineBackend.CreateBuffer: TSoundBufferBackend;
function TFMODSoundEngineBackend.CreateBuffer(const SoundLoading: TSoundLoading): TSoundBufferBackend;
begin
Result := TFMODSoundBufferBackend.Create(Self);
Result := TFMODSoundBufferBackend.Create(Self, SoundLoading);
end;

function TFMODSoundEngineBackend.CreateSource: TSoundSourceBackend;
@@ -164,7 +164,7 @@ TOpenALSoundEngineBackend = class(TSoundEngineBackend)
procedure DetectDevices(const Devices: TSoundDeviceList); override;
function ContextOpen(const ADevice: String; out Information: String): Boolean; override;
procedure ContextClose; override;
function CreateBuffer(SoundLoading: TSoundLoading): TSoundBufferBackend; override;
function CreateBuffer(const SoundLoading: TSoundLoading): TSoundBufferBackend; override;
function CreateSource: TSoundSourceBackend; override;

procedure SetGain(const Value: Single); override;
@@ -1000,14 +1000,13 @@ procedure TOpenALSoundEngineBackend.SetListener(const Position, Direction, Up: T
alListenerOrientation(Direction, Up);
end;

function TOpenALSoundEngineBackend.CreateBuffer(SoundLoading: TSoundLoading): TSoundBufferBackend;
function TOpenALSoundEngineBackend.CreateBuffer(const SoundLoading: TSoundLoading): TSoundBufferBackend;
begin
{$ifdef CASTLE_SUPPORTS_THREADING}
case SoundLoading of
slComplete:
Result := TOpenALSoundBufferBackend.Create(Self);
slStreaming:
Result := TOpenALStreamBuffersBackend.Create(Self);
slComplete : Result := TOpenALSoundBufferBackend.Create(Self);
slStreaming: Result := TOpenALStreamBuffersBackend.Create(Self);
else raise EInternalError.Create('TOpenALSoundEngineBackend.CreateBuffer: Invalid SoundLoading');
end;
{$else}
Result := TOpenALSoundBufferBackend.Create(Self);
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.