what is the simpliest way to play sound from array data in delphi

后端 未结 4 1587
说谎
说谎 2020-12-31 21:01

Is there any simple function? I am searching something like that

Play(@data, 44000, 100 {time});

4条回答
  •  时光取名叫无心
    2020-12-31 21:11

    I have worked quite a lot with PCM audio manipulation. I always use this function when playing short sequences of custom waveform audio data:

    var
      PlaySoundStopper: PBoolean;
      SoundPlayerActive: boolean = false;
    
    procedure PlaySound(const Sound: TASSound);
    var
      hWave: HWAVEOUT;
      hdr: TWaveHdr;
      buf: PAnsiChar;
      fmt: TWaveFormatEx;
      i: Integer;
      n: Integer;
    begin
    
      try
    
        with fmt do
        begin
          wFormatTag := WAVE_FORMAT_PCM;
          nChannels := length(Sound.Channels);
          nSamplesPerSec := Sound.SampleRate;
          wBitsPerSample := 32;
          nAvgBytesPerSec := nChannels * nSamplesPerSec * wBitsPerSample div 8;
          nBlockAlign := nChannels * wBitsPerSample div 8;
          cbSize := 0;
        end;
    
        GetMem(buf, fmt.nChannels * length(Sound.Channels[0]) * sizeof(TASWaveformSample));
        if length(Sound.Channels) = 1 then
          CopyMemory(buf, @(Sound.Channels[0, 0]), length(Sound.Channels[0]) * sizeof(TASWaveformSample))
        else
          for i := 0 to high(Sound.Channels[0]) do
            for n := 0 to high(Sound.Channels) do
              CopyMemory(buf + sizeof(TASWaveformSample) * (i * fmt.nChannels + n), @(Sound.Channels[n, i]), sizeof(TASWaveformSample));
    
        if waveOutOpen(@hWave, WAVE_MAPPER, @fmt, 0, 0, CALLBACK_NULL) <> MMSYSERR_NOERROR then
          raise Exception.Create('SoundPlayerThread.Execute: waveOutOpen failed: ' + SysErrorMessage(GetLastError));
    
        ZeroMemory(@hdr, sizeof(hdr));
        with hdr do
        begin
          lpData := buf;
          dwBufferLength := fmt.nChannels * length(Sound.Channels[0]) * sizeof(TASWaveformSample);
          dwFlags := 0;
        end;
    
        try
    
          SoundPlayerActive := true;
    
          waveOutPrepareHeader(hWave, @hdr, sizeof(hdr));
          waveOutWrite(hWave, @hdr, sizeof(hdr));
          sleep(500);
    
          while waveOutUnprepareHeader(hWave, @hdr, sizeof(hdr)) = WAVERR_STILLPLAYING do
            if PlaySoundStopper^ then
            begin
              waveOutPause(hWave);
              waveOutUnprepareHeader(hWave, @hdr, sizeof(hdr));
              break;
            end
            else
              sleep(100);
    
        finally
          SoundPlayerActive := false;
          waveOutClose(hWave);
          FreeMem(buf);
        end;
    
      except
        on E: Exception do MessageBox(0, PChar(E.ClassName + ': ' + E.Message), 'Sound Playback Error', MB_ICONERROR);
      end;
    end;
    

    where

    type
      TASWaveformSample = integer; // signed 32-bit; -2147483648..2147483647
      TASWaveformSamples = packed array of TASWaveformSample; // one channel
      PASSound = ^TASSound;
      TASSound = record
        Channels: packed array of TASWaveformSamples;
        SampleRate: cardinal;
      end;
    

    A perhaps better way, is to use a thread for the playing. Then I do

    var
      OwnerForm: HWND; // = 0;
      SndSource: PASSound; // = nil;
      ThreadPlaying: boolean; // = false;
    
    type
      TSoundPlayerThread = class(TThread)
      private
        { Private declarations }
      protected
        procedure Execute; override;
      end;
    

    implemented as

    procedure TSoundPlayerThread.Execute;
    var
      hWave: HWAVEOUT;
      hdr: TWaveHdr;
      buf: PAnsiChar;
      fmt: TWaveFormatEx;
      i: Integer;
      n: Integer;
    begin
    
      ThreadPlaying := true;
      try
    
       try
    
          if not Assigned(SndSource) then
            Exit;
    
          with fmt do
          begin
            wFormatTag := WAVE_FORMAT_PCM;
            nChannels := length(SndSource^.Channels);
            nSamplesPerSec := SndSource^.SampleRate;
            wBitsPerSample := 32;
            nAvgBytesPerSec := nChannels * nSamplesPerSec * wBitsPerSample div 8;
            nBlockAlign := nChannels * wBitsPerSample div 8;
            cbSize := 0;
          end;
    
          GetMem(buf, fmt.nChannels * length(SndSource^.Channels[0]) * sizeof(TASWaveformSample));
          if length(SndSource^.Channels) = 1 then
            CopyMemory(buf, @(SndSource^.Channels[0, 0]), length(SndSource^.Channels[0]) * sizeof(TASWaveformSample))
          else
            for i := 0 to high(SndSource^.Channels[0]) do
              for n := 0 to high(SndSource^.Channels) do
                CopyMemory(buf + sizeof(TASWaveformSample) * (i * fmt.nChannels + n), @(SndSource^.Channels[n, i]), sizeof(TASWaveformSample));
    
          if waveOutOpen(@hWave, WAVE_MAPPER, @fmt, 0, 0, CALLBACK_NULL) <> MMSYSERR_NOERROR then
            raise Exception.Create('SoundPlayerThread.Execute: waveOutOpen failed: ' + SysErrorMessage(GetLastError));
    
          ZeroMemory(@hdr, sizeof(hdr));
          with hdr do
          begin
            lpData := buf;
            dwBufferLength := fmt.nChannels * length(SndSource^.Channels[0]) * sizeof(TASWaveformSample);
            dwFlags := 0;
          end;
    
          waveOutPrepareHeader(hWave, @hdr, sizeof(hdr));
          waveOutWrite(hWave, @hdr, sizeof(hdr));
          sleep(500);
    
          while waveOutUnprepareHeader(hWave, @hdr, sizeof(hdr)) = WAVERR_STILLPLAYING do
          begin
            sleep(100);
            if Terminated then
              waveOutReset(hWave);
          end;
    
          waveOutClose(hWave);
          FreeMem(buf);
    
        except
          on E: Exception do MessageBox(0, PChar(E.ClassName + ': ' + E.Message), 'TSoundPlayerThread', MB_ICONERROR);
        end;
    
      finally
        ThreadPlaying := false;
      end;
    end;
    

提交回复
热议问题