aboutsummaryrefslogtreecommitdiffstats
path: root/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
diff options
context:
space:
mode:
Diffstat (limited to 'Game/Code/Classes/UAudioDecoder_FFMpeg.pas')
-rw-r--r--Game/Code/Classes/UAudioDecoder_FFMpeg.pas600
1 files changed, 600 insertions, 0 deletions
diff --git a/Game/Code/Classes/UAudioDecoder_FFMpeg.pas b/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
new file mode 100644
index 00000000..54055454
--- /dev/null
+++ b/Game/Code/Classes/UAudioDecoder_FFMpeg.pas
@@ -0,0 +1,600 @@
+unit UAudioDecoder_FFMpeg;
+
+(*******************************************************************************
+
+This unit is primarily based upon -
+ http://www.dranger.com/ffmpeg/ffmpegtutorial_all.html
+
+ and tutorial03.c
+
+ http://www.inb.uni-luebeck.de/~boehme/using_libavcodec.html
+
+*******************************************************************************)
+
+interface
+
+{$IFDEF FPC}
+ {$MODE Delphi}
+{$ENDIF}
+
+{$I switches.inc}
+
+
+uses Classes,
+ {$IFDEF win32}
+ windows,
+ {$ENDIF}
+ SysUtils,
+ SDL,
+ avcodec, // FFMpeg Audio file decoding
+ avformat,
+ avutil,
+ ULog,
+ UMusic;
+
+implementation
+
+uses
+ {$IFDEF LAZARUS}
+ lclintf,
+ {$ifndef win32}
+ libc,
+ {$endif}
+ {$ENDIF}
+ UIni,
+ UMain,
+ UThemes;
+
+
+type
+ PPacketQueue = ^TPacketQueue;
+ TPacketQueue = class
+ private
+ firstPkt,
+ lastPkt : PAVPacketList;
+ nbPackets : integer;
+ size : integer;
+ mutex : PSDL_Mutex;
+ cond : PSDL_Cond;
+ quit : boolean;
+
+ public
+ constructor Create();
+ destructor Destroy(); override;
+
+ function Put(pkt : PAVPacket): integer;
+ function Get(var pkt: TAVPacket; block: boolean): integer;
+ end;
+
+var
+ EOSPacket: TAVPacket;
+
+type
+ PAudioBuffer = ^TAudioBuffer;
+ TAudioBuffer = array[0 .. (AVCODEC_MAX_AUDIO_FRAME_SIZE * 3 div 2)-1] of byte;
+
+type
+ TFFMpegDecodeStream = class(TAudioDecodeStream)
+ private
+ status: TStreamStatus;
+
+ EOS_Flag: boolean; // end-of-stream flag
+
+ parseThread: PSDL_Thread;
+ packetQueue: TPacketQueue;
+
+ // FFMpeg internal data
+ pFormatCtx : PAVFormatContext;
+ pCodecCtx : PAVCodecContext;
+ pCodec : PAVCodec;
+ ffmpegStreamID : Integer;
+ ffmpegStream : PAVStream;
+
+ // "static" vars for DecodeFrame
+ pkt : TAVPacket;
+ audio_pkt_data : PChar;
+ audio_pkt_size : integer;
+
+ // "static" vars for AudioCallback
+ audio_buf_index : cardinal;
+ audio_buf_size : cardinal;
+ audio_buf : TAudioBuffer;
+
+ function DecodeFrame(var buffer: TAudioBuffer; bufSize: integer): integer;
+ public
+ constructor Create(pFormatCtx: PAVFormatContext;
+ pCodecCtx: PAVCodecContext; pCodec: PAVCodec;
+ ffmpegStreamID : Integer; ffmpegStream: PAVStream);
+ destructor Destroy(); override;
+
+ procedure Close(); override;
+
+ function GetLength(): real; override;
+ function GetChannelCount(): cardinal; override;
+ function GetSampleRate(): cardinal; override;
+ function GetPosition: real; override;
+ procedure SetPosition(Time: real); override;
+ function IsEOS(): boolean; override;
+
+ function ReadData(Buffer: PChar; BufSize: integer): integer; override;
+ end;
+
+type
+ TAudioDecoder_FFMpeg = class( TInterfacedObject, IAudioDecoder )
+ private
+ class function FindAudioStreamID(pFormatCtx : PAVFormatContext): integer;
+ public
+ function GetName: String;
+
+ function InitializeDecoder(): boolean;
+ function Open(const Filename: string): TAudioDecodeStream;
+ end;
+
+function ParseAudio(streamPtr: Pointer): integer; cdecl; forward;
+
+var
+ singleton_AudioDecoderFFMpeg : IAudioDecoder;
+
+
+{ TFFMpegDecodeStream }
+
+constructor TFFMpegDecodeStream.Create(pFormatCtx: PAVFormatContext;
+ pCodecCtx: PAVCodecContext; pCodec: PAVCodec;
+ ffmpegStreamID : Integer; ffmpegStream: PAVStream);
+begin
+ inherited Create();
+
+ status := sStopped;
+ packetQueue := TPacketQueue.Create();
+
+ audio_pkt_data := nil;
+ audio_pkt_size := 0;
+
+ audio_buf_index := 0;
+ audio_buf_size := 0;
+
+ FillChar(pkt, sizeof(TAVPacket), #0);
+
+ Self.pFormatCtx := pFormatCtx;
+ Self.pCodecCtx := pCodecCtx;
+ Self.pCodec := pCodec;
+ Self.ffmpegStreamID := ffmpegStreamID;
+ Self.ffmpegStream := ffmpegStream;
+
+ EOS_Flag := false;
+
+ parseThread := SDL_CreateThread(@ParseAudio, Self);
+end;
+
+destructor TFFMpegDecodeStream.Destroy();
+begin
+ packetQueue.Free();
+ //SDL_WaitThread(parseThread, nil);
+ inherited;
+end;
+
+procedure TFFMpegDecodeStream.Close();
+begin
+ // Close the codec
+ avcodec_close(pCodecCtx);
+
+ // Close the video file
+ av_close_input_file(pFormatCtx);
+
+ // TODO: abort thread
+end;
+
+function TFFMpegDecodeStream.GetLength(): real;
+begin
+ result := pFormatCtx^.duration / AV_TIME_BASE;
+end;
+
+function TFFMpegDecodeStream.GetChannelCount(): cardinal;
+begin
+ result := pCodecCtx^.channels;
+end;
+
+function TFFMpegDecodeStream.GetSampleRate(): cardinal;
+begin
+ result := pCodecCtx^.sample_rate;
+end;
+
+function TFFMpegDecodeStream.IsEOS(): boolean;
+begin
+ result := EOS_Flag;
+end;
+
+
+function ParseAudio(streamPtr: Pointer): integer; cdecl;
+var
+ packet: TAVPacket;
+ stream: TFFMpegDecodeStream;
+begin
+ stream := TFFMpegDecodeStream(streamPtr);
+
+ while (av_read_frame(stream.pFormatCtx, packet) >= 0) do
+ begin
+ //writeln( 'ffmpeg - av_read_frame' );
+
+ if (packet.stream_index = stream.ffmpegStreamID) then
+ begin
+ //writeln( 'packet_queue_put' );
+ stream.packetQueue.put(@packet);
+ end
+ else
+ begin
+ av_free_packet(packet);
+ end;
+ end;
+
+ //Writeln('Done: ' + inttostr(stream.packetQueue.nbPackets));
+
+ // signal end-of-stream
+ stream.packetQueue.put(@EOSPacket);
+
+ result := 0;
+end;
+
+function TFFMpegDecodeStream.DecodeFrame(var buffer: TAudioBuffer; bufSize: integer): integer;
+var
+ len1,
+ data_size: integer;
+begin
+ result := -1;
+
+ if (EOS_Flag) then
+ exit;
+
+ while true do
+ begin
+ while (audio_pkt_size > 0) do
+ begin
+ //writeln( 'got audio packet' );
+ data_size := bufSize;
+
+ // TODO: should be avcodec_decode_audio2 but this wont link on my ubuntu box.
+ len1 := avcodec_decode_audio(pCodecCtx, @buffer,
+ data_size, audio_pkt_data, audio_pkt_size);
+
+ //writeln('avcodec_decode_audio : ' + inttostr( len1 ));
+
+ if(len1 < 0) then
+ begin
+ // if error, skip frame
+ //writeln( 'Skip audio frame' );
+ audio_pkt_size := 0;
+ break;
+ end;
+
+ Inc(audio_pkt_data, len1);
+ Dec(audio_pkt_size, len1);
+
+ if (data_size <= 0) then
+ begin
+ // No data yet, get more frames
+ continue;
+ end;
+
+ // We have data, return it and come back for more later
+ result := data_size;
+ exit;
+ end;
+
+ if (pkt.data <> nil) then
+ begin
+ av_free_packet(pkt);
+ end;
+
+ if (packetQueue.quit) then
+ exit;
+
+ if (packetQueue.Get(pkt, true) < 0) then
+ exit;
+
+ audio_pkt_data := PChar(pkt.data);
+ audio_pkt_size := pkt.size;
+
+ // check for end-of-stream
+ if (audio_pkt_data = PChar(EOSPacket.data)) then
+ begin
+ // end-of-stream reached -> set EOS-flag
+ EOS_Flag := true;
+ // note: buffer is not (even partially) filled -> no data to return
+ exit;
+ end;
+
+ //writeln( 'Audio Packet Size - ' + inttostr(audio_pkt_size) );
+ end;
+end;
+
+function TFFMpegDecodeStream.ReadData(Buffer : PChar; BufSize: integer): integer;
+var
+ outStream : TFFMpegDecodeStream;
+ len1,
+ audio_size : integer;
+ pSrc : Pointer;
+ len : integer;
+begin
+ len := BufSize;
+
+ // end-of-stream reached
+ if (EOS_Flag) then
+ exit;
+
+ while (len > 0) do begin
+ if (audio_buf_index >= audio_buf_size) then
+ begin
+ // We have already sent all our data; get more
+ audio_size := DecodeFrame(audio_buf, sizeof(TAudioBuffer));
+ //writeln('audio_decode_frame : '+ inttostr(audio_size));
+
+ if(audio_size < 0) then
+ begin
+ // If error, output silence
+ audio_buf_size := 1024;
+ FillChar(audio_buf, audio_buf_size, #0);
+ //writeln( 'Silence' );
+ end
+ else
+ begin
+ audio_buf_size := audio_size;
+ end;
+ audio_buf_index := 0;
+ end;
+
+ len1 := audio_buf_size - audio_buf_index;
+ if (len1 > len) then
+ len1 := len;
+
+ pSrc := PChar(@audio_buf) + audio_buf_index;
+ {$ifdef WIN32}
+ CopyMemory(Buffer, pSrc , len1);
+ {$else}
+ memcpy(Buffer, pSrc , len1);
+ {$endif}
+
+ Dec(len, len1);
+ Inc(PChar(Buffer), len1);
+ Inc(audio_buf_index, len1);
+ end;
+
+ 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 }
+
+function TAudioDecoder_FFMpeg.GetName: String;
+begin
+ result := 'FFMpeg_Decoder';
+end;
+
+function TAudioDecoder_FFMpeg.InitializeDecoder: boolean;
+begin
+ //Log.LogStatus('InitializeDecoder', 'UAudioDecoder_FFMpeg');
+
+ av_register_all();
+
+ // init end-of-stream package
+ av_init_packet(EOSPacket);
+ EOSPacket.data := Pointer(PChar('EOS'));
+
+ result := true;
+end;
+
+class function TAudioDecoder_FFMpeg.FindAudioStreamID(pFormatCtx : PAVFormatContext): integer;
+var
+ i : integer;
+ streamID: integer;
+ stream : PAVStream;
+begin
+ // Find the first audio stream
+ streamID := -1;
+
+ for i := 0 to pFormatCtx^.nb_streams-1 do
+ begin
+ //Log.LogStatus('aFormatCtx.streams[i] : ' + inttostr(i), 'UAudio_FFMpeg');
+ stream := pFormatCtx^.streams[i];
+
+ if ( stream.codec^.codec_type = CODEC_TYPE_AUDIO ) then
+ begin
+ //Log.LogStatus('Found Audio Stream', 'UAudio_FFMpeg');
+ streamID := i;
+ break;
+ end;
+ end;
+
+ result := streamID;
+end;
+
+function TAudioDecoder_FFMpeg.Open(const Filename: string): TAudioDecodeStream;
+var
+ pFormatCtx : PAVFormatContext;
+ pCodecCtx : PAVCodecContext;
+ pCodec : PAVCodec;
+ ffmpegStreamID : Integer;
+ ffmpegStream : PAVStream;
+ wanted_spec,
+ csIndex : integer;
+ stream : TFFMpegDecodeStream;
+begin
+ result := nil;
+
+ if (not FileExists(Filename)) then
+ begin
+ Log.LogStatus('LoadSoundFromFile: Sound not found "' + Filename + '"', 'UAudio_FFMpeg');
+ exit;
+ end;
+
+ // Open audio file
+ if (av_open_input_file(pFormatCtx, PChar(Filename), nil, 0, nil) > 0) then
+ exit;
+
+ // Retrieve stream information
+ if (av_find_stream_info(pFormatCtx) < 0) then
+ exit;
+
+ dump_format(pFormatCtx, 0, pchar(Filename), 0);
+
+ ffmpegStreamID := FindAudioStreamID(pFormatCtx);
+ if (ffmpegStreamID < 0) then
+ exit;
+
+ //Log.LogStatus('Audio Stream ID is : '+ inttostr(ffmpegStreamID), 'UAudio_FFMpeg');
+
+ ffmpegStream := pFormatCtx.streams[ffmpegStreamID];
+ pCodecCtx := ffmpegStream^.codec;
+
+ pCodec := avcodec_find_decoder(pCodecCtx^.codec_id);
+ if (pCodec = nil) then
+ begin
+ Log.LogStatus('Unsupported codec!', 'UAudio_FFMpeg');
+ exit;
+ end;
+
+ avcodec_open(pCodecCtx, pCodec);
+ //writeln( 'Opened the codec' );
+
+ stream := TFFMpegDecodeStream.Create(pFormatCtx, pCodecCtx, pCodec,
+ ffmpegStreamID, ffmpegStream);
+
+ result := stream;
+end;
+
+
+{ TPacketQueue }
+
+constructor TPacketQueue.Create();
+begin
+ inherited;
+
+ firstPkt := nil;
+ lastPkt := nil;
+ nbPackets := 0;
+ size := 0;
+
+ mutex := SDL_CreateMutex();
+ cond := SDL_CreateCond();
+end;
+
+destructor TPacketQueue.Destroy();
+begin
+ SDL_DestroyMutex(mutex);
+ SDL_DestroyCond(cond);
+ inherited;
+end;
+
+function TPacketQueue.Put(pkt : PAVPacket): integer;
+var
+ pkt1 : PAVPacketList;
+begin
+ result := -1;
+
+ if (pkt <> @EOSPacket) then
+ if (av_dup_packet(pkt) < 0) then
+ exit;
+
+ pkt1 := av_malloc(sizeof(TAVPacketList));
+ if (pkt1 = nil) then
+ exit;
+
+ pkt1^.pkt := pkt^;
+ pkt1^.next := nil;
+
+
+ SDL_LockMutex(Self.mutex);
+ try
+
+ if (Self.lastPkt = nil) then
+ Self.firstPkt := pkt1
+ else
+ Self.lastPkt^.next := pkt1;
+
+ Self.lastPkt := pkt1;
+ inc(Self.nbPackets);
+
+ //Writeln('Put: ' + inttostr(nbPackets));
+
+ Self.size := Self.size + pkt1^.pkt.size;
+ SDL_CondSignal(Self.cond);
+
+ finally
+ SDL_UnlockMutex(Self.mutex);
+ end;
+
+ result := 0;
+end;
+
+function TPacketQueue.Get(var pkt: TAVPacket; block: boolean): integer;
+var
+ pkt1 : PAVPacketList;
+begin
+ result := -1;
+
+ SDL_LockMutex(Self.mutex);
+ try
+ while true do
+ begin
+ if (quit) then
+ exit;
+
+ pkt1 := Self.firstPkt;
+
+ if (pkt1 <> nil) then
+ begin
+ Self.firstPkt := pkt1.next;
+ if (Self.firstPkt = nil) then
+ Self.lastPkt := nil;
+ dec(Self.nbPackets);
+
+ //Writeln('Get: ' + inttostr(nbPackets));
+
+ Self.size := Self.size - pkt1^.pkt.size;
+ pkt := pkt1^.pkt;
+ av_free(pkt1);
+
+ result := 1;
+ break;
+ end
+ else
+ if (not block) then
+ begin
+ result := 0;
+ break;
+ end
+ else
+ begin
+ SDL_CondWait(Self.cond, Self.mutex);
+ end;
+ end;
+ finally
+ SDL_UnlockMutex(Self.mutex);
+ end;
+end;
+
+
+
+initialization
+ singleton_AudioDecoderFFMpeg := TAudioDecoder_FFMpeg.create();
+
+ //writeln( 'UAudioDecoder_FFMpeg - Register Decoder' );
+ AudioManager.add( singleton_AudioDecoderFFMpeg );
+
+finalization
+ AudioManager.Remove( singleton_AudioDecoderFFMpeg );
+
+
+end.