Laporan Chat Client-Server + Voice Note
Laporan Chat Client-Server + Voice Note
PEMBIMBING:
Ginanjar Suwasono Adi, S.ST., M.Sc.
Oleh :
Ferdilan Ramadhani 2331130063
Moh. Ekhsan Rhomadhoni 2331130020
M. Avryll Satrya Pratama 2331130040
Teknik Telekomunikasi 2C
2025
1. Tujuan
• Merancang dan membangun aplikasi komunikasi berbasis client-server dengan
fitur pengiriman pesan teks dan voice note secara real-time.
• Menerapkan teknik adaptive buffer untuk menjamin integritas dan keutuhan
data suara yang dikirim melalui jaringan TCP.
• Mengintegrasikan pengaturan alamat IP server secara dinamis melalui file
konfigurasi eksternal (config.txt) untuk fleksibilitas koneksi jaringan.
• Menghasilkan aplikasi dalam bentuk executable (.exe) agar dapat dijalankan
tanpa terminal atau IDE.
2. Landasan Teori
2.1 Socket Programming
Socket programming adalah teknik pemrograman yang digunakan untuk mengirim
dan menerima data melalui jaringan komputer menggunakan socket. Socket sendiri
merupakan jembatan yang menghubungkan suatu aplikasi berbasis jaringan dengan
lapisan TCP/UDP.
2.2 PyQt5
PyQt5 adalah library Python yang digunakan untuk membangun aplikasi berbasis
Graphical User Interface (GUI), yang merupakan binding dari framework Qt versi
5 yang ditulis dalam bahasa C++. Dengan PyQt5, pengembang dapat membuat
antarmuka pengguna yang interaktif dan modern secara lintas platform, seperti
Windows, Linux, dan macOS. Library ini menyediakan berbagai modul penting
seperti QtWidgets untuk elemen GUI, QtCore untuk manajemen event dan waktu,
serta QtGui untuk pengolahan grafis. Salah satu fitur utama PyQt5 adalah sistem
sinyal dan slot, yaitu mekanisme komunikasi antar objek yang memudahkan
pengelolaan aksi pengguna seperti klik tombol atau perubahan data.
2.3 Audio Processing
Audio processing atau pemrosesan audio adalah teknik pengolahan sinyal suara
untuk memperoleh informasi, memperbaiki kualitas, atau mengubah karakteristik
audio sesuai kebutuhan. Dalam konteks digital, audio processing melibatkan
pengambilan sampel suara analog menjadi sinyal digital melalui proses analog-to-
digital conversion (ADC), lalu dilakukan berbagai manipulasi seperti filtering,
noise reduction, echo cancellation, pitch shifting, atau pengenalan suara. Teknik ini
digunakan secara luas dalam berbagai bidang seperti komunikasi (telepon dan
VoIP), hiburan (musik dan film), kecerdasan buatan (speech recognition), dan
medis (analisis suara untuk diagnosis).
2.4 Adaptive Buffer
Adaptive buffer dalam audio processing adalah mekanisme yang secara dinamis
menyesuaikan ukuran dan waktu penyimpanan data audio berdasarkan kondisi
jaringan yang berubah-ubah, seperti jitter dan delay. Tujuannya adalah untuk
menjaga kelancaran pemutaran suara tanpa gangguan, meskipun terjadi fluktuasi
dalam kecepatan atau kestabilan transmisi data. Metode seperti adaptive playback
buffer (APB) telah terbukti efektif dengan memanfaatkan informasi jaringan secara
real-time untuk menyeimbangkan antara jitter dan delay pemutaran.
3. Alat dan Bahan
• Laptop dengan python 3.x
• Library: pyqt5, socket, threading, sounddevice, wave, base64, simpleaudio, io,
datetime.
• Code Editor
• Terminal
4. Topologi Sistem
Penjelasan Topologi:
• Terdiri dari satu server dan beberapa client.
• Koneksi TCP, full-duplex.
• Voice note dikirim via encoded string dan diterima client lain.
• Client bertindak sebagai sender dan receiver sekaligus.
5. Prosedur Implementasi Sistem
1. Mendesain GUI client menggunakan Qt Designer dan mengonversinya ke Python
dengan pyuic5.
2. Mengembangkan kode server menggunakan Python dengan socket TCP dan thread.
3. Mengembangkan kode client dengan kemampuan:
• Kirim pesan teks
• Rekam dan kirim voice note
• Terima dan putar voice note
4. Mengimplementasikan sistem buffer adaptif dengan panjang data tetap (10-byte
header).
5. Menyusun file konfigurasi config.txt untuk IP dinamis.
6. Menyusun file .exe menggunakan PyInstaller agar program dapat dijalankan tanpa
terminal.
7. Melakukan uji coba sistem pada dua perangkat client dan satu server.
6. Hasil Percobaan
7. Penjelasan Kode Program
7.1.Server
1. Import Modul
import socket
import threading
• socket: Modul untuk komunikasi jaringan (TCP/IP)
• threading: Untuk menjalankan beberapa koneksi client secara paralel (multi-
threading).
PORT = 5555
clients = [] # List of connected client sockets
usernames = {} # mapping socket → username.
2. get_ip_from_config
def get_ip_from_config(path="config.txt"):
try:
with open(path, "r") as f:
ip = f.read().strip()
socket.inet_aton(ip) # validasi IP format
return ip
except Exception as e:
print(f"[ERROR] Gagal membaca config.txt: {e}")
return "127.0.0.1" # fallback ke localhostfps:
def recv_with_length(sock):
try:
length_bytes = b""
if not part:
return None
length_bytes += part
total_length = int(length_bytes.decode())
data = b""
if not packet:
break
data += packet
except:
return None
if isinstance(message, str):
message = message.encode()
length = str(len(message)).zfill(10).encode()
sock.sendall(length + message)
5. broadcast
while True:
try:
message = recv_with_length(client_socket)
if not message:
break
decoded = message.decode()
print(f"[SERVER] Terima data {len(decoded)}
bytes dari {addr}: {decoded[:60]}...")
▪ Terima dan decode pesan dari client.
▪ Tampilkan sebagian isi pesan untuk log.
if decoded.startswith("LOGIN|"):
username = decoded.split("|", 1)[1]
usernames[client_socket] = username
print(f"[LOGIN] {username} dari {addr}")
continue)
Jika pesan diawali dengan "LOGIN|", simpan username.
broadcast(message, client_socket)
Pesan lainnya akan dibroadcast ke client lain.
except Exception as e:
print(f"[ERROR] {addr}:", e)
break
Jika ada error, keluar dari loop dan lanjut ke cleanup.
while True:
try:
client_socket, addr = server.accept()
clients.append(client_socket)
thread =
threading.Thread(target=handle_client,
args=(client_socket, addr))
thread.start()
except KeyboardInterrupt:
break
start()
3. __init__
self.username = username
self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Menyimpan nama pengguna.
Membuat socket TCP.
server_ip = get_server_ip_from_config()
self.client.connect((server_ip, 5555))
Koneksi ke server menggunakan IP dari config dan port 5555.
self.ui.sendButton.clicked.connect(self.send_text)
self.ui.voiceButton.pressed.connect(self.record_audio)
self.ui.voiceButton.released.connect(self.stop_record_and_send)
self.ui.messageInput.returnPressed.connect(self.send_text)
Menghubungkan tombol di GUI dengan fungsinya masing-masing.
4. send_with_length
length = str(len(data)).zfill(10).encode()
Menambahkan panjang pesan (10 digit awal) untuk buffering adaptif.
5. recv_with_length
def recv_with_length(self):
Mendefinisikan fungsi untuk menerima pesan dari server dengan panjang data
diawali 10 byte (buffer adaptif).
Fungsi ini digunakan untuk menangani data besar seperti voice note.
length_bytes = b""
Membuat variabel kosong untuk menyimpan 10 byte awal yang berisi informasi
panjang pesan.
total_length = int(length_bytes.decode())
Mengubah 10 byte panjang (b'0000012345') menjadi integer 12345.
Ini adalah total panjang data aktual yang akan diterima.
data = b""
while len(data) < total_length:
packet = self.client.recv(min(4096, total_length - len(data)))
if not packet:
break
data += packet
return data.decode()
Setelah semua data diterima, ubah dari bytes ke str menggunakan .decode().
except Exception as e:
print("[ERROR] recv_with_length:", e)
return None
Jika terjadi error selama proses, tampilkan pesan kesalahan dan kembalikan None.
6. add_message
def add_message(self, text, is_sender=False):
Fungsi ini digunakan untuk menampilkan pesan teks ke dalam tampilan obrolan
(chatDisplay).
Parameter:
text: isi pesan yang akan ditampilkan.
is_sender: boolean, apakah pesan ini dikirim oleh pengguna itu sendiri (True) atau
oleh orang lain (False). Nilai defaultnya False.
item = QListWidgetItem(text)
Membuat elemen baru (QListWidgetItem) dari teks pesan.
Item ini akan dimasukkan ke dalam list obrolan (chat list).
if is_sender:
item.setTextAlignment(Qt.AlignRight)
item.setForeground(Qt.blue)
else:
item.setTextAlignment(Qt.AlignLeft)
item.setForeground(Qt.darkGreen)
self.ui.chatDisplay.addItem(item)
Menambahkan item pesan yang sudah diatur ke dalam tampilan daftar chat
(chatDisplay), yaitu komponen QListWidget pada GUI.
self.ui.chatDisplay.scrollToBottom()
Setelah menambahkan pesan baru, scroll tampilan akan otomatis turun ke bagian
paling bawah.
Tujuannya agar pengguna langsung melihat pesan terbaru tanpa harus menggulir
manual.
7. start_receive_thread
def start_receive_thread(self):
threading.Thread(target=self.receive_messages,
daemon=True).start()
Menjalankan receive_messages() di thread background agar GUI tidak freeze.
8. receive_messages
def receive_messages(self):
while True:
else:
sender = message.split(":")[0]
is_sender = (sender == self.username)
self.add_message(message, is_sender=is_sender)
Jika pesan bukan voice note, maka diasumsikan sebagai teks biasa dengan format:
"username: pesan".
Pisahkan username dari pesan.
Tampilkan ke UI dengan warna dan posisi yang sesuai.
except:
print("[ERROR] Format VOICE tidak valid.")
break
Jika terjadi error (seperti format tidak sesuai atau koneksi putus), tampilkan pesan
error dan keluar dari loop. Ini mencegah crash saat pesan tidak sesuai dengan format
VOICE.
9. play_audio
def play_audio(self, audio_b64):
▪ Fungsi instance (mungkin bagian dari class UI atau client).
▪ Menerima parameter audio_b64, yaitu string base64 dari file audio WAV.
def _play():
...
threading.Thread(target=_play, daemon=True).start()
▪ Fungsi _play() adalah worker function untuk dijalankan secara asinkron
menggunakan threading.Thread.
▪ daemon=True artinya thread ini akan berhenti otomatis saat program
utama berhenti.
Tujuannya: agar proses pemutaran audio tidak menghambat GUI atau alur
utama.
if len(audio_b64) % 4 != 0:
audio_b64_padded = audio_b64 + '=' * (4 - len(audio_b64) %
4)
else:
audio_b64_padded = audio_b64
▪ Base64 harus punya panjang kelipatan 4.
▪ Jika tidak, tambahkan padding = agar valid saat didekode.
audio_bytes = base64.b64decode(audio_b64_padded)
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"voice_note_{timestamp}.wav"
with open(filename, "wb") as f:
f.write(audio_bytes)
print(f"[SAVED] Voice note disimpan sebagai {filename}")
▪ Buat nama file berdasarkan timestamp agar unik.
▪ Simpan audio_bytes sebagai file .wav untuk backup/debugging.
with io.BytesIO(audio_bytes) as f:
wf = wave.open(f, 'rb')
▪ Gunakan io.BytesIO() untuk membaca file dari memori (tidak perlu baca
dari disk).
▪ wave.open(f, 'rb') membuka audio format WAV untuk dibaca.
num_channels = wf.getnchannels()
sampwidth = wf.getsampwidth()
framerate = wf.getframerate()
audio_data = wf.readframes(wf.getnframes())
▪ num_channels: Mono/stereo (1/2)
▪ sampwidth: Lebar sample (biasanya 2 byte = 16-bit)
▪ framerate: Sampling rate (misal 44100 Hz)
▪ audio_data: Frame audio (data PCM mentah)
except Exception as e:
print("[ERROR] Gagal memutar suara:", e)
Menangkap error apapun selama proses decoding, file handling, atau playback
10. send_text
def send_text(self):
Mendefinisikan fungsi untuk menangani pengiriman pesan teks ketika tombol
diklik atau tombol Enter ditekan.
msg = self.ui.messageInput.text().strip()
Mengambil teks dari input field (messageInput) di GUI.
.strip() digunakan untuk menghapus spasi di awal dan akhir.
Jika pengguna hanya menekan spasi, maka msg akan jadi string kosong.
if msg:
Mengecek apakah msg tidak kosong.
Jika kosong, fungsi tidak lanjut (menghindari pengiriman kosong ke server).
self.send_with_length(full_msg)
Mengirim pesan ke server menggunakan metode send_with_length().
Fungsi ini menambahkan header panjang 10 karakter untuk menjaga
kestabilan transmisi data melalui socket.
self.add_message(full_msg, is_sender=True)
Menampilkan pesan tersebut di UI (chatDisplay) sebagai pesan dari pengguna
itu sendiri.
is_sender=True menyebabkan tampilan sejajar kanan dan berwarna biru (pada
fungsi add_message()).
self.ui.messageInput.clear()
Menghapus isi kotak input setelah pesan berhasil dikirim.
Memberi kenyamanan kepada pengguna untuk langsung mengetik pesan baru.
11. record_audio
def record_audio(self):
Fungsi ini dipanggil ketika pengguna menekan tombol Voice Note di
antarmuka (GUI).
Bertujuan untuk memulai perekaman audio secara real-time.
self.fs = 44100
Menetapkan frekuensi sampling (sampling rate) sebesar 44.100 Hz, standar
kualitas audio CD. Artinya, setiap detik akan direkam 44.100 sample audio.
self.audio_buffer = []
Buffer berupa list kosong untuk menyimpan potongan-potongan data audio
selama proses rekaman berlangsung.
self.recording = True
Menandai bahwa proses perekaman telah dimulai.
Flag ini akan digunakan untuk menghentikan rekaman saat tombol dilepas.
Fungsi callback akan dipanggil setiap kali ada data audio baru yang masuk.
indata: array NumPy berisi sample audio yang direkam.
.copy() diperlukan agar data tidak tertimpa di iterasi selanjutnya.
Selama self.recording == True, data akan terus ditambahkan ke buffer.
self.stream = sd.InputStream(
callback=callback,
channels=1,
samplerate=self.fs,
dtype='int16'
)
self.stream.start()
print("Rekaman dimulai...")
Menjalankan perekaman (memulai stream).
Mencetak log ke terminal sebagai informasi bahwa proses rekaman telah aktif.
12. stop_record_and_send
self.recording = False
self.stream.stop()
print("Rekaman selesai, mengirim...")
▪ self.recording = False: Menandai bahwa proses perekaman berhenti.
▪ self.stream.stop(): Menghentikan stream audio input (misalnya dari
sounddevice).
▪ Cetak log: informasi bahwa rekaman selesai dan proses pengiriman
dimulai.
audio_b64 = base64.b64encode(wav_bytes).decode()
self.send_with_length(f"VOICE|{self.username}|{audio_b64}")
▪ base64.b64encode(...): Konversi file WAV menjadi string base64.
▪ .decode(): Mengubah dari byte ke string agar bisa dikirim via protokol
teks.
▪ send_with_length(...): Fungsi internal untuk mengirim pesan melalui
socket, diawali panjang data.
▪ Format: "VOICE|<username>|<data_base64>"
app = QApplication(sys.argv)
Membuat objek aplikasi PyQt.
sys.argv digunakan untuk menerima argumen dari command line (jika ada).
window = EchoChatClient(username)
window.show()
Membuat instance dari kelas EchoChatClient (yaitu jendela utama aplikasi
chat).
username dikirim sebagai argumen ke konstruktor.
show() digunakan untuk menampilkan GUI ke layar.
sys.exit(app.exec_())
Menjalankan event loop PyQt.
Menunggu dan memproses semua interaksi pengguna, seperti klik tombol,
ketikan, dll.
sys.exit() dipanggil untuk memastikan aplikasi berhenti dengan kode keluar
yang benar setelah ditutup.
app = QtWidgets.QApplication(sys.argv)
window = EchoChatClient()
window.show()
sys.exit(app.exec_())
Ini adalah duplikat dari baris sebelumnya dan tidak akan pernah dijalankan,
karena program sudah keluar di sys.exit() sebelumnya.
Harusnya dihapus untuk menjaga kebersihan kode.
8. Kesimpulan
Praktikum ini berhasil membuktikan bahwa sistem chat client-server berbasis
GUI dengan dukungan pesan teks dan voice note dapat diimplementasikan secara
efektif menggunakan Python, PyQt5, dan socket TCP. Sistem mampu mengelola
pengiriman dan penerimaan data teks maupun audio dengan baik melalui buffer adaptif,
serta menampilkan hasil komunikasi secara real-time dalam antarmuka yang responsif.
Fitur tambahan seperti input username, konfigurasi IP dinamis, dan penyimpanan voice
note menjadikan aplikasi lebih fungsional dan siap digunakan dalam jaringan lokal.