aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>2008-01-10 22:59:10 +0000
committertobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>2008-01-10 22:59:10 +0000
commitd69c2a35192623e5d923f17c07d96d980d045adf (patch)
treeabdda71929e097c4dd7ed521fc93b4cc10a0f363
parent2d5c9ab3a153b95d0a1bc089ebe8ae2eb7376afa (diff)
downloadusdx-d69c2a35192623e5d923f17c07d96d980d045adf.tar.gz
usdx-d69c2a35192623e5d923f17c07d96d980d045adf.tar.xz
usdx-d69c2a35192623e5d923f17c07d96d980d045adf.zip
partial seeking and mixing support
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@775 b956fd51-792f-4845-bead-9b4dfca2ff2c
-rw-r--r--Game/Code/Classes/UAudioDecoder_FFMpeg.pas273
-rw-r--r--Game/Code/Classes/UAudioPlayback_Portaudio.pas123
2 files changed, 275 insertions, 121 deletions
diff --git a/Game/Code/Classes/UAudioDecoder_FFMpeg.pas b/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
index 54055454..c81e4be1 100644
--- a/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
+++ b/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
@@ -26,9 +26,11 @@ uses Classes,
{$ENDIF}
SysUtils,
SDL,
- avcodec, // FFMpeg Audio file decoding
+ avcodec, // FFMpeg Audio file decoding
avformat,
avutil,
+ avio, // used for url_ferror
+ mathematics, // used for av_rescale_q
ULog,
UMusic;
@@ -64,10 +66,15 @@ type
function Put(pkt : PAVPacket): integer;
function Get(var pkt: TAVPacket; block: boolean): integer;
+ procedure Flush();
end;
+const
+ MAX_AUDIOQ_SIZE = (5 * 16 * 1024);
+
var
- EOSPacket: TAVPacket;
+ EOFPacket: TAVPacket;
+ FlushPacket: TAVPacket;
type
PAudioBuffer = ^TAudioBuffer;
@@ -76,9 +83,17 @@ type
type
TFFMpegDecodeStream = class(TAudioDecodeStream)
private
- status: TStreamStatus;
+ _EOF: boolean; // end-of-stream flag
+ _EOF_lock : PSDL_Mutex;
+
+ lock : PSDL_Mutex;
+ resumeCond : PSDL_Cond;
+
+ quitRequest : boolean;
- EOS_Flag: boolean; // end-of-stream flag
+ seekRequest: boolean;
+ seekFlags : integer;
+ seekPos : int64;
parseThread: PSDL_Thread;
packetQueue: TPacketQueue;
@@ -87,8 +102,8 @@ type
pFormatCtx : PAVFormatContext;
pCodecCtx : PAVCodecContext;
pCodec : PAVCodec;
- ffmpegStreamID : Integer;
- ffmpegStream : PAVStream;
+ ffmpegStreamIndex : Integer;
+ ffmpegStream : PAVStream;
// "static" vars for DecodeFrame
pkt : TAVPacket;
@@ -101,6 +116,7 @@ type
audio_buf : TAudioBuffer;
function DecodeFrame(var buffer: TAudioBuffer; bufSize: integer): integer;
+ procedure SetEOF(state: boolean);
public
constructor Create(pFormatCtx: PAVFormatContext;
pCodecCtx: PAVCodecContext; pCodec: PAVCodec;
@@ -114,7 +130,7 @@ type
function GetSampleRate(): cardinal; override;
function GetPosition: real; override;
procedure SetPosition(Time: real); override;
- function IsEOS(): boolean; override;
+ function IsEOF(): boolean; override;
function ReadData(Buffer: PChar; BufSize: integer): integer; override;
end;
@@ -122,7 +138,7 @@ type
type
TAudioDecoder_FFMpeg = class( TInterfacedObject, IAudioDecoder )
private
- class function FindAudioStreamID(pFormatCtx : PAVFormatContext): integer;
+ class function FindAudioStreamIndex(pFormatCtx : PAVFormatContext): integer;
public
function GetName: String;
@@ -144,7 +160,6 @@ constructor TFFMpegDecodeStream.Create(pFormatCtx: PAVFormatContext;
begin
inherited Create();
- status := sStopped;
packetQueue := TPacketQueue.Create();
audio_pkt_data := nil;
@@ -158,30 +173,46 @@ begin
Self.pFormatCtx := pFormatCtx;
Self.pCodecCtx := pCodecCtx;
Self.pCodec := pCodec;
- Self.ffmpegStreamID := ffmpegStreamID;
- Self.ffmpegStream := ffmpegStream;
+ Self.ffmpegStreamIndex := ffmpegStreamIndex;
+ Self.ffmpegStream := ffmpegStream;
- EOS_Flag := false;
+ _EOF := false;
+ _EOF_lock := SDL_CreateMutex();
+
+ lock := SDL_CreateMutex();
+ resumeCond := SDL_CreateCond();
parseThread := SDL_CreateThread(@ParseAudio, Self);
end;
destructor TFFMpegDecodeStream.Destroy();
begin
- packetQueue.Free();
- //SDL_WaitThread(parseThread, nil);
+ //Close();
+ //packetQueue.Free();
inherited;
end;
procedure TFFMpegDecodeStream.Close();
begin
+ // TODO: abort thread
+ //quitRequest := true;
+ //SDL_WaitThread(parseThread, nil);
+
+ (*
// Close the codec
- avcodec_close(pCodecCtx);
+ if (pCodecCtx <> nil) then
+ begin
+ avcodec_close(pCodecCtx);
+ pCodecCtx := nil;
+ end;
// Close the video file
- av_close_input_file(pFormatCtx);
-
- // TODO: abort thread
+ if (pFormatCtx <> nil) then
+ begin
+ av_close_input_file(pFormatCtx);
+ pFormatCtx := nil;
+ end;
+ *)
end;
function TFFMpegDecodeStream.GetLength(): real;
@@ -199,24 +230,134 @@ begin
result := pCodecCtx^.sample_rate;
end;
-function TFFMpegDecodeStream.IsEOS(): boolean;
+function TFFMpegDecodeStream.IsEOF(): boolean;
+begin
+ SDL_mutexP(_EOF_lock);
+ result := _EOF;
+ SDL_mutexV(_EOF_lock);
+end;
+
+procedure TFFMpegDecodeStream.SetEOF(state: boolean);
+begin
+ SDL_mutexP(_EOF_lock);
+ _EOF := state;
+ SDL_mutexV(_EOF_lock);
+end;
+
+function TFFMpegDecodeStream.GetPosition(): real;
+var
+ bytes: integer;
begin
- result := EOS_Flag;
+ // see: tutorial on synching (audio-clock)
+ Result := 0;
end;
+procedure TFFMpegDecodeStream.SetPosition(Time: real);
+var
+ bytes: integer;
+begin
+ SDL_mutexP(lock);
+ seekPos := Trunc(Time * AV_TIME_BASE);
+ // FIXME: seek_flags = rel < 0 ? AVSEEK_FLAG_BACKWARD : 0
+ seekFlags := 0;
+ seekRequest := true;
+ SDL_CondSignal(resumeCond);
+ SDL_mutexV(lock);
+end;
function ParseAudio(streamPtr: Pointer): integer; cdecl;
var
packet: TAVPacket;
stream: TFFMpegDecodeStream;
+ seekTarget: int64;
+ eofState: boolean;
begin
stream := TFFMpegDecodeStream(streamPtr);
+ eofState := false;
- while (av_read_frame(stream.pFormatCtx, packet) >= 0) do
+ while (true) do
begin
+
+ SDL_mutexP(stream.lock);
+
+ // wait if end-of-file reached
+ if (eofState) then
+ begin
+ if (not (stream.seekRequest or stream.quitRequest)) then
+ begin
+ // signal end-of-file
+ stream.packetQueue.put(@EOFPacket);
+ // wait for reuse or destruction of stream
+ repeat
+ SDL_CondWait(stream.resumeCond, stream.lock);
+ until (stream.seekRequest or stream.quitRequest);
+ end;
+ eofState := false;
+ stream.SetEOF(false);
+ end;
+
+ if (stream.quitRequest) then
+ begin
+ break;
+ end;
+
+ // handle seek-request
+ if(stream.seekRequest) then
+ begin
+ // TODO: Do we need this?
+ // The position is converted to AV_TIME_BASE and then to the stream-specific base.
+ // Why not convert to the stream-specific one from the beginning.
+ seekTarget := av_rescale_q(stream.seekPos, AV_TIME_BASE_Q, stream.ffmpegStream^.time_base);
+ if(av_seek_frame(stream.pFormatCtx, stream.ffmpegStreamIndex,
+ seekTarget, stream.seekFlags) = 0) then
+ begin
+ Log.LogStatus(stream.pFormatCtx^.filename + ': error while seeking', 'UAudioDecoder_FFMpeg');
+ end
+ else
+ begin
+ stream.packetQueue.Flush();
+ stream.packetQueue.Put(@FlushPacket);
+ end;
+ stream.seekRequest := false;
+ end;
+
+ SDL_mutexV(stream.lock);
+
+
+ if(stream.packetQueue.size > MAX_AUDIOQ_SIZE) then
+ begin
+ SDL_Delay(10);
+ continue;
+ end;
+
+ if(av_read_frame(stream.pFormatCtx, packet) < 0) then
+ begin
+ // check for end-of-file (eof is not an error)
+ if(url_feof(@stream.pFormatCtx^.pb) <> 0) then
+ begin
+ eofState := true;
+ continue;
+ end;
+
+ // check for errors
+ if(url_ferror(@stream.pFormatCtx^.pb) = 0) then
+ begin
+ // no error -> wait for user input
+ SDL_Delay(100);
+ continue;
+ end
+ else
+ begin
+ // an error occured -> abort
+ // TODO: eof or quit?
+ eofState := true;
+ continue;
+ end;
+ end;
+
//writeln( 'ffmpeg - av_read_frame' );
- if (packet.stream_index = stream.ffmpegStreamID) then
+ if(packet.stream_index = stream.ffmpegStreamIndex) then
begin
//writeln( 'packet_queue_put' );
stream.packetQueue.put(@packet);
@@ -229,9 +370,6 @@ begin
//Writeln('Done: ' + inttostr(stream.packetQueue.nbPackets));
- // signal end-of-stream
- stream.packetQueue.put(@EOSPacket);
-
result := 0;
end;
@@ -242,10 +380,10 @@ var
begin
result := -1;
- if (EOS_Flag) then
+ if EOF then
exit;
- while true do
+ while(true) do
begin
while (audio_pkt_size > 0) do
begin
@@ -253,6 +391,8 @@ begin
data_size := bufSize;
// TODO: should be avcodec_decode_audio2 but this wont link on my ubuntu box.
+ // FIXME: with avcodec_decode_audio a package could contain several frames
+ // this is not handled yet
len1 := avcodec_decode_audio(pCodecCtx, @buffer,
data_size, audio_pkt_data, audio_pkt_size);
@@ -294,11 +434,17 @@ begin
audio_pkt_data := PChar(pkt.data);
audio_pkt_size := pkt.size;
- // check for end-of-stream
- if (audio_pkt_data = PChar(EOSPacket.data)) then
+ if (audio_pkt_data = PChar(FlushPacket.data)) then
+ begin
+ avcodec_flush_buffers(pCodecCtx);
+ continue;
+ end;
+
+ // check for end-of-file
+ if (audio_pkt_data = PChar(EOFPacket.data)) then
begin
- // end-of-stream reached -> set EOS-flag
- EOS_Flag := true;
+ // end-of-file reached -> set EOF-flag
+ SetEOF(true);
// note: buffer is not (even partially) filled -> no data to return
exit;
end;
@@ -316,9 +462,10 @@ var
len : integer;
begin
len := BufSize;
+ result := -1;
- // end-of-stream reached
- if (EOS_Flag) then
+ // end-of-file reached
+ if EOF then
exit;
while (len > 0) do begin
@@ -361,19 +508,6 @@ begin
result := BufSize;
end;
-function TFFMpegDecodeStream.GetPosition(): real;
-var
- bytes: integer;
-begin
- Result := 0;
-end;
-
-procedure TFFMpegDecodeStream.SetPosition(Time: real);
-var
- bytes: integer;
-begin
-end;
-
{ TAudioDecoder_FFMpeg }
@@ -388,21 +522,25 @@ begin
av_register_all();
- // init end-of-stream package
- av_init_packet(EOSPacket);
- EOSPacket.data := Pointer(PChar('EOS'));
+ // init end-of-file package
+ av_init_packet(EOFPacket);
+ EOFPacket.data := Pointer(PChar('EOF'));
+
+ // init flush package
+ av_init_packet(FlushPacket);
+ FlushPacket.data := Pointer(PChar('FLUSH'));
result := true;
end;
-class function TAudioDecoder_FFMpeg.FindAudioStreamID(pFormatCtx : PAVFormatContext): integer;
+class function TAudioDecoder_FFMpeg.FindAudioStreamIndex(pFormatCtx : PAVFormatContext): integer;
var
i : integer;
- streamID: integer;
+ streamIndex: integer;
stream : PAVStream;
begin
// Find the first audio stream
- streamID := -1;
+ streamIndex := -1;
for i := 0 to pFormatCtx^.nb_streams-1 do
begin
@@ -412,12 +550,12 @@ begin
if ( stream.codec^.codec_type = CODEC_TYPE_AUDIO ) then
begin
//Log.LogStatus('Found Audio Stream', 'UAudio_FFMpeg');
- streamID := i;
+ streamIndex := i;
break;
end;
end;
- result := streamID;
+ result := streamIndex;
end;
function TAudioDecoder_FFMpeg.Open(const Filename: string): TAudioDecodeStream;
@@ -449,11 +587,11 @@ begin
dump_format(pFormatCtx, 0, pchar(Filename), 0);
- ffmpegStreamID := FindAudioStreamID(pFormatCtx);
+ ffmpegStreamID := FindAudioStreamIndex(pFormatCtx);
if (ffmpegStreamID < 0) then
exit;
- //Log.LogStatus('Audio Stream ID is : '+ inttostr(ffmpegStreamID), 'UAudio_FFMpeg');
+ //Log.LogStatus('AudioStreamIndex is: '+ inttostr(ffmpegStreamID), 'UAudio_FFMpeg');
ffmpegStream := pFormatCtx.streams[ffmpegStreamID];
pCodecCtx := ffmpegStream^.codec;
@@ -503,7 +641,7 @@ var
begin
result := -1;
- if (pkt <> @EOSPacket) then
+ if ((pkt <> @EOFPacket) and (pkt <> @FlushPacket)) then
if (av_dup_packet(pkt) < 0) then
exit;
@@ -585,6 +723,27 @@ begin
end;
end;
+procedure TPacketQueue.Flush();
+var
+ pkt, pkt1: PAVPacketList;
+begin
+ SDL_LockMutex(Self.mutex);
+
+ pkt := Self.firstPkt;
+ while(pkt <> nil) do
+ begin
+ pkt1 := pkt^.next;
+ av_free_packet(pkt^.pkt);
+ av_freep(pkt);
+ pkt := pkt1;
+ end;
+ Self.lastPkt := nil;
+ Self.firstPkt := nil;
+ Self.nbPackets := 0;
+ Self.size := 0;
+
+ SDL_UnlockMutex(Self.mutex);
+end;
initialization
diff --git a/Game/Code/Classes/UAudioPlayback_Portaudio.pas b/Game/Code/Classes/UAudioPlayback_Portaudio.pas
index c2694e6d..c1abd0eb 100644
--- a/Game/Code/Classes/UAudioPlayback_Portaudio.pas
+++ b/Game/Code/Classes/UAudioPlayback_Portaudio.pas
@@ -22,6 +22,7 @@ uses
libc,
{$endif}
{$ENDIF}
+ sdl,
portaudio,
ULog,
UIni,
@@ -58,6 +59,7 @@ type
TAudioMixerStream = class
private
activeStreams: TList;
+ mixerBuffer: PChar;
public
constructor Create();
destructor Destroy(); override;
@@ -104,7 +106,7 @@ type
procedure Rewind;
procedure SetPosition(Time: real);
procedure Play;
- procedure Pause; //Pause Mod
+ procedure Pause;
procedure Stop;
procedure Close;
@@ -152,6 +154,8 @@ end;
destructor TAudioMixerStream.Destroy();
begin
+ if (mixerBuffer <> nil) then
+ Freemem(mixerBuffer);
activeStreams.Free;
end;
@@ -170,36 +174,33 @@ end;
function TAudioMixerStream.ReadData(Buffer: PChar; BufSize: integer): integer;
var
i: integer;
+ size: integer;
stream: TPortaudioPlaybackStream;
- dataAvailable: boolean;
begin
- dataAvailable := false;
+ result := BufSize;
- // search for playing stream
- if (activeStreams.Count > 0) then
+ // zero target-buffer (silence)
+ FillChar(Buffer^, BufSize, 0);
+
+ // resize mixer-buffer
+ ReallocMem(mixerBuffer, BufSize);
+ if (mixerBuffer = nil) then
+ Exit;
+
+ writeln('Mix: ' + inttostr(activeStreams.Count));
+
+ for i := 0 to activeStreams.Count-1 do
begin
- for i := 0 to activeStreams.Count-1 do
+ stream := TPortaudioPlaybackStream(activeStreams[i]);
+ if (stream.GetStatus() = sPlaying) then
begin
- stream := TPortaudioPlaybackStream(activeStreams[i]);
- if (stream.getStatus = sPlaying) then
+ // fetch data from current stream
+ size := stream.ReadData(mixerBuffer, BufSize);
+ if (size > 0) then
begin
- dataAvailable := true;
- break;
+ SDL_MixAudio(PUInt8(Buffer), PUInt8(mixerBuffer), size, SDL_MIX_MAXVOLUME);
end;
end;
-
- // fetch data from current stream
- if (dataAvailable) then
- begin
- result := stream.ReadData(Buffer, BufSize);
- end;
- end;
-
- // return silence
- if ((not dataAvailable) or (result = -1)) then
- begin
- FillChar(Buffer^, BufSize, 0);
- result := BufSize;
end;
end;
@@ -219,7 +220,13 @@ end;
procedure TPortaudioPlaybackStream.Play();
begin
+ if (status <> sPaused) then
+ begin
+ // rewind
+ decodeStream.Position := 0;
+ end;
status := sPlaying;
+ mixerStream.AddStream(Self);
end;
procedure TPortaudioPlaybackStream.Pause();
@@ -234,7 +241,9 @@ end;
procedure TPortaudioPlaybackStream.Close();
begin
- Stop();
+ status := sStopped;
+ Loaded := false;
+ // TODO: cleanup decode-stream
end;
function TPortaudioPlaybackStream.IsLoaded(): boolean;
@@ -265,8 +274,8 @@ end;
function TPortaudioPlaybackStream.ReadData(Buffer: PChar; BufSize: integer): integer;
begin
result := decodeStream.ReadData(Buffer, BufSize);
- // end-of-stream reached -> stop playback
- if (decodeStream.EOS) then
+ // end-of-file reached -> stop playback
+ if (decodeStream.EOF) then
begin
status := sStopped;
end;
@@ -424,7 +433,9 @@ end;
procedure TAudioPlayback_Portaudio.SetLoop(Enabled: boolean);
begin
- MusicStream.SetLoop(Enabled);
+ if (MusicStream <> nil) then
+ if (MusicStream.IsLoaded) then
+ MusicStream.SetLoop(Enabled);
end;
function TAudioPlayback_Portaudio.Open(Filename: string): boolean;
@@ -449,8 +460,6 @@ begin
end;
procedure TAudioPlayback_Portaudio.SetPosition(Time: real);
-var
- bytes: integer;
begin
if (MusicStream.IsLoaded) then
begin
@@ -458,10 +467,26 @@ begin
end;
end;
+function TAudioPlayback_Portaudio.GetPosition: real;
+begin
+ if (MusicStream.IsLoaded) then
+ Result := MusicStream.GetPosition();
+end;
+
+function TAudioPlayback_Portaudio.Length: real;
+begin
+ Result := 0;
+ if assigned( MusicStream ) then
+ if (MusicStream.IsLoaded) then
+ begin
+ Result := MusicStream.GetLength();
+ end;
+end;
+
procedure TAudioPlayback_Portaudio.Play;
begin
if (MusicStream <> nil) then
- if (MusicStream.IsLoaded()) then
+ if (MusicStream.IsLoaded) then
begin
if (MusicStream.GetLoop()) then
begin
@@ -472,12 +497,10 @@ begin
end;
end;
-procedure TAudioPlayback_Portaudio.Pause; //Pause Mod
+procedure TAudioPlayback_Portaudio.Pause;
begin
if (MusicStream <> nil) then
- if (MusicStream.IsLoaded()) then begin
- MusicStream.Pause(); // Pauses Song
- end;
+ MusicStream.Pause();
end;
procedure TAudioPlayback_Portaudio.Stop;
@@ -492,37 +515,10 @@ begin
MusicStream.Close();
end;
-function TAudioPlayback_Portaudio.Length: real;
-begin
- Result := 0;
- if assigned( MusicStream ) then
- begin
- Result := MusicStream.GetLength();
- end;
-end;
-
-function TAudioPlayback_Portaudio.getPosition: real;
-var
- bytes: integer;
-begin
- Result := 0;
-
-(*
- bytes := BASS_ChannelGetPosition(BASS);
- Result := BASS_ChannelBytes2Seconds(BASS, bytes);
-*)
-end;
-
function TAudioPlayback_Portaudio.Finished: boolean;
begin
- Result := false;
-
-(*
- if BASS_ChannelIsActive(BASS) = BASS_ACTIVE_STOPPED then
- begin
- Result := true;
- end;
-*)
+ if MusicStream <> nil then
+ Result := (MusicStream.GetStatus() = sStopped);
end;
procedure TAudioPlayback_Portaudio.PlayStart;
@@ -623,7 +619,6 @@ begin
end;
playbackStream := TPortaudioPlaybackStream.Create(decodeStream);
- mixerStream.AddStream(playbackStream);
//Add CustomSound
csIndex := High(CustomSounds) + 1;