บทความนี้เป็นการประยุกต์ใช้ DAC และ MicroPython ของไมโครคอนโทรลเลอร์ ESP32 เพื่อเปิดไฟล์ WAV ซึ่งเป็นไฟล์บันทึกเสียง และนำออกไปยัง DAC ที่เชื่อมต่อกับลำโพงดังภาพที่ 1 โดยไฟล์ที่นำมาใช้นั้นเป็นไฟล์แบบเสียงโมโน (mono) แบบ PCM 8 บิตที่ไม่ได้ถูกบีบอัด และโปรแกรมตัวอย่างรองรับการทำ Sampling Rate ที่ประมาณ 50KHz หรือที่ระดับ 44100
โครงสร้างไฟล์ WAV
ไฟล์ Wav ประกอบด้วยส่วนของหัวไฟล์ที่ใช้สำหรับตรวจสอบประเภทและขนาดของไฟล์ และตามด้วย Chunk ของส่วน fmt สำหรับเก็บรายละเอียดของรูปแบบไฟล์ และ data สำหรับเก็บข้อมูลเสียงดังนี้
จำนวนไบต์ | ความหมาย |
---|---|
4 | ‘RIFF’ |
4 | ขนาดไฟล์ |
4 | ‘WAVE’ |
4 | ‘fmt ‘ |
4 | ขนาดของส่วน fmt |
2 | ประเภทของออดิโอ |
2 | จำนวนช่องสัญญาณ |
4 | Sample Rate |
4 | Byte Rate |
2 | Block Align |
2 | จำนวนบิตต่อ Sample |
4 | ‘data’ |
4 | ขนาดของข้อมูล |
n | ข้อมูล |
จากตารางจะได้ว่าในการอ่านข้อมูลจากไฟล์จะเริ่มจากการอ่าน 4 ไบต์แรก เพื่อตรวจสอบว่าเป็น ‘RIFF’ หรือไม่ ถ้าใช่จะดำเนินการอ่านขนาดของไฟล์อีก 4 ไบต์ หลังจากนั้นอ่าน 4 ไบต์ถัดมาเพื่อตรวจสอบว่าเป็น ‘WAVE’ หรือไม่ ถ้าใช่หมายถึงเป็นส่วนหัวของไฟล์ WAV
4 ไบต์ถัดมาเป็นข้อความ ‘fmt ‘ สำหรับเป็นการบอกว่าเป็นส่วนของเก็บรายละเอียดของรูปแบบไฟล์เสียง และส่วนสุดท้ายใน ‘data’ เป็นส่วนเก็บข้อมูลเสียงที่ต้องอ่านขึ้นมาเพื่อนำออกไปให้ภาค DAC
ตัวอย่างโปรแกรม
ตัวอย่างโปรแกรมของบทความนี้เป็นการเล่นไฟล์เสียง mono.wav และ mono2.wav ซึ่งผู้อ่านจะต้องอัพโหลดไฟล์ไปไว้ที่บอร์ดของไมโครคอนโทรลเลอร์ก่อนดังภาพที่ 2
โค้ดตัวอย่างโปรแกรมภาษาไพธอนเป็นดังนี้
import time
import sys
from machine import DAC, Pin, freq
import gc
gc.enable()
gc.collect()
freq(240000000)
dacPin1 = Pin(25) # ต่อกับลำโพง
dacPin2 = Pin(26) # ต่อกับ adcPin1
dac1 = DAC( dacPin1 )
dac2 = DAC( dacPin2 )
def playWavFile( fName ):
monoFile = open(fName,"rb")
mark = monoFile.read(4)
if (mark != b'RIFF'):
print("ไม่ใช้ WAV!")
monoFile.close()
sys.exit(1)
fileSize = int.from_bytes(monoFile.read(4),"little")
print("File size = {} bytes".format(fileSize))
fileType = monoFile.read(4)
if (fileType != b'WAVE'):
print("ไม่ใช้ WAV!!")
monoFile.close()
sys.exit(2)
chunk = monoFile.read(4)
lengthFormat = 0
audioFormat = 0
numChannels = 0
sampleRate = 0
byteRate = 0
blockAlign = 0
if (chunk == b'fmt '):
lengthFormat = int.from_bytes(monoFile.read(4),"little")
audioFormat = int.from_bytes(monoFile.read(2),"little")
numChannels = int.from_bytes(monoFile.read(2),"little")
sampleRate = int.from_bytes(monoFile.read(4),"little")
byteRate = int.from_bytes(monoFile.read(4),"little")
blockAlign = int.from_bytes(monoFile.read(2),"little")
bitsPerSample = int.from_bytes(monoFile.read(2),"little")
print("Length of format data = {}".format(lengthFormat))
print("Audio's format = {}".format(audioFormat))
print("Number of channel(s) = {}".format(numChannels))
print("Sample rate = {}".format(sampleRate))
print("Byte rate = {}".format(byteRate))
print("Block align = {}".format(blockAlign))
print("Bits per sample = {}".format(bitsPerSample))
minValue = 255
maxValue = 0
chunk = monoFile.read(4)
if (chunk != b'data'):
print("ไม่ใช้ WAV!!!!")
monoFile.close()
sys.exit(5)
dataSize = int.from_bytes(monoFile.read(4),"little")
print("Data size = {}".format(dataSize))
if (bitsPerSample > 8):
print("ไม่รองรับข้อมูลที่มากกว่า 8 บืต")
monoFile.close()
sys.exit(6)
buffer = monoFile.read(dataSize)
# find min/max
for i in range(len(buffer)):
if (buffer[i] > maxValue):
maxValue = buffer[i]
if (buffer[i]<minValue):
minValue = buffer[i]
# normalize
xScale = 255.0/(maxValue-minValue)
# play
tm = int(1000000/sampleRate)
for i in range(len(buffer)):
data = int(((buffer[i]-minValue)*xScale))
dac1.write( data )
time.sleep_us(tm)
print("---------------------------")
if (audioFormat != 1):
print("ไม่รองรับกรณีที่ไม่ใช้ PCM!!!")
monoFile.close()
sys.exit(3)
monoFile.close()
dac1.write( 0 )
############### main program
playWavFile("/mono.wav")
time.sleep_ms(1000)
playWavFile("/mono2.wav")
time.sleep_ms(1000)
จากโค้ดจะพบว่าในขั้นตอนของการอ่านข้อมูลเสียงจะทำการค้นหาค่าน้อยสุดและมากสุดเพื่อใช้สำหรับปรับสัดส่วนของข้อมูลให้ข้อมูลค่าน้อยสุดไปอยู่ที่ 0 และค่าสูงสุดเป็น 255 หลังจากนั้นในการนำออกจะนำค่าที่ต้องการส่งออกลบออกด้วยค่าน้อยสุดและคูณกับ xScale เพื่อปรับค่าให้อยู่ในช่วง 0 ถึง 255
นอกจากนี้ได้สร้างตัวแปร tm สำหรับหาค่าหน่วงเวลาแบบคร่าว ๆ โดยนำค่า Sample Rate ไปหา 1000000 เพื่อนำค่าไปหน่วงในระดับไมโครวินาที ทำให้รองรับการส่งสัญญาณเสียงได้ใกล้เคียงความจริงมากขึ้น
เมื่อโปรแกรมทำงานจะรายงานข้อมูลของไฟล์ที่เปิดใช้งานดังตัวอย่างผลลัพธ์ในภาพที่ 3 และทำการอ่านข้อมูลเสียงส่งไปให้ DAC
สรุป
จากบทความนี้จะพบว่าผู้อ่านจะสามารถเปิดไฟล์ WAV และอ่านข้อมูลมาได้อย่างถูกต้อง แต่ไฟล์ที่สามารถนำไปประมวลผลเพื่อนำออกไปยัง DAC จะต้องถูกแปลงให้เป็นแบบ MONO และเป็นข้อมูลแบบ 8 บิตที่ไม่ได้ถูกบีบอัดเท่านั้น ซึ่งทางทีมเราหวังว่าผู้อ่านจะนำไปปรับปรุงต่อไปให้ดีขึ้น และสุดท้ายขอให้สนุกกับการเขียนโปรแกรมครับ
(C) 2020-2022, โดย อ.ดนัย เจษฎาฐิติกุล/อ.จารุต บุศราทิจ
ปรับปรุงเมื่อ 2021-12-02, 2022-01-27