开发者

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

Is there any sim开发者_开发知识库ple function? I am searching something like that

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


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;


Wave Audio Package has TLiveAudioPlayer component. It plays audio from buffer.


The Win32 API PlaySound function can play standard RIFF-encoded audio (such as WAV audio) from a memory block by using its SND_MEMORY flag. Alternatively, if the audio is in the app's resources, you can use the SND_RESOURCE flag instead.


Microsoft has a Knowledge Base article telling you how you can play sound from memory using MCI. You'll probably need to have the wave file header in your array, or otherwise copy in the right data during the first read, but other than that it should be fairly easy to port over.


I couldn't find a complete solution that isn't based on the outdated sndPlaySound, so here are two functions that play ".wav" files from both a TMemoryStream and from a file :

uses mmsystem;

procedure PlaySoundFromFile(FileName : String);
var
  mStream : TMemoryStream;
begin
  mStream := TMemoryStream.Create;
  Try mStream.LoadFromFile(FileName); Except End;
  If mStream.Size > 0 then PlaySoundFromStream(mStream);
  mStream.Free;
end;

procedure PlaySoundFromStream(mStream : TMemoryStream);
begin
  PlaySound(mStream.Memory,0,SND_MEMORY or SND_SYNC);
end;

The sound is played synchronously and from memory, you can find the other PlaySound flags in the link on Remy's answer. If you switch to async playback, make sure to not clear the sound memory before playback ends.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜