Announcement

Collapse
No announcement yet.

How to record a telephone call into a wave file ?

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • How to record a telephone call into a wave file ?

    Hi all

    This is part of a power basic TAM project.
    Recording audio via microphone and soundcard into a wave file works great.
    Making phone calls with my voice modem and headset (connected to soundcard) works either.

    But i´m not able to record incoming audio from my modem.
    i use a Motorola SM56 Speakerphone (PCI). Control panel tells me, there is a
    wave driver installed for this modem. audio transfer between modem and soundcard
    works (otherwise my headset couldn´t work). The modem manual states DirectX
    version 6 must be installed for audio transfer to work. On the other hand
    WaveInGetNumDevs() returns "2" (default audio input device = soundcard [deviceid=0],
    second input device = "modem nr. 0" [deviceid=1]), meaning this modem is a
    wave input device.

    If i query the modem device, i get dwformats=0 [wave_invalidformat] and
    wchannels=1 in waveincaps structure in return, meaning it doesn´t support
    wave format (?) but it offers input for one channel. (???)

    So what, if it is listed as a wave input device, why doesn´t it support
    wave format? .

    How does DirectX audio capture work ? maybe this could be a way to do it.


    Any ideas about this, i´m stuck with it since several days ...


    Thanks in advance,

    Guenther



    below some testing code i use (put together from code sample i found):


    #COMPILE EXE
    #DIM ALL

    %USEMACROS = 1
    #INCLUDE "Win32API.inc"

    $ComPort = "COM3"
    $AppTitle = "PB/Win Comm Example"

    %IDD_MAIN = 100
    %IDC_LISTBOX1 = 101
    %IDC_EDIT1 = 102
    %IDC_SEND = 103
    %IDC_SENDFILE = 104
    %IDC_RECEIVEFILE = 105
    %IDC_QUIT = 106
    %IDC_ECHO = 107



    GLOBAL nComm AS LONG ' file number of comm device
    GLOBAL nWriteFile AS LONG ' file number of output file
    GLOBAL fUpdating AS LONG ' flag to avoid conflicts when updating
    GLOBAL fCloseThread AS LONG ' flag to tell thread to shut down

    GLOBAL hwi AS LONG

    '***********************************************************************************************


    CALLBACK FUNCTION check_callback
    '***********************************************************************************************
    '
    '***********************************************************************************************
    'FUNCTION OpenWaveIn(BYVAL hWnd AS LONG) AS LONG
    LOCAL rc AS LONG
    LOCAL NumDevs AS DWORD
    LOCAL i AS LONG
    LOCAL ErrStr AS ASCIIZ*512
    LOCAL wfx AS WAVEFORMATEX
    LOCAL wic AS WAVEINCAPS

    rc = WaveInGetNumDevs()

    FOR i = 0 TO rc -1
    ' FOR i = 1 TO rc -1
    rc = WaveInGetDevCaps(i, wic, LEN(WAVEINCAPS))
    IF (rc = %MMSYSERR_NOERROR) THEN

    MSGBOX wic.szpname+" --- "+HEX$(wic.dwformats)+" --- "+STR$(wic.wchannels)

    ' IF wic.dwFormats AND %WAVE_FORMAT_1M16 THEN
    wfx.nChannels = 1
    wfx.nSamplesPerSec = 11025
    wfx.wFormatTag = %WAVE_FORMAT_PCM
    wfx.wBitsPerSample = 16
    wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample \ 8
    wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign
    wfx.cbSize = 0
    ' rc = waveinOpen(hwi, i, wfx, hWnd, hWnd, %CALLBACK_WINDOW)
    IF (rc = %MMSYSERR_NOERROR) THEN
    FUNCTION = %True
    ' EXIT FUNCTION
    ELSE
    WaveInGetErrorText rc, ErrStr, 511
    MSGBOX ErrStr
    END IF
    ' ELSE
    MSGBOX "Device " & wic.szpName & " - WAVE_FORMAT_1M16 Not Supported"
    ' END IF
    ELSE
    WaveInGetErrorText rc, ErrStr, 127
    MSGBOX ErrStr
    END IF
    NEXT i

    MSGBOX "No suitable device found"

    FUNCTION = %False

    END FUNCTION


    '***********************************************************************************************
    SUB AddLine (BYVAL hWnd AS DWORD, BYVAL nID AS LONG, SendText AS ASCIIZ)
    '***********************************************************************************************
    '
    '***********************************************************************************************
    LOCAL ListCount AS LONG

    ' find the current listbox count
    CONTROL SEND hWnd, nID, %LB_GETCOUNT, 0, 0 TO ListCount

    ' update the listbox
    CONTROL SEND hWnd, nID, %LB_ADDSTRING, 0, VARPTR(SendText)

    ' scroll the new item into view
    CONTROL SEND hWnd, nID, %LB_SETCURSEL, ListCount, 0

    END SUB


    '***********************************************************************************************


    FUNCTION StartComms () AS LONG
    '***********************************************************************************************
    '
    '***********************************************************************************************
    LOCAL dummy AS STRING

    COMM OPEN $COMPORT AS #nComm
    IF ERRCLEAR THEN EXIT FUNCTION ' Exit if port cannot be opened

    COMM SET #nComm, BAUD = 9600 ' 9600 baud
    COMM SET #nComm, BYTE = 8 ' 8 bits
    COMM SET #nComm, PARITY = %False ' No parity
    COMM SET #nComm, STOP = 0 ' 1 stop bit
    COMM SET #nComm, TXBUFFER = 4096 ' 4k transmit buffer
    COMM SET #nComm, RXBUFFER = 4096 ' 4k receive buffer

    ' Issue a CR/LF and flush the receive buffer
    COMM PRINT #nComm, $NUL
    SLEEP 2000
    IF COMM(#nComm, RXQUE) THEN COMM RECV #nComm, COMM(#nComm, RXQUE), dummy

    FUNCTION = %TRUE

    END FUNCTION


    '***********************************************************************************************


    SUB EndComms ()
    '***********************************************************************************************
    '
    '***********************************************************************************************
    LOCAL dummy AS STRING

    ' Flush the RX buffer & close the port
    SLEEP 1000

    IF COMM(#nComm, RXQUE) THEN
    COMM RECV #nComm, COMM(#nComm, RXQUE), dummy
    END IF

    COMM CLOSE #nComm

    END SUB


    '***********************************************************************************************


    FUNCTION ReceiveData (BYVAL hWnd AS LONG) AS LONG
    '***********************************************************************************************
    '
    '***********************************************************************************************
    LOCAL sBigBuffer AS STRING
    LOCAL sBuffer AS STRING
    LOCAL ncBytesInBuffer AS LONG


    WHILE ISFALSE fCloseThread

    ' Test the RX buffer
    ncBytesInBuffer = COMM(#nComm, RXQUE)

    ' Abort this iteration if sending
    IF ISFALSE ncBytesInBuffer OR fUpdating THEN
    SLEEP 100
    ITERATE LOOP
    END IF

    ' Read incoming characters.
    COMM RECV #nComm, ncBytesInBuffer, sBuffer
    sBigBuffer = sBigBuffer & sBuffer

    ' If Receive mode is on, write raw data to the file
    IF nWriteFile THEN PRINT #nWriteFile, sBuffer;

    ' Strip out LF characters
    REPLACE $LF WITH "" IN sBigBuffer

    ' Process only complete lines of text terminated by carriage returns
    WHILE INSTR(sBigBuffer, $CR)
    ' Display the data
    AddLine hWnd, %IDC_LISTBOX1, "==> " + EXTRACT$(sBigBuffer, $CR)

    ' Remove the "displayed" line from the buffer
    sBigBuffer = STRDELETE$(sBigBuffer, 1, LEN(EXTRACT$(sBigBuffer, $CR)) + 1)
    WEND
    WEND

    FUNCTION = %TRUE


    END FUNCTION


    '***********************************************************************************************


    CALLBACK FUNCTION Dialog_Callback () AS LONG
    '***********************************************************************************************
    '
    '***********************************************************************************************


    IF CBMSG = %WM_INITDIALOG THEN
    ' Set focus to the edit control
    CONTROL SET FOCUS CBHNDL, %IDC_EDIT1

    ' Set the SELECTION range to highlight the initial entry
    CONTROL SEND CBHNDL, %IDC_EDIT1, %EM_SETSEL, 0, -1

    ' Return 0 to stop the dialog box engine setting focus
    FUNCTION = %FALSE
    END IF


    END FUNCTION


    '***********************************************************************************************


    CALLBACK FUNCTION Send_Callback () AS LONG
    '***********************************************************************************************
    '
    '***********************************************************************************************
    LOCAL SendText AS ASCIIZ * 1024
    LOCAL Result AS LONG
    LOCAL hListBox AS DWORD

    ' Obtain the text to send from the edit control
    CONTROL GET TEXT CBHNDL, %IDC_EDIT1 TO SendText

    ' Set the update flag
    fUpdating = %TRUE

    ' Send the line to the comm port
    COMM PRINT #nComm, SendText

    ' Check the Echo mode state
    CONTROL SEND CBHNDL, %IDC_ECHO, %BM_GETCHECK, 0, 0 TO Result
    IF Result <> %BST_CHECKED THEN
    ' Add the echo to the listbox
    AddLine CBHNDL, %IDC_LISTBOX1, "<== " + SendText
    END IF

    ' Set the SELECTION range for the edit control so the next keypress "clears" the control
    CONTROL SEND CBHNDL, %IDC_EDIT1, %EM_SETSEL, 0, -1

    ' Restore the keyboard focus to the edit control
    CONTROL SET FOCUS CBHNDL, %IDC_EDIT1

    ' Release the update flag
    fUpdating = %FALSE

    FUNCTION = %TRUE

    END FUNCTION


    '***********************************************************************************************


    CALLBACK FUNCTION SendFile_Callback() AS LONG
    '***********************************************************************************************
    '
    '***********************************************************************************************
    LOCAL SendFileName AS STRING
    LOCAL nReadFile AS LONG
    LOCAL FileLen AS LONG
    LOCAL Chunk AS LONG
    LOCAL i AS LONG
    LOCAL sBuffer AS STRING


    SendFileName = INPUTBOX$("Name of disk file to transmit? ", $AppTitle, "")
    IF ISFALSE LEN(SendFileName) OR ISFALSE LEN(DIR$(SendFileName)) THEN
    EXIT FUNCTION
    END IF

    AddLine CBHNDL, %IDC_LISTBOX1, "Wait... Sending " & SendFileName
    DIALOG DOEVENTS

    ' send the file
    nReadFile = FREEFILE
    OPEN SendFileName FOR BINARY AS #nReadFile ' Open as binary
    FileLen = LOF(nReadFile) ' File length
    Chunk = COMM(#nComm, TXBUFFER) \ 2 ' 1/2 * TXBUFFER

    FOR i = 1 TO FileLen \ Chunk
    GET$ #nReadFile, Chunk, sBuffer ' Read a chunk
    COMM SEND #nComm, sBuffer ' and send it
    SLEEP 0
    NEXT i

    IF FileLen MOD Chunk <> 0 THEN ' More to send?
    GET$ #nReadFile, FileLen MOD Chunk, sBuffer
    COMM SEND #nComm, sBuffer
    END IF

    CLOSE #nReadFile

    AddLine CBHNDL, %IDC_LISTBOX1, "Transmission complete!"

    END FUNCTION


    '***********************************************************************************************

    CALLBACK FUNCTION ReceiveFile_Callback () AS LONG
    '***********************************************************************************************
    '
    '***********************************************************************************************
    LOCAL sReceiveFileName AS STRING
    LOCAL sBuffer AS STRING

    ' First check if file is already open
    IF nWriteFile THEN
    CLOSE #nWriteFile 'close the file
    AddLine CBHNDL, %IDC_LISTBOX1, "Finished writing file!"

    ' Update the button label
    sBuffer = "&Receive File"
    CONTROL SEND CBHNDL, %IDC_RECEIVEFILE, %WM_SETTEXT, 0, STRPTR(sBuffer)

    nWriteFile = 0
    EXIT FUNCTION
    END IF

    ' Create a new file
    sReceiveFileName = INPUTBOX$("Output file name?", $AppTitle, "")
    IF ISFALSE LEN(sReceiveFileName) THEN EXIT FUNCTION

    nWriteFile = FREEFILE

    OPEN sReceiveFileName FOR APPEND AS #nWriteFile
    IF ERRCLEAR THEN
    ' Error opening the file
    nWriteFile = 0
    ELSE
    ' Update the dialog
    AddLine CBHNDL, %IDC_LISTBOX1, "Receiving data stream to " & sReceiveFileName
    sBuffer = "Stop &Receive"
    CONTROL SEND CBHNDL, %IDC_RECEIVEFILE, %WM_SETTEXT, 0, STRPTR(sBuffer)
    END IF

    END FUNCTION


    '***********************************************************************************************


    CALLBACK FUNCTION Quit_Callback () AS LONG
    '***********************************************************************************************
    '
    '***********************************************************************************************

    ' Kill the dialog and let PBMAIN() continue
    DIALOG END CBHNDL, 0

    END FUNCTION


    '***********************************************************************************************


    FUNCTION PBMAIN () AS LONG
    '***********************************************************************************************
    '
    '***********************************************************************************************
    #REGISTER NONE

    LOCAL hDlg AS DWORD
    LOCAL Result AS LONG
    LOCAL hThread AS LONG
    DIM Txt(1 TO 1) AS STRING


    ' Initialize the port ready for the session
    IF ISFALSE StartComms THEN
    MSGBOX "Failed to start communications!",, $AppTitle
    EXIT FUNCTION
    END IF

    Txt(1) = "This listbox shows the transmission I/O stream..."

    ' Create a modal dialog box
    DIALOG NEW 0, $AppTitle,,, 330, 203, %WS_POPUP OR %WS_VISIBLE OR %WS_CLIPCHILDREN OR %WS_CAPTION OR %WS_SYSMENU OR %WS_MINIMIZEBOX, 0 TO hDlg

    ' Add our application controls
    CONTROL ADD LABEL, hDlg, -1, "Transmission &log for " & $ComPort, _
    9, 5, 100, 10, 0

    CONTROL ADD LISTBOX, hDlg, %IDC_LISTBOX1, Txt(), 9, 15, 313, 133, _
    %WS_BORDER OR %LBS_WANTKEYBOARDINPUT _
    OR %LBS_DISABLENOSCROLL OR %WS_VSCROLL OR %WS_GROUP _
    OR %WS_TABSTOP OR %LBS_NOINTEGRALHEIGHT

    CONTROL ADD LABEL, hDlg, -1, "Te&xt to send", 9, 151, 100, 10, 0

    CONTROL ADD TEXTBOX, hDlg, %IDC_EDIT1, "ATZ", 9, 161, 257, 12, _
    %ES_AUTOHSCROLL OR %ES_NOHIDESEL OR %WS_BORDER _
    OR %WS_GROUP OR %WS_TABSTOP

    CONTROL ADD BUTTON, hDlg, %IDC_SEND, "Send &Text", 273, 160, 50, 14, _
    %WS_GROUP OR %WS_TABSTOP OR %BS_DEFPUSHBUTTON _
    CALL Send_Callback

    CONTROL ADD BUTTON, hDlg, %IDC_SENDFILE, "&Send File", 9, 182, 50, 14, _
    %WS_GROUP OR %WS_TABSTOP CALL SendFile_Callback

    CONTROL ADD BUTTON, hDlg, %IDC_RECEIVEFILE, "&Receive File", _
    62, 182, 50, 14, %WS_GROUP OR %WS_TABSTOP _
    CALL ReceiveFile_Callback

    CONTROL ADD BUTTON, hDlg, %IDC_QUIT, "&Quit", 273, 182, 50, 14, _
    %WS_GROUP OR %WS_TABSTOP CALL Quit_Callback

    CONTROL ADD BUTTON, hDlg, 500, "check", 150, 182, 50, 14, _
    %WS_GROUP OR %WS_TABSTOP CALL check_Callback



    CONTROL ADD CHECKBOX, hDlg, %IDC_ECHO, "Disable Local &Echo", _
    241, 5, 80, 10, _
    %WS_GROUP OR %WS_TABSTOP OR %BS_AUTOCHECKBOX _
    OR %BS_LEFTTEXT OR %BS_RIGHT

    ' Erase our array to free memory no longer required
    REDIM Txt()

    ' Create a "listen" thread to monitor input from the modem
    THREAD CREATE ReceiveData(hDlg) TO hThread

    ' Start the dialog box & run until DIALOG END executed.
    DIALOG SHOW MODAL hDlg, CALL Dialog_Callback TO Result

    ' close down our "listen" thread
    fCloseThread = %TRUE
    DO
    THREAD CLOSE hThread TO Result
    SLEEP 0
    LOOP UNTIL ISTRUE Result

    ' Flush & close the comm port and close the Receive file if open
    EndComms

    IF nWriteFile THEN CLOSE #nWriteFile


    END FUNCTION

    '***********************************************************************************************
    '***********************************************************************************************

  • #2
    Looks like typical Serial Port code (could use code tags though), but I see nothing about audio from a modem?

    code tags are typically
    [ c o d e ]

    [ / c o d e ]


    and take out the spaces for code and end-code

    Engineer's Motto: If it aint broke take it apart and fix it

    "If at 1st you don't succeed... call it version 1.0"

    "Half of Programming is coding"....."The other 90% is DEBUGGING"

    "Document my code????" .... "WHYYY??? do you think they call it CODE? "

    Comment


    • #3
      Sorry for the code thing, will try to make it better next time !

      audio cap test code is in check_callback. As mentioned before
      capturing audio from mircophone and getting it into a wave file
      is not the problem.

      The problem is, audio input from modem does not work after
      establishing a call with AT commands. I can hear audio in my
      headset, which tells me, there is a way to transfer audio from
      modem to the soundcard. But i cannot get this audio data recorded
      like i can, if audio input is a mircophone (deviceid=1 on my machine).

      Has anyone experience recording audio from a modem (like a TAM does) ?

      Any hint what to try, where to find information would be great

      Thanks


      Guenther

      Comment


      • #4
        Vac

        Virtual Audio Cables might be worth a try.

        Comment


        • #5
          I used to do this with a little device I got at Radio Shack. It went inline with an actual phone line and output the audio which I could record through the aux input line. May not be what you want since it sounds like you want to talk using your PC soundcard too and it worked through an actual phone, but it will record the conversation on any device (tape, CD, PC, micro cassette/digital recorder, etc).
          sigpic
          Mobile Solutions
          Sys Analyst and Development

          Comment


          • #6
            Great tip William, with virtual audio cable i got it working finally.
            Many thanks !

            Roger, i thought about that too, but you are right this is not what i want
            thanks anyway.

            I don´t know if this is the right place to ask such questions, because it
            might be not possible to do this with power basic. Nevertheless i ask here
            hoping someone can help.

            The solution William suggested works, but it seems rather complicated to
            me. The path of audio info, i want to record, goes like this:

            modem hardware AD-PCM-Wave converter
            -> modem driver (-> modem audio driver)
            -> sm56hlpr.exe -> directsound -> soundcard

            virtual audio cables must be configured as default audio output, then
            it goes like this:

            modem hardware AD-PCM-Wave converter
            -> modem driver (-> modem audio driver)
            -> sm56hlpr.exe -> directsound
            -> virtual audio cable ->
            -> my app

            sm56hlpr.exe is a helper utility transferring audio from the
            modem (audio) driver to directsound default audio output device.

            So it should be possible to have it this way:

            modem hardware AD-PCM-Wave converter
            -> modem driver (-> modem audio driver)
            -> my app

            sm56hlpr.exe comunicates with smserial.sys (modem driver),
            gets the audio info and routes it to directsound. So all i would have to
            do is communicate with this driver myself, but this communication
            cannot be established as expected. In control panel this modem (driver)
            is listed as audio input wave device. But i cannot open it as wave device.
            What am i doing wrong? Any suggestions?


            Thanks,

            Guenther

            Comment


            • #7
              To end this topic, here is what i have done finally.

              After some research with sm56hlpr.exe, i found out
              the audio stream is not routed through sm56hlpr.exe.
              sm56hlpr.exe communicates with the modem driver via
              deviceiocontrol. If necessary the driver requests for
              a handle (or pointer or whatever) from sm56hlpr.exe.
              Sm56hlpr.exe initialises DirectX audio and passes this
              handle to the driver which in return uses it, to send
              audio data via KS interface.

              No way to intercept audio data in sm56hlpr.exe

              So i do the following:

              set vitual audio cable as default audio output device,
              by setting this registry entry

              HKEY_CURRENT_USER\Software\Microsoft\Multimedia\Sound Mapper\playback

              to "Virtual Cable 1", then start sm56hlpr.exe, send "ath1"
              (initialze DirectX with the default audio output = virtual cable 1)
              send "atz", restore the previous registry value.

              Now i can connect to virtual audio cable using wave functions.
              look for a sample program like "soundcapture" in forums to see
              how this could be done.

              Other running or later on started audio programs are not affected
              and will work as expected.

              Virtual audio cable was the tip, that really made it possible.


              Thanks to all, special thanks to William


              Guenther

              Comment

              Working...
              X