(*
  this file is a part of audio components suite v 2.4.
  see the license file for more details.
  you can contact me at mail@z0m3ie.de
*)

{
$Log: acs_dsaudio.pas,v $
Revision 1.1  2005/12/19 18:36:38  z0m3ie
*** empty log message ***

Revision 1.3  2005/11/28 21:57:24  z0m3ie
mostly FileOut fixes
moved PBuffer to PBuffer8
set all to dynamically Buffering

Revision 1.2  2005/09/13 04:04:50  z0m3ie
First release without Components for Fileformats
only TFileIn and TFileOut are Visible

Revision 1.1  2005/09/13 03:13:57  z0m3ie
*** empty log message ***

Revision 1.1  2005/09/12 22:04:52  z0m3ie
modified structure again, fileformats are now in an sperat folder.
all File In/Out classes are capsulated from TFileIn and TFileOut

Revision 1.4  2005/09/11 18:06:26  z0m3ie
first working Version

Revision 1.3  2005/09/10 08:25:40  z0m3ie
*** empty log message ***

Revision 1.2  2005/09/07 21:13:24  z0m3ie
*** empty log message ***

Revision 1.1  2005/09/07 20:53:22  z0m3ie
begon to add MPEG and WMA support using DirectX

}
unit acs_dsaudio;

{$ifdef linux}{$message error 'unit not supported'}{$endif linux}

{$DEFINE DYNAMIC_LINK_ALL}

interface

uses
  ACS_File,ACS_Classes,DirectShow9,Classes,ActiveX,MMSystem,Windows;

const
  BUF_SIZE = $8000; // 32k

type
  { TDSIn }

  TDSIn = class(TACSFileIn)
  private
    buf : PBuffer8;  // ring buffer
    FxFormat: TWaveFormatEx;
    FxAMMultiMediaStream: IAMMultiMediaStream;
    FxGraphBuilder: IGraphBuilder;
    FxMediaSeeking: IMediaSeeking;
    FxMediaControl: IMediaControl;
    FxAudioMediaStream: IAudioMediaStream;
    FxMediaStream: IMediaStream;
    FxAudioStreamSample: IAudioStreamSample;
    FxAudioData: IAudioData;
    FxBuffer: Pointer;
    FxBufferSize: DWord;
    FDuration,
    FxPosition,
    FxSelStart,
    FxSelLength,
    FxLastReadingStartTime,
    FxLastReadingEndTime : Stream_Time;
    FSeekScale : Integer;
    function Read(const Buffer; xSize: DWord): DWord;
    procedure SetPosition( Value: STREAM_TIME );
  protected
    procedure OpenFile; override;
    procedure CloseFile; override;

    procedure Init; override;
    procedure Flush; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function GetData(Buffer : Pointer; BufferSize : Integer): Integer; override;
    function Seek(SampleNum : Integer) : Boolean; override;
  end;

const
  DSUSER_HRESULT = HResult($08000000);
  DSUSER_INVALIDSIZE = DSUSER_HRESULT + 1;

function ErrorCheck( Value: HRESULT ): HRESULT; { Check the result of a COM operation }

implementation

procedure TDSIn.OpenFile;
var
  v : WideString;
begin
  FValid := True;
  if FOpened = 0 then
  begin
    ErrorCheck( CoCreateInstance( CLSID_AMMultiMediaStream, nil, CLSCTX_INPROC_SERVER,IID_IAMMultiMediaStream, FxAMMultiMediaStream ) );
    ErrorCheck( FxAMMultiMediaStream.Initialize(STREAMTYPE_READ, AMMSF_NOGRAPHTHREAD, nil) );
    ErrorCheck( FxAMMultiMediaStream.AddMediaStream(nil, @MSPID_PrimaryAudio, 0, FxMediaStream) );
    ErrorCheck( FxAMMultiMediaStream.GetMediaStream(MSPID_PrimaryAudio, FxMediaStream) );
    v := FFileName;
    ErrorCheck( FxAMMultiMediaStream.OpenFile(PWideChar(v), 0) );
    ErrorCheck( FxAMMultiMediaStream.GetFilterGraph(FxGraphBuilder) );
    ErrorCheck( FxGraphBuilder.QueryInterface(IID_IMediaControl, FxMediaControl) );
    ErrorCheck( FxGraphBuilder.QueryInterface(IID_IMediaSeeking, FxMediaSeeking) );
    ErrorCheck( FxMediaStream.QueryInterface(IID_IAudioMediaStream, FxAudioMediaStream) );
    ErrorCheck( FxAudioMediaStream.GetFormat(FxFormat) );
    ErrorCheck( CoCreateInstance(CLSID_AMAudioData, nil, CLSCTX_INPROC_SERVER,IID_IAudioData, FxAudioData) );
    ErrorCheck( FxAudioData.SetFormat(FxFormat) );
    ErrorCheck( FxAudioMediaStream.CreateSample(FxAudioData, 0, FxAudioStreamSample) );
    ErrorCheck( FxAMMultiMediaStream.GetDuration(FDuration) );
    ErrorCheck( FxAMMultiMediaStream.SetState( STREAMSTATE_RUN ) );
    FxSelLength := 0;
    FSR   := FxFormat.nSamplesPerSec;
    FBPS  := FxFormat.wBitsPerSample;
    FChan := FxFormat.nChannels;
    FTotalSamples := FSize;
    FSeekable     := TRUE;
    FSize := (FDuration div 10000000) *
            FxFormat.nSamplesPerSec *
            FxFormat.nChannels *
            FxFormat.wBitsPerSample div 8;
    FSeekScale := FDuration div FSize;
  end;
  Inc(FOpened);
end;

procedure TDSIn.CloseFile;
begin
  if FOpened = 1 then
  begin
    if Assigned(FxAMMultiMediaStream) then
    ErrorCheck( FxAMMultiMediaStream.SetState( STREAMSTATE_STOP ) );
    FxAudioStreamSample  := nil;
    FxAudioData          := nil;
    FxAudioMediaStream   := nil;
    FxMediaStream        := nil;
    FxMediaSeeking       := nil;
    FxMediaControl       := nil;
    FxGraphBuilder       := nil;
    FxAMMultiMediaStream := nil;
  end;
  if FOpened > 0 then Dec(FOpened);
end;

function TDSIn.GetData(Buffer: Pointer; BufferSize: Integer): Integer;
var
  nDone : Integer;
  nOffs : Integer;
begin
  if not Busy then raise EACSException.Create('The Stream is not opened');
  if BufStart > BufEnd then
  begin
    if FOffset <> 0 then
    begin
      FPosition := FOffset;
      if FPosition < 0 then
        FPosition := 0
      else if FPosition > FSize then
        FPosition := FSize;
      SetPosition(Int64(FPosition) * FSeekScale);
      FOffset := 0;
    end;
    BufStart := 1;
    nDone := Read(Buf^, BUF_SIZE);
    if nDone = 0 then
    begin
      if FLoop then
      begin
        SetPosition(0); // just rewind
        nDone := Read(Buf^, BUF_SIZE);
      end else
      begin
        Result := 0;
        Exit;
      end;
    end;
    BufEnd := nDone;
  end;

  if BufferSize < (BufEnd - BufStart + 1) then
    Result := BufferSize
  else
    Result := BufEnd - BufStart + 1;

  Move(Buf^[BufStart], Buffer^, Result);
  Inc(BufStart, Result);
  Inc(FPosition, Result);
end;

constructor TDSIn.Create(AOwner: TComponent);
var
  AMovie: IGraphBuilder;
begin
  QzInitialize(nil);
  ErrorCheck(CoCreateInstance( CLSID_FilterGraph, nil, CLSCTX_INPROC_SERVER,IID_IGraphBuilder, AMovie));
  inherited Create(AOwner);
end;

destructor TDSIn.Destroy;
begin
  inherited Destroy;
end;

procedure TDSIn.Init;
begin
  inherited Init;
  GetMem(Buf, BUF_SIZE);
end;

procedure TDSIn.Flush;
begin
  FreeMem(Buf);
  inherited Flush;
end;

function TDSIn.Seek(SampleNum: Integer): Boolean;
begin
  FOffset := SampleNum;
end;

function TDSIn.Read(const Buffer; xSize: DWord): DWord;
var
  hr : DWord;
  Tmp: STREAM_TIME;
begin
  Result := 0;
  if (xSize <= 0) then ErrorCheck(DSUSER_INVALIDSIZE);
  if (FxSelLength <> 0) and (FxPosition >= FxSelStart + FxSelLength) then exit;
  if (@Buffer <> FxBuffer) or (xSize <> FxBufferSize) then
  begin
    FxBuffer := @Buffer;
    FxBufferSize := xSize;
    ErrorCheck( FxAudioData.SetBuffer( FxBufferSize, FxBuffer, 0 ) );
  end;
  hr := FxAudioStreamSample.Update(0, 0, nil, 0);
  if (hr = MS_S_ENDOFSTREAM) or (hr = MS_E_NOSTREAM) then exit;
  ErrorCheck(hr);
  ErrorCheck( FxAudioData.GetInfo(FxBufferSize, FxBuffer, Result) );
  ErrorCheck( FxAudioStreamSample.GetSampleTimes( FxLastReadingStartTime,FxLastReadingEndTime, Tmp ) );
  if FxLastReadingStartTime > FxLastReadingEndTime then
    FxLastReadingStartTime := FxLastReadingEndTime;
  if (FxSelLength <> 0) and (FxLastReadingEndTime > FxLastReadingStartTime) and
    (FxSelStart + FxSelLength < FxLastReadingEndTime) then
  begin
    Result := DWord(Trunc(((Result *
      (FxSelStart + FxSelLength - FxLastReadingStartTime)) /
      (FxLastReadingEndTime - FxLastReadingStartTime)))) and
      (not(FxFormat.nBlockAlign-1));
    FxLastReadingEndTime := FxSelStart + FxSelLength;
  end;
  FxPosition := FxLastReadingEndTime;
end;

procedure TDSIn.SetPosition( Value: STREAM_TIME );
var pfs: TFilterState;
begin
  if (Value <> FxPosition) then
  begin
    if (Value < FxSelStart) then
      Value := FxSelStart
    else
      if (Value > FDuration) then
        Value := FDuration
      else
        if (FxSelLength <> 0) and (Value > FxSelStart + FxSelLength) then
          Value := FxSelStart + FxSelLength;
    ErrorCheck(FxMediaControl.StopWhenReady );
    ErrorCheck(FxMediaSeeking.SetPositions(Value,AM_SEEKING_AbsolutePositioning, Value, AM_SEEKING_NoPositioning));
    ErrorCheck(FxMediaControl.Run );
    ErrorCheck(FxMediaControl.GetState(INFINITE, pfs) );
    FxPosition := Value;
  end;
end;

function ErrorCheck( Value: HRESULT ): HRESULT; { Check the result of a COM operation }
var
  S: String;
  S2: array [0..300] of Char;
begin
  Result := Value;
  if (Value <> S_OK) then
  begin
    Case DWord(Value) of
      DSUSER_INVALIDSIZE: S:='Invalid buffer size.';
      DWord(REGDB_E_CLASSNOTREG): S:='A specified class is not registered in the registration database.';
      DWord(CLASS_E_NOAGGREGATION): S:='This class cannot be created as part of an aggregate.';
      DWord(E_ABORT): S:='The update aborted.';
      DWOrd(E_INVALIDARG): S:='One of the parameters is invalid.';
      DWord(E_POINTER): S:='This method tried to access an invalid pointer.';
      DWord(E_NOINTERFACE): S:='No interface.';
      MS_S_PENDING: S:='The asynchronous update is pending.';
      MS_S_NOUPDATE: S:='Sample was not updated after forced completion.';
      MS_S_ENDOFSTREAM: S:='Reached the end of the stream; the sample wasn''t updated.';
      MS_E_SAMPLEALLOC: S:='An IMediaStream object could not be removed from an IMultiMediaStream object because it still contains at least one allocated sample.';
      MS_E_PURPOSEID: S:='The specified purpose ID can''t be used for the call.';
      MS_E_NOSTREAM: S:='No stream can be found with the specified attributes.';
      MS_E_NOSEEKING: S:='One or more media streams don''t support seeking.';
      MS_E_INCOMPATIBLE: S:='The stream formats are not compatible.';
      MS_E_BUSY: S:='This sample already has a pending update.';
      MS_E_NOTINIT: S:='The object can''t accept the call because its initialize function or equivalent has not been called.';
      MS_E_SOURCEALREADYDEFINED: S:='Source already defined.';
      MS_E_INVALIDSTREAMTYPE: S:='The stream type is not valid for this operation.';
      MS_E_NOTRUNNING: S:='The IMultiMediaStream object is not in running state.';
      Else
        begin
          if AMGetErrorText( Value, s2, High(s2) ) = 0 then
            S:='Unrecognized error value.'
          else
            S:=String( s2 );
        end;
    end;
    raise EACSException.Create(S);
  end;
end ;

initialization
  FileFormats.Add('mp3','Mpeg Audio Layer III',TDSIn);
  FileFormats.Add('mp2','Mpeg Audio Layer II',TDSIn);
  FileFormats.Add('mpeg','Mpeg Audio',TDSIn);
  FileFormats.Add('wma','Windows Media Audio',TDSIn);

end.

