Tag Archives: sound

ALSA : Playback เสียงพร้อมกันด้วย Dmix

ปกติแล้วระบบเสียงบนลินุกซ์ ไม่ว่าจะใช้ ALSA หรือ OSS อนุญาตให้แอพพลิเคชันใช้งานได้เพียงตัวเดียวเท่านั้น เพราะเหตุนี้ หาก XMMS กำลัง playback ผ่าน ALSA แล้ว โปรแกรมอื่นๆ จะใช้ ALSA ไม่ได้ .. ที่จริงแล้วระบบเสียงทุกๆ โอเอสก็เป็นแบบนี้ล่ะครับ เพราะข้อจำกัดจริงๆ อยู่ที่ซาวด์การ์ดซึ่งเกือบทั้งหมดนี้มีชิปสังเคราะห์เสียงเพียงตัวเดียว .. เพื่อแก้ปัญหานี้ บนลินุกซ์จึงมีซอฟต์แวร์ที่เรียกกันว่าซาวด์เซิร์ฟเวอร์ เช่น aRTs ESD NAS หรือ JACK ทำหน้าที่เป็นตัวบริการแทนไดรเวอร์ โดยรับหน้าที่ในการมิกซ์เสียงเพื่อให้แอพพลิเคชันหลายๆ ตัวใช้งานระบบเสียงได้พร้อมๆ กัน .. อย่างไรก็ตาม ข้อเสียของซาวด์เซิร์ฟเวอร์ที่พบในปัจจุบันคือคุณภาพเสียงจะลดลงเมื่อแอพพลิเคชันใช้งานกันหลายตัว โดยเฉพาะการ playback ในอัตราแซมปลิ้งที่ต่างๆ ไปจากความสามารถของซาวด์การ์ด อีกทั้งโปรแกรมต้องเขียนให้สนับสนุนซาวด์เซิร์ฟเวอร์นั้นๆ ด้วย

ที่จริงแล้ว หากใช้ ALSA แล้ว ไม่จำเป็นต้องมีซาวด์เซิร์ฟเวอร์ แอพพลิเคชันก็สามารถสั่ง playback เสียงพร้อมๆ กันได้ โดยใช้ Dmix plugin

ใช้งาน Dmix

Dmix เป็น PCM direct stream mixing plugin ของ ALSA ปลั๊กอินนี้ทำหน้าที่มิกซ์เสียงเพื่อให้แอพพลิเคชันสั่ง playback เสียงได้พร้อมๆ กัน .. Dmix ฝังอยู่ใน libasound มาตั้งแต่ ALSA เวอร์ชัน 0.9 กว่าๆ แล้ว เพียงแต่ไม่ค่อยมีใครรู้และเอามาใช้งานมากนัก ข้อดีของ Dmix คือมันไม่ได้ใช้โมเดลแบบไคลเอนด์/เซิร์ฟเวอร์เหมือนซาวด์เซิร์ฟเวอร์ แต่ใช้วิธีรับข้อมูลแล้วเขียนลงบัฟเฟอร์ของซาวด์การ์ดโดยตรงผ่านทาง DMA ผลคือ Dmix ไม่มีข้อจำกัดเรื่องจำนวนไคลเอนด์ และได้คุณภาพเสียงที่ดี

ในขั้นพื้นฐานที่สุด การใช้งาน Dmix ทำได้โดยระบุดีไวซ์ของ ALSA เป็น Dmix plugin เช่น โปรแกรม aplay ใช้พารามิเตอร์ -D กำหนดดีไวซ์ที่ต้องการใช้ ลองเปิดเทอร์มินัลสองตัว แล้วเรียกใช้ aplay ตามข้างล่างนี้:

$ aplay -Dplug:dmix file.wav

ควรจะได้ยินเสียงจาก file.wav สองเสียงพร้อมกัน

หรือ XMMS ก็สามารถใช้ ALSA plugin แล้วระบุดีไวซ์เป็น plug:dmix ได้เช่นกัน

อย่างไรก็ตาม การใช้งาน Dmix โดยตรงนี้มักจะให้คุณภาพเสียงที่ไม่ดีมากนัก และมักจะมี noise การใช้งานจริงจึงมักจะสร้างดีไวซ์ PCM ขึ้นมาใหม่ โดยสร้างหรือแก้ไขไฟล์ ~/.asoundrc ตามนี้

pcm.intel8x0 {
    type dmix
    ipc_key 1313
    slave {
        pcm "hw:0,0"
        period_time 0
        period_size 1024
        buffer_size 8192
        rate 44100
    }
}

อธิบายคร่าวๆ

~/.asoundrc เป็นคอนฟิกไฟล์ของ libasound นอกเหนือจากคอนฟิกค่าปริยายใน /usr/share/alsa/alsa.conf เราสามารถสร้างดีไวซ์ PCM ของ ALSA ขึ้นมาใหมได้โดยการกำหนดไว้ในไฟล์นี้ จากตัวอย่างนี้ผมสร้างดีไวซ์ PCM ของ ALSA ในชื่อ “intel8x0” ชื่อนี้จะตั้งเป็นอะไรก็ได้ครับ ขอเพียงไม่ซ้ำกับที่กำหนดไว้ก่อนหน้านี้เท่านั้น

  • ผมตั้งเป็น intel8x0 ตามไดรเวอร์ของ i830 AC’97
  • ชนิดของ PCM เป็น dmix หมายความว่า PCM ตัวนี้จะใช้ Dmix plugin เป็นตัว playback ส่วนของ
  • slave มีไว้กำหนดดีไวซ์ที่ intel8x0 ทำงานครอบอยู่ จากตัวอย่างนี้กำหนดเป็น
    • ดีไวซ์ PCM “hw:0,0” อ้างอิง ฮาร์ดแวร์ PCM (บนซาวด์การ์ด) ไอดี 0 พอร์ต 0
    • period_time ขนาดของข้อมูลที่ส่งเข้า/ออกซาวด์การ์ด หน่วยเป็น usec ค่าปริยายจะส่งข้อมูลเข้าซาวด์การ์ดเป็นจังหวะทุก 125 msec (ค่าปริยาย = 125000 usec) กำหนดเป็น 0 เพื่อ override ค่าปริยาย
    • period_size ขนาดของข้อมูลที่ส่งเข้า/ออกซาวด์การ์ด หน่วยเป็นไบต์ ค่าที่กำหนดต้องเป็นสองยกกำลัง n กำหนดมากหรือน้อยไปจะมี noise รบกวน
    • buffer_size กำหนดขนาดของบัฟเฟอร์ กำหนดมากไปอาจจะทำให้มี latency สูง กำหนดน้อยไปก็จะทำให้เสียงขาดช่วง
    • rate อัตราแซมปลิ้ง ที่ 44.1 kHz (ค่าปริยาย = 48 kHz)

ออปชันอาจจะมากหรือน้อยกว่านี้ก็ได้ ค่า period_size กับ buffer_size ไม่จำเป็นต้องเป็นค่า 1024/8192 เสมอไป เพราะขึ้นกับซาวด์การ์ดและชิป PCM ด้วย ต้องลองปรับกันเอาเองครับ

หลังจากได้ดีไวซ์ PCM ตัวใหม่แล้วทีนี้เราก็สามารถอ้างอิงดีไวซ์นี้เพื่อใช้ playback เช่น

$ aplay -Dintel8x0 file.wav

และ หากต้องการกำหนดให้ใช้ดีไวซ์ Dmix โดยปริยาย สามารถทำได้โดยเพิ่มบรรทัดต่อไปนี้ ลงไปใน %HOME/.asoundrc

pcm.!default {
    type plug
    slave.pcm "intel8x0"
}

กรณีนี้แอพพลิเคชันที่ตั้งดีไวซ์ PCM เป็น default ไว้แล้ว เช่น xine และ xmms ก็จะใช้งาน Dmix โดยอัตโนมัติ โปรแกรมอื่นๆ ก็เพียงกำหนดให้ใช้ดีไวซ์ที่เป็น Dmix เท่านั้น เช่น กรณีของ mplayer/gmplayer หากใช้ ALSA จะ playback ออก hw:0,0 โดยตรง เราสามารถกำหนดดีไวซ์ที่ต้องการ playback ได้โดยใช้ออปชัน -ao เช่น

$ mplayer -ao alsa9:intel8x0 file.mpg

หรือ

$ mplayer -ao alsa9:default file.mpg

หากจะตั้งถาวรก็ตั้งในไฟล์ ~/.mplayer/config เช่น

ao=alsa9:intel8x0

หรือในกรณีของ gaim ซึ่งไม่สนับสนุน ALSA โดยตรง ก็สามารถใช้โปรแกรม aplay แทนได้ โดยเลือก

Preferences -> Sounds เลือก Method เป็น Command กรอกในช่อง Sound command เป็น

$ aplay -Dintel8x0 %s

โดยสรุปคือ

ถ้าตั้งดีไวซ์ได้ ก็ให้ตั้งเป็นดีไวซ์ที่มี type เป็น Dmix หรือมี slave เป็น Dmix

ถ้ากำหนดคำสั่งในการ playback ได้ ให้ใช้ aplay โดยกำหนดให้ใช้ดีไวซ์ที่มี type เป็น Dmix หรือมี slave เป็น Dmix

ปัญหาที่เจอเมื่อใช้ Dmix

เวลานี้มีอยู่สองอย่างคือ

  1. xmms อาจจะหยุด playback กลางคัน โปรแกรมไม่แครช กด play อีกครั้งก็จะเริ่ม playback ได้ปกติ .. ตอนนี้ยังไม่มีทางแก้ครับ
  2. mplayer หาก playback ไฟล์ที่อัตราแซมปลิ้งสูงหรือต่ำกว่าที่ตั้งไว้ใน .asoundrc เสียงจะเพี้ยน (xine ไม่มีปัญหาตรงจุดนี้) ทางแก้คือกกำหนดให้ใช้ดีไวซ์ที่เหมาะกับการ playback เช่น กรณีที่ playback ไฟล์ที่มีอัตราแซมปลิ้ง 44.1 kHz อาจใช้ -ao alsa9:default หรือ -ao alsa9:intel8x0 แต่ถ้าไฟล์มีอัตราแซมปลิ้งที่ 48 kHz ให้ใช้ -ao alsa9:hw เป็นต้น

.. ลองใช้งานกันดูนะครับ :)

MIDI SoftSynth บนลินุกซ์ด้วย ALSA VirMIDI + TiMidity

ปัญหานึงของซาวด์การ์ดหลายๆ รุ่นคือไม่มีมิดี้ที่เป็นฮาร์ดแวร์ หรือ FM Synth ก็เลยไม่สามารถเล่นมิดี้บนลินุกซ์ได้ (ถ้าเป็นวินโดวส์ ไดรเวอร์จะจำลองมิดิ้ให้ เป็นซอฟต์ซินธ์ด้วย (SoftSynth/Software Wave Table Emulation) แต่นับว่าโชคดีที่ลินุกซ์ทะเลใช้ระบบเสียงของ ALSA (Advanced Linux Sound Architecture) ก็เลยมีทางที่จะเซ็ตซอฟต์ซินธ์บลินุกซ์ด้วยเหมือนกันโดยใช้ไดรเวอร์ Virtual MIDI และซอฟต์แวร์ชื่อ TiMidity++ … ขั้นตอนมีดังนี้ครับ

เริ่มกันที่ ALSA

คอมไฟล์ alsa-driver โดยระบุให้สร้างไดรเวอร์ virmidi ด้วย ถ้าติดตั้งลินุกซ์ทะเลปกติ จะมีไดรเวอร์นี้อยู่แล้ว แต่สำหรับคนที่ recompile alsa-driver ใหม่ถ้าระบุออพชั่น –with-cards ให้ใส่ virmidi เข้าไปด้วยประมาณนี้

./configure --with-sequencer=yes --with-oss=yes --with-cards=virmidi,intel8x0

จากนั้นก็ make และ make install ตามปกติ หลังจากติดตั้ง alsa-driver แล้ว ทีนี้ก็ลอง load kernel module กัน ..

[kitt@peorth kitt]$ modprobe snd-virmidi index=1

ถ้า lsmod ดูควรจะเห็นอะไรประมาณนี้

snd-virmidi             2144   0 (autoclean)
snd-seq-virmidi         5096   0 (autoclean) [snd-virmidi]
snd-seq-midi-event      5672   0 (autoclean) [snd-seq-virmidi]
snd-seq                47408   0 (autoclean) [snd-seq-virmidi snd-seq-midi-event]
snd-intel8x0           24228   1
snd-pcm                83360   0 [snd-intel8x0]
snd-timer              19688   0 [snd-seq snd-pcm]
snd-ac97-codec         44640   0 [snd-intel8x0]
snd-page-alloc          8552   0 [snd-intel8x0 snd-pcm]
snd-mpu401-uart         5184   0 [snd-intel8x0]
snd-rawmidi            18752   0 [snd-seq-virmidi snd-mpu401-uart]
snd-seq-device          6364   0 [snd-seq snd-rawmidi]
snd                    43332   0 [snd-mixer-oss snd-virmidi snd-seq-virmidi
snd-seq-midi-event snd-seq snd-intel8x0 snd-pcm snd-timer snd-ac97-codec
snd-mpu401-uart snd-rawmidi snd-seq-device]

ลองสั่ง cat /proc/asound/cards

[kitt@peorth kitt]$ cat /proc/asound/cards
0 [82801CAICH3    ]: ICH - Intel 82801CA-ICH3
                     Intel 82801CA-ICH3 at 0x9800, irq 10
1 [VirMIDI        ]: VirMIDI - VirMIDI
                     Virtual MIDI Card 1

จะเห็นว่ามีซาวด์การ์ดสองใบ หนึ่งในนั้นเป็นซาวด์การ์ดจริง (ในตัวอย่างนี้คือ 82801CA ICH3 ของชิพเซ็ต i830) อีกตัวเป็น VirMIDI เป็นการ์ดที่จำลองขึ้นมาโดย module snd-virmidi ..

TiMidity

ที่จริงลินุกซ์ทะเลให้ TiMidity++ มาด้วยนะครับ แต่ว่าไม่ได้คอมไพล์ให้ใช้กับ ALSA ได้ .. ก็ต้องดาวน์โหลดเวอร์ชันที่คอมไพล์ ALSA และ ALSA Sequencer Client ด้วย .. ไม่ต้องไปหาไกล ผมทำให้แล้วล่ะ คิดว่าคงอยู่ใน TLE Update แล้ว จะใช้ Synaptic หรือจะสั่ง apt-get install หรือ apt-get update/upgrade เอาก็ได้ หรือดาวน์โหลด rpm ก็ได้ที่

ftp://ftp.kitty.in.th/pub/rpms/timidity++-2.11.3-4_1kit.i386.rpm

เชื่อม Virtual MIDI กับ TiMidity

Virtual MIDI ไม่ได้เล่นมิดี้ได้ด้วยตัวเองครับ มันแค่จำลองเป็นมิดี้อินเทอร์เฟซเท่านั้น จะทำให้มันเล่นมิดี้ มีเสียงได้ต้องทำให้ Virtual MIDI ส่งข้อมูลมิดี้ไปยัง TiMidity++ ให้ได้เสียก่อน .. อืมม จะอธิบายหลักการตรงนี้ก็เป็นเรื่องยาว .. ขอตัดบทเลยก็แล้วกัน ก่อนอื่น TiMidity++ เป็นโหมด ALSA Sequencer Client โดยระบุออพชัน -iA เข้าไป

[kitt@peorth kitt]$ timidity -iA -Os &
[1] 1106
Requested buffer size 32768, fragment size 8192
ALSA pcm 'default' set buffer size 32768, period size 8192 bytes
TiMidity starting in ALSA server mode
can't set sched_setscheduler - using normal priority
Opening sequencer port: 128:0 128:1

จากนั้นหาหมายเลขพอร์ตของ Virtual MIDI โดยดูจากไฟล์ /proc/asound/clients หรือ /proc/asound/seq/clients

[kitt@peorth kitt]$ cat /proc/asound/seq/clients
Client info
  cur  clients : 6
  peak clients : 6
  max  clients : 192

Client   0 : "System" [Kernel]
  Port   0 : "Timer" (Rwe-)
  Port   1 : "Announce" (R-e-)
Client  72 : "Virtual Raw MIDI 1-0" [Kernel]
  Port   0 : "VirMIDI 1-0" (RWeX)
Client  73 : "Virtual Raw MIDI 1-1" [Kernel]
  Port   0 : "VirMIDI 1-1" (RWeX)
Client  74 : "Virtual Raw MIDI 1-2" [Kernel]
  Port   0 : "VirMIDI 1-2" (RWeX)
Client  75 : "Virtual Raw MIDI 1-3" [Kernel]
  Port   0 : "VirMIDI 1-3" (RWeX)
Client 128 : "Client-128" [User]
  Port   0 : "TiMidity port 0" (-We-)
  Port   1 : "TiMidity port 1" (-We-)
  Output pool :
    Pool size          : 500
    Cells in use       : 0
    Peak cells in use  : 0
    Alloc success      : 0
    Alloc failures     : 0
  Input pool :
    Pool size          : 1000
    Cells in use       : 0
    Peak cells in use  : 0
    Alloc success      : 0
    Alloc failures     : 0

จะเห็นว่ามี Virtual RAW MIDI อยู่ 4 ไคลเอนด์ หมายเลข 72, 73, 74 และ 75 ตามลำดับ ทุกตัวมีพอร์ต 0 … ส่วน TiMidity++ อยู่ที่ 128 มีสองพอร์ตคือ 0 และ 1 .. เมื่อได้หมายเลขพอร์ตแล้วก็สั่งเชื่อมพอร์ตได้แล้ว ที่ต้องทำก็คือเชื่อมพอร์ต 0 ของ Virtual RAW MIDI ตัวแรก เข้าไปที่พอร์ต 0 ของ TiMidity++ โดยคำสั่ง aconnect

[kitt@peorth kitt]$ aconnect 72:0 128:0

ดู /proc/asound/seq/clients อีกครั้ง

[kitt@peorth kitt]$ cat /proc/asound/seq/clients
Client info
  cur  clients : 6
  peak clients : 7
  max  clients : 192

Client   0 : "System" [Kernel]
  Port   0 : "Timer" (Rwe-)
  Port   1 : "Announce" (R-e-)
Client  72 : "Virtual Raw MIDI 1-0" [Kernel]
  Port   0 : "VirMIDI 1-0" (RWeX)
    Connecting To: 128:0
Client  73 : "Virtual Raw MIDI 1-1" [Kernel]
  Port   0 : "VirMIDI 1-1" (RWeX)
Client  74 : "Virtual Raw MIDI 1-2" [Kernel]
  Port   0 : "VirMIDI 1-2" (RWeX)
Client  75 : "Virtual Raw MIDI 1-3" [Kernel]
  Port   0 : "VirMIDI 1-3" (RWeX)
Client 128 : "Client-128" [User]
  Port   0 : "TiMidity port 0" (-We-)
    Connected From: 72:0
  Port   1 : "TiMidity port 1" (-We-)
  Output pool :
    Pool size          : 500
    Cells in use       : 0
    Peak cells in use  : 0
    Alloc success      : 0
    Alloc failures     : 0
  Input pool :
    Pool size          : 1000
    Cells in use       : 0
    Peak cells in use  : 1
    Alloc success      : 1
    Alloc failures     : 0

จะเห็นว่า Client 72 มีข้อความ Connected To: 128:0 และที่ TiMidity++ มีข้อความ Connected From: 72:0

แสดงว่าเชื่อมต่อเป็นที่เรียบร้อยแล้ว ถ้าสั่ง playmidi หรือใช้โปรแกรมอะไรก็ตามที่ใช้งานมิดี้ ข้อมูลของมิดี้จะส่งผ่าน external MIDI ซึ่งจำลองขึ้นมาโดยไดรเวอร์ Virtual MIDI ไปยัง TiMidity++ แล้วเราก็จะได้ยินเสียงมิดี้กันล่ะ :)

คอนฟิกระบบ

ถ้าต้องการคอนฟิกให้ลินุกซ์เรียก Virtual MIDI อัตโนมัติทุกครั้งที่บูตก็สามารถทำได้โดยแก้ /etc/modules.conf ประมาณนี้

alias snd-card-0 snd-intel8x0
alias snd-card-1 snd-virmidi
alias sound-slot-0 snd-card-0
alias sound-slot-1 snd-card-1
alias char-major-116 snd
alias char-major-14 soundcore
options snd major=116 cards_limit=2
options snd-intel8x0 index=0
options snd-virmidi index=1
alias sound-service-0-0 snd-mixer-oss
alias sound-service-0-1 snd-seq-oss
alias sound-service-0-3 snd-pcm-oss
alias sound-service-0-8 snd-seq-oss
alias sound-service-0-12 snd-pcm-oss

ที่ต้องเพิ่มเข้าไปใน /etc/modules.conf ก็คือ บรรทัด

alias snd-card-1 snd-virmidi
alias sound-slot-1 snd-card-1
options snd-virmidi index=1

และต้องเปลี่ยน cards_limit เป็น 2

ทีนี้ทุกครั้งที่บูต snd-virmidi module ก็จะได้รับการติดตั้งอัตโนมัติ ส่วนการเชื่อม Virtual MIDI กับ TiMidity++ ทำเป็นครั้งๆ ไปเมื่อต้องการใช้ดีกว่า เพราะยังต้องรัน timidity -iA -Os ก่อนถึงจะสั่ง aconnect ได้ .. จะให้มันรันตอนบูตเลยดูจะเป็นการเปลืองทรัพยากรระบบเปล่าๆ

อื่นๆ

ยังมีเทคนิคอีกเล็กน้อยสำหรับ TiMidity++ เพื่อให้การทำงานกับมิดี้ตอบสนองได้ดีขึ้นครับ … ปกติ TiMidity ใช้บัฟเฟอร์ขนาดใหญ่เพื่อให้เพลย์ได้ราบรื่น แต่ก็ทำให้มี latency เยอะซึ่งไม่เหมาะกับโปรแกรมที่ต้องการทำงานแบบเรียลไทม์ เช่น โปรแกรมซีเควนเซอร์ อย่าง Rosegarden หรือ Noteedit ทางแก้คือกำหนดบัฟเฟอร์ให้เล็กลงด้วยออพชัน -B ค่าที่เหมาะๆ มีคนแนะมาว่าใช้ -B2,8 ส่วนตัวผมใช้ -B8,8 กำลังพอดี

ถ้า TiMidity++ กินซีพียูเยอะ และต้องการลดมันลง สามารถทำได้โดยยกเลิกเอฟเฟกต์ต่างๆ เช่น reverb และ/หรือ chorus โดยกำหนดออพชัน -EFreverb=0 และ/หรือ -EFchorus=0 ตามลำดับ

ถ้ายังไม่พอใจอีก ก็ต้องลองรัน TiMidity++ ด้วย root หรือตั้ง suid root วิธีนี้ทำให้ TiMidity++ สามารถตั้ง scheduling เป็น FIFO ด้วย priority สูงสุดเท่าที่จะทำได้ (ซึ่งมีเฉพาะ root ที่มีสิทธิตั้งค่าแบบนั้น) วิธีนี้ทำให้การตอบสนองดีขึ้นโดยเฉพาะกับโปรแกรมที่ทำงานแบบเรียลไทม์

.. วันนี้ยาวแฮะ