Author Topic: Opus Encoding Decoding in Delphi  (Read 224 times)

GioQveladze

  • Posts: 24
Opus Encoding Decoding in Delphi
« on: 26 Dec '20 - 06:41 »
Hello
I am trying to use Opus in Bass
more precisely:
i am trying to get sound from the microphone I'm doing it:
Code: [Select]
//Client
var
  Form3: TForm3;
  BLoaded: boolean;
  chanPlay, ChanMic: Dword;
  EncodeChan: HENCODE;
  avail, freq: word;
  playchan:HSTREAM;
  buffer: TIDBytes;
  BASSProcs: BASS_FILEPROCS;
  isrecording : Boolean;
  writestream:TmemoryStream;
................
procedure TForm3.FormCreate(Sender: TObject);
begin
  UDP_Rec := TIdUDPClient.Create;
  with UDP_Rec do begin
    BufferSize:=3000;
    Port := 5000;
    BroadcastEnabled:=true;
    Active:=true;
  end;
  freq:=44100; //Частота дискретизации
  setlength(buffer,UDP_Rec.BufferSize);

  BLoaded:=BASS_Init(-1, 44100, 0, 0, nil);
  if BLoaded then
  begin
    BASS_RecordInit(-1);
  end;
  BASS_SetConfig(BASS_CONFIG_NET_PLAYLIST, 1); // enable playlist processing
  BASS_SetConfig(BASS_CONFIG_NET_PREBUF, 0); // minimize automatic pre-buffering, so we can do it (and display it) instead
end;

procedure TForm3.Button1Click(Sender: TObject);
begin
  isrecording := True;
  writeStream:=TmemoryStream.create;
  chanMic:=BASS_RecordStart(freq, 1, 0, nil, nil);

  VoiceThread:=TVoiceThread.Create(True);
  VoiceThread.UDP_Rec := UDP_Rec;
  VoiceThread.Start;
end;

procedure TVoiceThread.Execute;
begin
  inherited;
  while isrecording do begin
        try
            avail:=BASS_ChannelGetData(chanMic, nil, BASS_DATA_AVAILABLE);
            if avail>=UDP_Rec.BufferSize then begin //Если в буффере собралось не меньше 1600 байт, отправляем
                try
                    BASS_ChannelGetData(chanMic, buffer, UDP_Rec.BufferSize);
//                    UDP_Rec.Broadcast(Buffer, 5000);
                    UDP_Rec.SendBuffer('127.0.0.1', 5000, buffer);
                except
                end;
            end;
        finally
            sleep(20);
        end;
    end;
    BASS_ChannelStop(chanmic);
    BASS_StreamFree(chanmic);
end;

//Server
uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Radio.Bass, IdBaseComponent, IdComponent, IdUDPBase, IdUDPClient, FMX.Controls.Presentation, FMX.StdCtrls,
  IdUDPServer, IdGlobal, IdSocketHandle;

type
  TForm4 = class(TForm)
    Button1: TButton;
    UDP_Rec: TIdUDPServer;
    IdUDPClient1: TIdUDPClient;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    chanPlay: HSTREAM;
    procedure UDP_RecUDPRead(AThread: TIdUDPListenerThread; const AData: TIdBytes;  ABinding: TIdSocketHandle);
  public
    { Public declarations }
  end;

var
  Form4: TForm4;

implementation

{$R *.fmx}

procedure TForm4.FormCreate(Sender: TObject);
begin
  BASS_Init(-1, 44100, 0, 0, nil);
end;

procedure TForm4.Button1Click(Sender: TObject);
begin
  chanPlay:= BASS_StreamCreate(44100, 1, 0, STREAMPROC_PUSH, nil);
//  IdUDPClient1.
  UDP_Rec.OnUDPRead:=UDP_RecUDPRead;
  BASS_ChannelPlay(chanPlay, false);
end;

procedure TForm4.UDP_RecUDPRead(AThread: TIdUDPListenerThread; const AData:
    TIdBytes; ABinding: TIdSocketHandle);
begin
  try
      BASS_StreamPutData(chanPlay, AData,length(AData))
  except
  end;
end;

everything works well here
how can I use Opus here? can anyone help

i tried to do the following:
added in code:
Code: [Select]
procedure EncodeCallback(handle:HENCODE; channel:DWORD; buffer:Pointer; length:DWORD; user:Pointer) stdcall;
begin

end;

Function Bass_Read(buffer: Pointer; length: DWORD; user: Pointer): DWORD; stdcall;
begin

End;

Procedure Bass_Close(user: Pointer); stdcall;
Begin

End;

Function Bass_Length(user: Pointer): QWORD; stdcall;
Begin

End;

function Bass_Seek(offset: QWORD; user: Pointer): BOOL stdcall;
Begin

End;
...............................
  EncodeChan:=BASS_Encode_OPUS_Start(chanMic, '--bitrate 80 --cvbr', BASS_ENCODE_AUTOFREE or BASS_UNICODE, @EncodeCallback, nil);
  BASS_ChannelPlay(EncodeChan, false);

  BASSProcs.close := Bass_Close;
  BASSProcs.length := Bass_Length;
  BASSProcs.read := Bass_Read;
  BASSProcs.seek := Bass_Seek;

  playchan:=BASS_OPUS_StreamCreateFileUser(STREAMFILE_BUFFERPUSH, BASS_STREAM_AUTOFREE or BASS_UNICODE, BASSProcs, nil);
  BASS_ChannelPlay(playChan, false);
but i can't figure out how to get the data?
if anyone has experience please share
Thank you in advance

Ian @ un4seen

  • Administrator
  • Posts: 23288
Re: Opus Encoding Decoding in Delphi
« Reply #1 on: 28 Dec '20 - 17:52 »
It looks like you want to send the Opus data to a UDP socket, I guess as part of a VOIP app? You can use the BASSenc_OPUS add-on for the Opus encoding (see BASS_Encode_OPUS_Start) but note that it wraps the Opus data in an Ogg container, which might not give the lowest possible latency, ie. might not be optimal for VOIP. You can try using the "--max-delay" option to reduce the latency.

On the client-side, you can minimize additional latency there by setting BASS_CONFIG_NET_PREBUF to 0 (via BASS_SetConfig) and BASS_ATTRIB_BUFFER to 0 (via BASS_ChannelSetAttribute). Please see the documentation for details on both, as well as BASS_Encode_OPUS_Start.

GioQveladze

  • Posts: 24
Re: Opus Encoding Decoding in Delphi
« Reply #2 on: 28 Dec '20 - 19:33 »
Hi Jan, I used to do a direct line through the Opuslib library in Delphi, everything worked fine, but after updating Delphi 10.4.1 release 1 everything started to work incorrectly.
After calling the function opus_encode, the length of the array is returned 3827, it should be smaller, most likely some kind of bug BASS_Encode_OPUS_Start.
question: what could be the problem if I use BASS_Encode_OPUS_Start?

P.S.: this only happens for the iOS version on windows, everything works well

Ian @ un4seen

  • Administrator
  • Posts: 23288
Re: Opus Encoding Decoding in Delphi
« Reply #3 on: 29 Dec '20 - 13:54 »
Try adding "--max-delay 25" to your BASS_Encode_OPUS_Start call options.

If you still see larger blocks of Opus output only on iOS then perhaps the recording channel is being processed in larger blocks/intervals there. How are you processing it, eg. using a RECORDPROC or BASS_ChannelGetData? In either case, you could try logging the data amounts to see if there is a difference.

GioQveladze

  • Posts: 24
Re: Opus Encoding Decoding in Delphi
« Reply #4 on: 30 Dec '20 - 19:45 »
Hi Jan, everything worked fine for me, I used Basslib.a and Opuslib.a on Delphi 10.3.3 everything works perfectly but on Delphi 10.4.1 it works poorly
i used BASS_ChannelGetData now I want to use RECORDPROC and so far I can not figure out how to decode if you can help, I will be grateful

Ian @ un4seen

  • Administrator
  • Posts: 23288
Re: Opus Encoding Decoding in Delphi
« Reply #5 on: 11 Jan '21 - 13:46 »
Please clarify what you want to decode and where. A RECORDPROC function receives plain PCM data, so there's no need to decode that.

GioQveladze

  • Posts: 24
Re: Opus Encoding Decoding in Delphi
« Reply #6 on: 12 Jan '21 - 20:01 »
i call for encoding function
Code: [Select]
EncodeChan:=BASS_Encode_OPUS_Start(chanMic, '--bitrate 80 --cvbr', BASS_ENCODE_AUTOFREE or BASS_UNICODE, @EncodeCallback, nil);The EncodeCallback procedure returns the values that I pass to the server (the first stream is 841)

I understand the server should have a procedure
Code: [Select]
BASSProcs.close := Bass_Close;
BASSProcs.length := Bass_Length;
BASSProcs.read := Bass_Read;
BASSProcs.seek := Bass_Seek;
playchan:=BASS_OPUS_StreamCreateFileUser(STREAMFILE_BUFFERPUSH, BASS_STREAM_AUTOFREE or BASS_UNICODE, BASSProcs, nil);
BASS_ChannelPlay(playChan, false);

I added procedures but how to decode I cannot understand
Code: [Select]
Function Bass_Read(buffer: Pointer; length: DWORD; user: Pointer): DWORD; stdcall;
begin

End;

Procedure Bass_Close(user: Pointer); stdcall;
Begin

End;

Function Bass_Length(user: Pointer): QWORD; stdcall;
Begin

End;

function Bass_Seek(offset: QWORD; user: Pointer): BOOL stdcall;
Begin

End;

Ian @ un4seen

  • Administrator
  • Posts: 23288
Re: Opus Encoding Decoding in Delphi
« Reply #7 on: 13 Jan '21 - 13:58 »
Your Bass_Read function should read up to "length" bytes of data from the socket - if there is no data available then it should wait for some. As the length is unknown, your Bass_Length function can simply return 0. I would suggest starting with system=STREAMFILE_BUFFER in your BASS_OPUS_StreamCreateFileUser call because it is simpler. You can then switch to STREAMFILE_BUFFERPUSH (and BASS_StreamPutFileData) when you have that working, if you still want to.

If you use a TCP socket (instead of UDP) then you could simply use BASS_OPUS_StreamCreateURL instead of BASS_OPUS_StreamCreateFileUser (and callbacks) to handle the connection.