0% menganggap dokumen ini bermanfaat (0 suara)
77 tayangan24 halaman

Laporan Chat Client-Server + Voice Note

Laporan ini menjelaskan tentang pengembangan aplikasi komunikasi berbasis client-server yang memungkinkan pengiriman pesan teks dan voice note secara real-time. Aplikasi ini menggunakan socket programming dan PyQt5 untuk antarmuka pengguna, serta menerapkan teknik adaptive buffer untuk menjaga integritas data suara. Proyek ini bertujuan untuk menghasilkan executable yang dapat dijalankan tanpa terminal, dengan pengaturan IP server yang dinamis.

Diunggah oleh

Ferdilan Ramadhani
Hak Cipta
© © All Rights Reserved
Kami menangani hak cipta konten dengan serius. Jika Anda merasa konten ini milik Anda, ajukan klaim di sini.
Format Tersedia
Unduh sebagai PDF, TXT atau baca online di Scribd
0% menganggap dokumen ini bermanfaat (0 suara)
77 tayangan24 halaman

Laporan Chat Client-Server + Voice Note

Laporan ini menjelaskan tentang pengembangan aplikasi komunikasi berbasis client-server yang memungkinkan pengiriman pesan teks dan voice note secara real-time. Aplikasi ini menggunakan socket programming dan PyQt5 untuk antarmuka pengguna, serta menerapkan teknik adaptive buffer untuk menjaga integritas data suara. Proyek ini bertujuan untuk menghasilkan executable yang dapat dijalankan tanpa terminal, dengan pengaturan IP server yang dinamis.

Diunggah oleh

Ferdilan Ramadhani
Hak Cipta
© © All Rights Reserved
Kami menangani hak cipta konten dengan serius. Jika Anda merasa konten ini milik Anda, ajukan klaim di sini.
Format Tersedia
Unduh sebagai PDF, TXT atau baca online di Scribd

LAPORAN

Chat Client-Server & Voice Note


Disusun untuk memenuhi salah satu tugas mata kuliah Workshop Komunikasi Multimedia
pada semester Genap 2024/2025

PEMBIMBING:
Ginanjar Suwasono Adi, S.ST., M.Sc.

Oleh :
Ferdilan Ramadhani 2331130063
Moh. Ekhsan Rhomadhoni 2331130020
M. Avryll Satrya Pratama 2331130040
Teknik Telekomunikasi 2C

PROGRAM STUDI TEKNIK TELEKOMUNIKASI


JURUSAN TEKNIK ELEKTRO
POLITEKNIK NEGERI MALANG
MALANG

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.

• PORT: Port server tempat mendengarkan koneksi.


• clients: Daftar semua client yang terhubung
• usernames: Pemetaan socket ke username (jika user login).

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:

• Membaca IP dari config.txt.


• Validasi IP menggunakan inet_aton() (jika tidak valid, raise exception).
• Jika gagal, fallback ke 127.0.0.1.
3. recv_with_length

def recv_with_length(sock):

try:

length_bytes = b""

while len(length_bytes) < 10:

part = sock.recv(10 - len(length_bytes))

if not part:

return None

length_bytes += part

total_length = int(length_bytes.decode())

Menerima 10 byte pertama yang menyatakan panjang pesan (panjang =


total_length).

data = b""

while len(data) < total_length:

packet = sock.recv(min(4096, total_length -


len(data)))

if not packet:

break

data += packet

return length_bytes + data # kirim lengkap ke client


lain

except:

return None

• Terima data utama hingga panjang terpenuhi.


• Kembalikan data (termasuk header panjang).
4. send_with_length
def send_with_length(sock, message):

if isinstance(message, str):

message = message.encode()

length = str(len(message)).zfill(10).encode()

sock.sendall(length + message)

Fungsi untuk mengirim data dengan header panjang 10 byte di depan

5. broadcast

def broadcast(message, sender):


for client in clients:
if client != sender:
try:
client.sendall(message)
except:
client.close()
if client in clients:
clients.remove(client) Saat cnt
mencapai batas frames_to_count, maka hitung FPS sebagai:
▪ Kirim data ke semua client kecuali pengirimnya.
▪ Jika gagal mengirim, hapus client dari daftar.
6. handle_client
def handle_client(client_socket, addr):
print(f"[+] Terhubung dengan {addr}")
Menandai bahwa client sudah terhubung.

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.

print(f"[-] {addr} terputus.")


if client_socket in clients:
clients.remove(client_socket)
if client_socket in usernames:
print(f"[INFO] {usernames[client_socket]} logout.")
del usernames[client_socket]
client_socket.close()
Bersihkan data client setelah putus: hapus dari list dan dict, serta tutup
socket.
7. start
def start():
server = socket.socket(socket.AF_INET,
socket.SOCK_STREAM)
try:
server.bind((HOST, PORT))
server.listen()
print(f"[SERVER] Aktif di {HOST}:{PORT}")
except Exception as e:
print(f"[CRITICAL] Gagal menjalankan server:
{e}")
return

• Inisialisasi socket server.


• Bind ke IP dan port.
• Mulai mendengarkan koneksi client.

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

• Terima koneksi baru, tambahkan ke list.

• Jalankan handle_client() di thread terpisah agar bisa menangani banyak client


sekaligus.

start()

• Memulai Program utama.


7.2.Client
1. Import Modul
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QListWidgetItem, QApplication,
QInputDialog, QLineEdit, QFileDialog
from PyQt5.QtCore import Qt

Import komponen GUI dari PyQt5 (GUI berbasis widget).


QListWidgetItem: digunakan untuk menampilkan setiap pesan.
QInputDialog: popup untuk input username.

from client_gui import Ui_Form


Mengimpor antarmuka GUI yang dibuat dengan Qt Designer dan dikompilasi
menggunakan pyuic5.

import socket, threading, sys


import sounddevice as sd
import wave, time
import base64
import simpleaudio as sa
import io
import datetime
socket: komunikasi TCP.
threading: menerima data tanpa membekukan GUI.
sounddevice: merekam suara.
wave: menyimpan audio dalam format .wav.
base64: encoding audio menjadi teks.
simpleaudio: memutar suara.
io: buffer memory sementara.
datetime: memberi nama file audio berdasar waktu.
2. get_server_ip_from_config
def get_server_ip_from_config(path="config.txt"):
Membaca IP server dari file config.txt.
socket.inet_aton(ip)
Memastikan bahwa IP valid.

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.

while len(length_bytes) < 10:


chunk = self.client.recv(10 - len(length_bytes))
if not chunk:
return None
length_bytes += chunk

Looping hingga length_bytes genap 10 byte.


self.client.recv(n) akan menerima n byte dari socket.
Jika chunk kosong, berarti koneksi tertutup → fungsi mengembalikan None.
Byte-byte ini akan menjadi angka panjang total data yang akan dibaca berikutnya.

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

Inisialisasi buffer kosong data.


Loop terus-menerus membaca data dalam blok maksimal 4096 byte.
Gunakan min(4096, sisa) agar tetap efisien dan tidak berlebih.
Jika packet kosong, berarti koneksi tertutup atau error → keluar dari loop.
data += packet menambahkan setiap bagian data ke buffer utama.

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)

Jika is_sender == True, maka:


Teks pesan diratakan ke kanan, menunjukkan bahwa itu dikirim oleh user.
Warna teks diubah menjadi biru (Qt.blue) agar lebih mudah dibedakan.

else:
item.setTextAlignment(Qt.AlignLeft)
item.setForeground(Qt.darkGreen)

Jika pesan bukan dari user:


Teks diratakan ke kiri, seperti pada aplikasi chat pada umumnya.
Warna teks diubah menjadi hijau tua (Qt.darkGreen), untuk menunjukkan pesan
dari orang lain.

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:

Fungsi ini dijalankan pada thread terpisah (background).


Digunakan untuk terus mendengarkan pesan masuk dari server tanpa menghentikan
GUI utama.
message = self.recv_with_length()
Menggunakan metode recv_with_length() untuk menerima pesan dengan header
panjang 10 digit.
Ini menjamin pesan diterima utuh, baik pesan teks kecil maupun voice note besar.
if message.startswith("VOICE|"):
Mengecek apakah pesan merupakan voice note.
Semua pesan suara diawali dengan format: VOICE|<username>|<data>.
_, rest = message.split("VOICE|", 1)
sender, audio_b64 = rest.split("|", 1)
Memisahkan bagian sender (pengirim voice note) dan audio_b64 (isi audio dalam
format base64).
Variabel audio_b64 akan dikirim ke fungsi pemutar audio.
self.add_message(f"{sender} mengirim voice note...",
is_sender=(sender == self.username))
Menampilkan pesan indikator ke dalam tampilan bahwa seseorang telah mengirim
voice note.
Jika pengirim adalah user itu sendiri, maka is_sender=True → tampil di kanan
layar.
self.play_audio(audio_b64)

Memutar file audio berdasarkan isi audio_b64.


Fungsi play_audio() akan:
Decode base64 ke .wav,
Simpan file,
Putar dengan simpleaudio

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)

Decode dari string base64 → data biner WAV (audio_bytes).

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)

print(f"[INFO] Memutar audio: channel={num_channels},


sampwidth={sampwidth}, rate={framerate}")
sa.play_buffer(audio_data, num_channels, sampwidth,
framerate).wait_done()
▪ sa.play_buffer() adalah dari library simpleaudio.
▪ Fungsi ini langsung memutar data PCM dari buffer.
▪ wait_done() menunggu pemutaran selesai sebelum thread berakhir.

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).

full_msg = f"{self.username}: {msg}"


Menyusun pesan lengkap dengan format standar: "username: isi_pesan".
Format ini penting untuk menampilkan nama pengirim pada client lain.

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.

def callback(indata, frames, time, status):


if self.recording:
self.audio_buffer.append(indata.copy())

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'
)

Membuat objek stream untuk menangkap suara dari microphone.


channels=1: merekam dalam mono.
dtype='int16': format audio 16-bit integer (standar WAV file).
callback: digunakan untuk menangkap data audio saat mengalir masuk.

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_data = b''.join([d.tobytes() for d in self.audio_buffer])


▪ self.audio_buffer adalah daftar blok/blobs numpy array hasil rekaman.
▪ d.tobytes(): Mengubah setiap blok array menjadi byte mentah.
▪ b''.join(...): Menggabungkan semua blok menjadi satu array byte audio.

with io.BytesIO() as wav_io:


with wave.open(wav_io, 'wb') as wf:
wf.setnchannels(1)
wf.setsampwidth(2)
wf.setframerate(self.fs)
wf.writeframes(audio_data)
wav_bytes = wav_io.getvalue()
▪ io.BytesIO(): Membuat objek buffer di memori (bukan file di disk).
▪ wave.open(..., 'wb'): Membuka wav_io sebagai file WAV untuk ditulis.
▪ setnchannels(1): 1 = Mono audio.
▪ setsampwidth(2): 2 byte = 16-bit audio (umumnya digunakan).
▪ setframerate(self.fs): Set sample rate, misal 44100 Hz.
▪ writeframes(audio_data): Tulis data audio mentah ke file WAV.
▪ wav_io.getvalue(): Ambil seluruh isi file WAV sebagai byte (wav_bytes).

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>"

print("[DEBUG] Audio berhasil direkam, panjang data:",


len(audio_data))
print("[DEBUG] Audio base64:", audio_b64[:100], "...")
▪ Cetak panjang data audio mentah.
▪ Cetak sebagian string base64 untuk debug (dipotong 100 karakter) agar
tidak memenuhi terminal.

13. __name__ == "__main__"


if __name__ == "__main__":
Menandakan bahwa file Python ini dieksekusi langsung (bukan diimpor
sebagai modul).
Semua kode di dalam blok ini hanya akan dijalankan jika file ini adalah entry
point aplikasi.

app = QApplication(sys.argv)
Membuat objek aplikasi PyQt.
sys.argv digunakan untuk menerima argumen dari command line (jika ada).

username, ok = QInputDialog.getText(None, "Login", "Masukkan


username Anda:", QLineEdit.Normal)
Menampilkan kotak dialog input untuk meminta nama pengguna (username).
username berisi teks yang dimasukkan.
ok adalah boolean yang bernilai True jika pengguna menekan OK, dan False
jika Cancel.

if not ok or not username.strip():


print("Username tidak valid. Keluar.")
sys.exit()
Jika pengguna menekan Cancel atau memasukkan nama kosong, maka program
akan berhenti.

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.

Anda mungkin juga menyukai