SIM7600X Raspberry Pi Raspbian Voice Call

From Waveshare Wiki
Jump to: navigation, search

Raspberry Pi 4B Voice Call

To reduce the size of the SIM7600G-H 4G Module, it is not designed with audio input/output interfaces. However, with the Audio port of the USB interface, the SIM7600 chip can input and output the audio data in a binary way when using the voice call function. As the demo adopts the audio interface of the Raspberry Pi and the input function of the USB audio card, it can realize real-time voice calls with data transmission via the USB port.

Hardware Preparation

PI5-audio-7600.png

  • Raspberry Pi development board
  • SIM7600G-H 4G Module
  • USB Type-C data cable
  • A SIM card that can normally use for the call service
  • External sound card device with microphone (USB sound card is used in this example)

Hardware Connection

  • Before powering on, insert the SIM card into the SIM7600G-H 4G Module card slot.
  • Connect the SIM7600G-H 4G Module with the Raspberry Pi development board with the USB data cable.
  • After waiting for the module booting, connect to the call service, please refer to "NET Indicator Working Status Description".
  • Connect the audio card to the Raspberry Pi.

Software Preparation

Enter the running commands to confirm the SIM7600G-H driving status:

ls /dev/ttyUSB*

If it is the newest Raspberry Pi system, it comes with the module driver. You can see there are five ports, from ttyUSB0 to ttyUSB4, and do not need external installation.
Install python function library:

#pyaudio is the module of python, install pyaudio on the Raspberry Pi. Firstly, install portaudio.dev
sudo apt-get install portaudio.dev
#and then use
sudo apt-get install python-pyaudio
#or
sudo apt-get install python3-pyaudio   #python3

Download SIM7600H-G_4G_Module_PhoneCall_code and unzip it.
According to the selected sound card check the sound card driver status. (the USB sound card used in this example is the driver-free one.)
Query the selected audio device number, and you can realize with pyaudio module:

sudo python Audio_check.py

This demo will list all the devices that you can use and the corresponding number. You can record the corresponding number after finding the device according to the name.

Test Process

As the Raspberry Pi system or the audio device is different, you can change some parameters in this demo before running it:

stream_in=p.open(format=p.get_format_from_width(2),channels=1,rate=8000,input=True,input_device_index=1,stream_callback=pcm_out)
#This demo is for initializing the audio input object, "input_device_index=1" means the audio input device number, and may be different among different devices.

If it is wrong with the audio input, you can check whether the device runs normally. If the device runs abnormally, you can choose the following loop-test demo:

sudo python Audio_test_R

The demo will play the audio data received by the audio input device on the audio output device, if there is no sound, you can try to refer to the list of audio devices queried in the previous section and modify the "input_device_index" and "output_device_index" parameters in the spy audio initialization object statement.

stream1=p.open(format=p.get_format_from_width(2), channels=1, rate=8000, input=True,input_device_index=1,stream_callback=pcm_in,frames_per_buffer=800)    #Parameter: input_device_index
stream2=p.open(format=p.get_format_from_width(2), channels=1, rate=8000, output=True,output_device_index=0,stream_callback=pcm_out,frames_per_buffer=800)    #Parameter: output_device_index

Raspberry Pi 5 Voice Call

Pi5-audio.png

import time
import serial
import pyaudio
import numpy as np
import sys

# ====== Serial Port Configuration ======
AT_PORT = "/dev/ttyUSB2"  # Adjust according to Raspberry Pi 5's serial port
AUDIO_PORT = "/dev/ttyUSB4"  # Adjust according to Raspberry Pi 5's serial port

# ====== Audio Parameters ======
SIM_RATE = 8000      # Phone quality
CHUNK_8K = 320       # 40ms per frame
MIC_RATE = 44100     # Default microphone sample rate

class AudioDeviceFinder:
    """Audio device auto-finder"""
    
    def __init__(self):
        self.pa = pyaudio.PyAudio()
        self.devices = []
        self.scan_devices()
    
    def scan_devices(self):
        """Scan all audio devices"""
        print("=" * 60)
        print("Scanning audio devices...")
        print("=" * 60)
        
        for i in range(self.pa.get_device_count()):
            info = self.pa.get_device_info_by_index(i)
            device = {
                'index': i,
                'name': info['name'],
                'input_channels': int(info['maxInputChannels']),
                'output_channels': int(info['maxOutputChannels']),
                'default_sample_rate': int(info['defaultSampleRate'])
            }
            self.devices.append(device)
            
            # Display device information
            input_str = f"Input:{device['input_channels']}" if device['input_channels'] > 0 else "Input:0"
            output_str = f"Output:{device['output_channels']}" if device['output_channels'] > 0 else "Output:0"
            print(f"{i:2d}: {device['name'][:40]:<40} {input_str:<10} {output_str:<10}")
    
    def find_usb_audio_devices(self):
        """Find USB Audio and HID devices"""
        usb_mics = []
        usb_speakers = []
        
        for device in self.devices:
            name = device['name'].lower()
            
            # Find USB audio devices
            if 'usb audio' in name or 'usb' in name:
                if device['input_channels'] > 0:
                    usb_mics.append(device)
                if device['output_channels'] > 0:
                    usb_speakers.append(device)
        
        return usb_mics, usb_speakers
    
    def auto_select_devices(self):
        """Automatically select devices"""
        usb_mics, usb_speakers = self.find_usb_audio_devices()
        
        # Select microphone
        if usb_mics:
            for mic in usb_mics:
                if 'microphone' in mic['name'].lower():
                    selected_mic = mic
                    break
            else:
                selected_mic = usb_mics[0]
        else:
            default_mic_idx = self.pa.get_default_input_device_info()['index']
            selected_mic = self.devices[default_mic_idx]
            print(f"Warning: No USB microphone found, using default device: {selected_mic['name']}")
        
        # Select speaker
        if usb_speakers:
            for spk in usb_speakers:
                if 'speaker' in spk['name'].lower():
                    selected_spk = spk
                    break
            else:
                selected_spk = usb_speakers[0]
        else:
            default_spk_idx = self.pa.get_default_output_device_info()['index']
            selected_spk = self.devices[default_spk_idx]
            print(f"Warning: No USB speaker found, using default device: {selected_spk['name']}")
        
        return selected_mic, selected_spk
    
    def terminate(self):
        """Clean up resources"""
        self.pa.terminate()

def at_cmd(ser, cmd, wait=0.2):
    """Send AT command"""
    ser.write((cmd + "\r\n").encode())
    time.sleep(wait)
    n = ser.in_waiting
    return ser.read(n) if n else b""


def resample_44k1_to_8k(pcm):
    """Resample 44.1kHz to 8kHz"""
    if not pcm:
        return b""
    
    x = np.frombuffer(pcm, dtype=np.int16)
    if x.size == 0:
        return b""
    
    out_len = int(x.size * SIM_RATE / MIC_RATE)
    if out_len <= 0:
        return b""
    
    idx = (np.linspace(0, x.size - 1, out_len)).astype(np.int32)
    return x[idx].astype(np.int16).tobytes()

def main():
    """Main program"""
    print("=" * 60)
    print("USB Audio Phone System")
    print("=" * 60)
    
    finder = AudioDeviceFinder()
    usb_mics, usb_speakers = finder.find_usb_audio_devices()
    
    if usb_mics:
        print(f"\nFound {len(usb_mics)} USB microphones: ")
        for mic in usb_mics:
            print(f"  [{mic['index']}] {mic['name']}")
    else:
        print("\nNo USB microphones found")
    
    if usb_speakers:
        print(f"\nFound {len(usb_speakers)} USB speakers: ")
        for spk in usb_speakers:
            print(f"  [{spk['index']}] {spk['name']}")
    else:
        print("\nNo USB speakers found")
    
    # Automatically select devices
    mic_device, spk_device = finder.auto_select_devices()
    
    print(f"\nSelected devices:")
    print(f"  Microphone: [{mic_device['index']}] {mic_device['name']}")
    print(f"  Speaker: [{spk_device['index']}] {spk_device['name']}")
    
    print(f"\nInitializing serial ports...")
    try:
        at_ser = serial.Serial(AT_PORT, 115200, timeout=0)
        au_ser = serial.Serial(AUDIO_PORT, 115200, timeout=0)
        print(f"AT serial port: {AT_PORT}, Audio serial port: {AUDIO_PORT}")
    except Exception as e:
        print(f"Serial initialization failed: {e}")
        finder.terminate()
        return
    
    print("Sending AT command to check device...")
    start_time = time.time()
    while time.time() - start_time < 5:
        if b"OK" in at_cmd(at_ser, "AT", 0.1):
            print("AT OK")
            break
        time.sleep(0.2)
    else:
        print("No response from AT command")
        at_ser.close()
        au_ser.close()
        finder.terminate()
        return
    
    print("\nWaiting for incoming call...")
    ring_detected = False
    while not ring_detected:
        if at_ser.in_waiting:
            data = at_ser.read(at_ser.in_waiting)
            if b"RING" in data:
                print("Incoming call detected!")
                ring_detected = True
                break
            print(f"AT response: {data}")
        time.sleep(0.1)
    
    print("Answering the call...")
    at_cmd(at_ser, "ATA", 0.5)
    print("Call answered")
    time.sleep(1.0)
    print("Configuring voice mode...")
    at_cmd(at_ser, "AT+CPCMREG=1", 0.1)
    print("\nInitializing audio stream...")
    
    try:
        spk_stream = finder.pa.open(
            format=pyaudio.paInt16,
            channels=1,
            rate=SIM_RATE,
            output=True,
            output_device_index=spk_device['index'],
            frames_per_buffer=CHUNK_8K
        )
        print(f"Speaker opened: {spk_device['name']}")
    except Exception as e:
        print(f"Failed to open speaker: {e}")
        at_ser.close()
        au_ser.close()
        finder.terminate()
        return
    
    mic_frames = int(mic_device['default_sample_rate'] * 0.02)
    
    def mic_callback(in_data, frame_count, time_info, status):
        try:
            pcm_data = in_data
            if mic_device['input_channels'] > 1:
                arr = np.frombuffer(pcm_data, dtype=np.int16)
                arr = arr.reshape(-1, mic_device['input_channels'])
                arr = arr.mean(axis=1).astype(np.int16)
                pcm_data = arr.tobytes()
            
            if mic_device['default_sample_rate'] != SIM_RATE:
                pcm_data = resample_44k1_to_8k(pcm_data)
            
            if pcm_data:
                au_ser.write(pcm_data)
        except Exception as e:
            print(f"Microphone callback error: {e}")
        
        return (None, pyaudio.paContinue)
    
    try:
        mic_stream = finder.pa.open(
            format=pyaudio.paInt16,
            channels=mic_device['input_channels'],
            rate=mic_device['default_sample_rate'],
            input=True,
            input_device_index=mic_device['index'],
            frames_per_buffer=mic_frames,
            stream_callback=mic_callback
        )
        print(f"Microphone opened: {mic_device['name']}")
    except Exception as e:
        print(f"Failed to open microphone: {e}")
        spk_stream.close()
        at_ser.close()
        au_ser.close()
        finder.terminate()
        return
    
    mic_stream.start_stream()
    print("\nCall in progress...")
    
    try:
        while True:
            if au_ser.in_waiting:
                audio_data = au_ser.read(au_ser.in_waiting)
                if audio_data:
                    spk_stream.write(audio_data)
            time.sleep(0.01)
    except KeyboardInterrupt:
        print("\nUser requested hang up...")
    
    finally:
        print("Cleaning up resources...")
        at_cmd(at_ser, "AT+CHUP", 0.2)
        print("Call hung up")
        try:
            mic_stream.stop_stream()
            mic_stream.close()
            print("Microphone closed")
        except:
            pass
        
        try:
            spk_stream.stop_stream()
            spk_stream.close()
            print("Speaker closed")
        except:
            pass
        
        at_ser.close()
        au_ser.close()
        print("Serial ports closed")
        finder.terminate()
        print("Audio resources released")
        
        print("\nProgram finished")
 
if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        print(f"Program exception: {e}")
        import traceback
        traceback.print_exc()
        input("\nPress Enter to exit...")