Getting the length of a Wav file

How do I get the length of a Wav file without using a TMediaPlayer to open the file?


Getting the length is possible using the MCI_SENDSTRING API call, but that does get involved.  However, a better method has been suggested that accesses the file directly and interprets its own internal data to obtain the information.

Here is the function:

function GetWaveLength(WaveFile: string): Double;
var
  groupID: array[0..3] of char;
  riffType: array[0..3] of char;
  BytesPerSec: Integer;
  Stream: TFileStream;
  dataSize: Integer;
  // chunk seeking function,
  // -1 means: chunk not found

function GotoChunk(ID: string): Integer;
var
  chunkID: array[0..3] of char;
  chunkSize: Integer;
begin
  Result := -1;
    
  with Stream do
    begin
      // index of first chunk
      Position := 12;
      repeat
        // read next chunk
        Read(chunkID, 4);
        Read(chunkSize, 4);
        if chunkID <> ID then
         // skip chunk
         Position := Position + chunkSize;
      until (chunkID = ID) or (Position >= Size);
      if chunkID = ID then
        // chunk found,
        // return chunk size
        Result := chunkSize;
    end;
  end;

  begin
    Result := -1;
    Stream := TFileStream.Create(WaveFile, fmOpenRead or fmShareDenyNone);
    with Stream do
      try
        Read(groupID, 4);
        Position := Position + 4; // skip four bytes (file size)
        Read(riffType, 4);
   
        if (groupID = 'RIFF') and (riffType = 'WAVE') then
          begin
            // search for format chunk
            if GotoChunk('fmt') <> -1 then
              begin
                // found it
                Position := Position + 8;
                Read(BytesPerSec,4);
                //search for data chunk
                dataSize := GotoChunk('data');

                if dataSize <> -1 then
                  // found it
                  Result := dataSize / BytesPerSec
              end
          end
      finally
        Free;
      end;
end;

This returns the number of seconds as a floating point number, which is not necessarily the most helpful format.  Far better to return it as a string  representing the time in hours, minutes and seconds.  The following function achieves this based on the number of seconds as an integer:

function SecondsToTimeStr(RemainingSeconds: Integer):String;
var
  Hours, Minutes, Seconds: Integer;
  HourString, MinuteString, SecondString: String;
begin
  // Calculate Minutes
  Seconds := RemainingSeconds mod 60;
  Minutes := RemainingSeconds div 60;
  Hours := Minutes div 60;
  Minutes := Minutes-(Hours*60);

  if Hours < 10  then
    HourString := '0'+IntToStr(Hours)+':'
  else
    HourString := IntToStr(Hours)+':';

  if Minutes < 10 then
    MinuteString := '0'+ IntToStr(Minutes)+':'
  else
    MinuteString := IntToStr(Minutes)+':';

  if Seconds <10 then
    SecondString := '0'+ IntToStr(Seconds)
  else
    SecondString := IntToStr(Seconds);
  Result := HourString+MinuteString+SecondString;
end;

Having created these functions you can call them from any relevant event - for example a button click:

procedure TForm1.Button1Click(Sender: TObject);
var
  Seconds: Integer;
begin
  Seconds := Trunc(GetWaveLength(Edit1.Text));//gets only the Integer part of the length
  Label1.Caption := SecondsToTimeStr(Seconds);
end;

You can even reduce this to a single line of code if you prefer:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Label1.Caption := SecondsToTimeStr(Trunc(GetWaveLength(Edit1.Text)));
end;

Chris Bray.