-
[Lab - Data Over Sound] 모스부호로 데이터 전송하기데이터 통신 2023. 4. 5. 21:41
모스부호 인코딩 파이썬 코드
https://github.com/CNU-DataNetwork/3week_morse-OneEpitome
모스부호 디코딩 with Android Studio
https://github.com/OneEpitome/DataComm_Lab_AndroidMorseCode
이 실습의 핵심 요소는 다음과 같습니다.
- 입력받은 문자열을 모스부호로 인코딩하기
- 인코딩 된 모스부호를 아날로그 신호 (소리) 로 변환
- 모스부호로 인코딩 된 아날로그 신호를 다시 텍스트로 디코딩
def start(self, text ='', test=False, filename = FILE_NAME): if test is False: while True: print('Type Any Text (only English)') text = input('User input: ').strip() if re.match(r'[A-Za-z0-9 ]+', text): break # 텍스트 -> 모스부호 문자열로 만들기 morse = self.text2morse(text) self.init_morse = morse print(f'MorseCode: {morse}') #모스부호 문자열 -> 오디오 파일로 만들기 audio = self.morse2audio(morse, filename) print(f'AudioSize: {len(audio)}') print('Play Audio ...') self.play_audio(audio) print("Done")
위 코드를 살펴보면, Alphabet & Numeric 으로 이루어진 문자열을 입력받아 text2morse 를 수행합니다. 그리고 변환된 morse 를 가지고 morse2audio 를 수행합니다. 그리고 audio 데이터를 이용하여 play_audio 를 수행합니다.
입력받은 문자열을 모스부호로 인코딩하기
text2morse 를 살펴보겠습니다.
def text2morse(self, text): text = text.upper() morse = '' for t in text: if t == ' ': morse = morse + '/' for key, value in morsecode.code.items(): if t == key: morse += value morse = morse + ' ' return morse[:-1] #morsecode.py code = {'A':'.-' , 'B':'-...' , 'C':'-.-.' , 'D':'-..' , 'E':'.' , 'F':'..-.' , 'G':'--.' , 'H':'....' , 'I':'..' , 'J':'.---' , 'K':'-.-' , 'L':'.-..' , 'M':'--' , 'N':'-.' , 'O':'---' , 'P':'.--.' , 'Q':'--.-' , 'R':'.-.' , 'S':'...' , 'T':'-' , 'U':'..-' , 'V':'...-' , 'W':'.--' , 'X':'-..-_', 'Y':'-.--' , 'Z':'--..' , '0':'-----', '1':'.----', '2':'..---', '3':'...--', '4':'....-', '5':'.....', '6':'-....', '7':'--...', '8':'---..', '9':'----.', '0':'-----'}
위 코드를 살펴보면, 텍스트를 전부 Uppercase 로 변환 후 한글자씩 인코딩 후 morse 에 저장합니다.
입력된 character 가 ' '(공백) 일 경우 '/' 라는 기호로 encoding 하고 그 외의 경우에는 morsecode.py 의 code dictionary 에서 미리 encoding 된 모스부호를 가져옵니다.
인코딩 후 각 글자 사이에 무조건 ' '을 집어넣음으로써 모스부호로 변환된 결과를 글자 단위로 끊어 읽을 수 있습니다.
인코딩 된 모스부호를 아날로그 신호 (소리) 로 변환하기
morse2audio 를 살펴보겠습니다.
def morse2audio(self, morse, filename=False): audio = [] for m in morse: if m == '.': for i in range(math.ceil(SAMPLERATE*UNIT)*1): #1초에 sample rate 만큼 샘플 만들고 이것을 unit 초 동안 녹음 audio.append(int(SHORTMAX*math.sin(2 * math.pi * FREQUENCY * (i/ SAMPLERATE)))) elif m == '-': for i in range(math.ceil(SAMPLERATE*UNIT)*3): audio.append(int(SHORTMAX*math.sin(2* math.pi * FREQUENCY * (i/SAMPLERATE)))) # (i / samplerate) 는 샘플 개수를 sin 함수 진행 시간에 mapping 시킴. elif m == ' ': for i in range(math.ceil(SAMPLERATE*UNIT)*1): audio.append(int(0)) elif m == '/': for i in range(math.ceil(SAMPLERATE*UNIT)*1): audio.append(int(0)) for i in range(math.ceil(SAMPLERATE*UNIT)*1): audio.append(int(0)) if filename: self.audio = audio self.audio2file(audio, filename) return audio
위 코드를 살펴보면, 인코딩 된 모스부호를 하나하나 꺼내서 audio 라는 list 에 int 형 자료를 집어넣고 있습니다.
아날로그 소리 데이터를 디지털 데이터로 표현하려면 어떻게 했었는지 기억나시나요? 표본화 - 양자화 - 부호화 과정을 거쳐 디지털 데이터로 변환 후 저장가능했습니다. 즉 audio list 에 int 를 저장하는 것은 부호화된 소리의 디지털 정보를 저장하는 것이라 볼 수 있습니다. 다시 한 번 정리하면 소리는 하나의 int 로 저장됩니다.
이 코드에서는 '.' 에 해당하는 모스부호를 ShortMax*sin(2 * pi * FREQUENCY * time) 에 해당하는 사인파형의 소리를 0.1 초로 변환하고, '-' 에 해당하는 모스부호를 ShortMax*sin(2 * pi * FREQUENCY * time) 에 해당하는 사인파형의 소리 0.3초로 변환합니다. 즉, 0.1 초 동안의 샘플 데이터 (SAMPLERATE * 0.1) 개의 데이터를 저장하거나 0.3 초 동안의 샘플데이터(data frame) (SAMPLETRATE * 0.3) 개의 데이터를 audio list 에 저장합니다.
그 외의 공백이나 '/' 으로 인코딩 된 모스부호는 0 의 데이터를 0.1초에 해당하는 샘플 데이터를 audio 에 저장합니다.
text2morse 에서 ' ' 을 글자 사이에 넣어줬던 것처럼, 각 모스부호를 하나하나 구분지을 수 있도록 0.1 초 길이에 해당하는 0 을 가진 샘플을 audio 에 저장합니다.
time 이 i / SAMPLERATE 으로 표현되는 이유는 샘플의 개수로 시간 (second) 를 표현하기 위함입니다.
다시 한 번 정리하면, '.', '-', ' ', '/' 을 같은 주파수를 갖는 소리를 사용하되, 이 소리의 길이(지속시간)를 사용하여 모스부호를 표현하고 있습니다.
모스부호로 인코딩 된 아날로그 신호를 다시 텍스트로 디코딩
record_audio 는 입력으로 들어오는 소리를 모스부호로 해석합니다.
def record_audio(): unit_size = math.ceil(SAMPLERATE*UNIT) p = pyaudio.PyAudio() stream = p.open(format=pyaudio.paInt16, channels=CHANNELS, rate=SAMPLERATE, frames_per_buffer=SAMPLERATE, input=True) tuning = True abs_sum = 0 count = 0 unseen = 0 morse = '' recording = True while recording: data = stream.read(unit_size, exception_on_overflow = False) for i in range(0, len(data), 2): d = struct.unpack('<h', data[i:i+2])[0] if tuning: if abs(d) > MORSE_THRESHOLD: tuning = False if DEBUG: print('Tuning end') else: abs_sum = abs_sum + abs(d) count = count + 1 if count == unit_size: if (abs_sum / count) >= MORSE_THRESHOLD: morse = morse + '.' unseen = 0 else: morse = morse + ' ' unseen = unseen + 1 count = 0 abs_sum = 0 if DEBUG: print(f'Current Morse: {morse}') if unseen >= (UNSEEN_THRESHOLD/UNIT): recording = False break print(f'RawMorse: {morse}') stream.stop_stream() stream.close() p.terminate() morse = morse.strip() morse = morse.replace('...', '-') morse = morse.replace(' ', 'm') morse = morse.replace(' ', 's') morse = morse.replace(' ', '') morse = morse.replace('s', ' ') morse = morse.replace('m', ' / ') morse = morse return morse
개괄적인 동작원리는 이렇습니다.
- 녹음 시작 -> Tuning 여부 파악 -> 소리가 발생하면 Tuning End
- 0.1 초에 해당하는 샘플 데이터 (data frame) 를 읽어 들여서 소리가 나고 있다고 판단되면 (MORSE_THRESHOLD 보다 평균적인 샘플링 데이터의 값이 크면 '.' 으로 인식, 소리가 나고 있지 않다고 판단되면 ' ' 으로 인식
- 일정 시간 (UNSEENTHRES_HOLD/UNIT -> 이 코드에서는 3초) 동안 소리가 나지 않으면 녹음 종료
- 녹음 종료 후, 입력받은 모스 부호를 적절하게 변환 -> 가공된 모스부호 반환
- 가공된 모스부호를 다시 morsecode.py 에 있던 code dictionary 와의 비교를 통해 원본 문자열을 얻을 수 있습니다.
먼저 0.1 초 동안 소리가 나느냐 안나느냐로 '.' 을 입력받아 raw morse data 를 만들고 그 이후에 가공하는 방식으로 동작합니다.
이 실습의 특징은 입력받은 문자열을 모스부호로 바꾸고 모스부호 하나하나 (디지털 데이터) 를 Line Coding 을 통해 디지털 신호로 변환하여 아날로그 신호 (소리) 를 통해 데이터를 주고 받는 의미가 있습니다.
'데이터 통신' 카테고리의 다른 글
[Data Communication] IP 주소를 왜 쓸까? (0) 2023.04.18 [Data Communication] Switching, 회선 교환과 패킷 교환 (0) 2023.04.15 [Data Communication] 다중화 (Multiplexing) (0) 2023.04.04 [Data Communication] 디지털 데이터 -> 디지털 신호, 디지털 데이터의 전송 방식 (0) 2023.03.27 [Data Communication] 디지털 데이터 -> 아날로그 신호로의 변환 (0) 2023.03.20