From e5bdcfc8e584d2867d0ad7995d18bd30c7874d1b Mon Sep 17 00:00:00 2001 From: tobigun Date: Fri, 28 Dec 2007 11:46:04 +0000 Subject: New class-structure seperates decoders/input and playback. The files aren't used in usdx until they are stable so they will not conflict with the old structure. The older files (UAudio_Bass etc.) will be replaced then. git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@752 b956fd51-792f-4845-bead-9b4dfca2ff2c --- Game/Code/Classes/UAudioDecoder_FFMpeg.pas | 600 +++++++++++++++++++++++++++++ 1 file changed, 600 insertions(+) create mode 100644 Game/Code/Classes/UAudioDecoder_FFMpeg.pas (limited to 'Game/Code/Classes/UAudioDecoder_FFMpeg.pas') 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. -- cgit v1.2.3