Dalam artikel terjemahan baru, kami membahas cara membuat pager di berbagai platform.
Audio I / O adalah topik rumit yang membuat takut banyak musisi yang merupakan pemrogram dan pemrogram yang menyukai musik. Mari kita coba mencari tahu ini! Pada artikel ini, kami akan membahas cara kerja suara pada setiap OS modern (versi desktop).
Kasus kita hari ini akan dipertimbangkan dengan menggunakan pager sederhana sebagai contoh. Ingat hal yang mengganggu di dalam kotak PC Anda yang mengeluarkan suara mendengung yang tidak menyenangkan? Sekarang itu hanya menjadi kenangan! Saya sarankan membuat perpustakaan yang mereproduksi suara serupa di semua OS.
Hasil akhirnya tersedia di tautan ini .
JENDELA
Kami beruntung dengan Windows: sudah ada fungsi Bip (frekuensi, durasi) di <utilapiset.h> . Kita bisa menggunakannya.
Fitur ini memiliki sejarah yang sangat panjang dan kompleks . Itu diperkenalkan untuk memutar sinyal audio melalui penyuara perangkat keras menggunakan pengatur waktu yang dapat diprogram 8245. Karena semakin banyak komputer yang dirilis tanpa penyuara bip, fitur ini menjadi usang seiring waktu. Namun, di Windows 7 itu ditulis ulang untuk memutar sinyal audio menggunakan API kartu suara.
Namun, kesederhanaan yang tampak dari fitur ini menyembunyikan kompleksitas dari semua API suara Windows. MME dirilis pada tahun 1991 ... Ini digunakan secara default untuk audio karena memiliki dukungan yang baik.
MME dikenal memiliki latensi pemutaran yang tinggi dan mungkin tidak cocok untuk sebagian besar aplikasi audio. Juga pada tahun 2007, WASAPI dirilis . Ini memiliki latensi yang lebih rendah, terutama saat digunakan dalam mode eksklusif (mode di mana pengguna tidak dapat mendengarkan Spotify atau aplikasi lain saat aplikasi Anda berjalan). WASAPI adalah pilihan yang baik untuk aplikasi audio, namun perhatikan DirectSound , yang merupakan pembungkus WASAPI untuk berinteraksi dengan DirectX.
Jika tidak yakin, gunakan WASAPI.
LINUX
Audio adalah salah satu dari sedikit area di mana API Linux sekeren platform lainnya. Pertama-tama, harus dikatakan tentang ALSA, yang merupakan bagian dari inti itu sendiri.
ALSA berinteraksi langsung dengan perangkat keras, dan jika Anda ingin aplikasi Anda bekerja dengan suara secara eksklusif, ALSA dapat menjadi kompromi yang baik antara kompleksitas dan kinerja. Jika Anda membuat synthesizer atau sampler untuk Raspberry Pi, ALSA adalah pilihan yang baik.
Selain itu, ada PulseAudio, lapisan abstraksi audio yang dibangun di atas ALSA. Ini merutekan audio dari berbagai aplikasi dan mencoba untuk menggabungkan aliran audio sehingga aplikasi penting tidak mengalami masalah latensi. Meskipun PulseAudio menyediakan banyak fitur yang tidak dapat dilakukan dengan ALSA (seperti merutekan audio melalui Internet), sebagian besar aplikasi musik tidak menggunakannya.
Banyak orang menggunakan JACK Audio Connection Kit... JACK diciptakan untuk musisi profesional. Ini menangani pemutaran waktu nyata, sedangkan PulseAudio dibuat untuk pengguna biasa yang mungkin mengalami kelambatan saat memutar video YouTube. JACK menghubungkan aplikasi audio dengan latensi minimal, tetapi perlu diingat bahwa itu masih berjalan di atas ALSA, jadi jika aplikasi Anda akan menjadi satu-satunya aplikasi audio yang berjalan (misalnya, jika Anda sedang membangun mesin drum dari Raspberry Pi lama), maka ALSA sangat berguna. lebih mudah digunakan dan kinerja yang lebih baik juga.
Membuat fungsi pager menggunakan ALSA sebenarnya tidak terlalu sulit. Kita perlu membuka perangkat audio default, mengonfigurasinya untuk menggunakan laju sampel dan format sampel yang didukung dengan baik, dan mulai menulis data ke dalamnya. Data audio bisa berupa gelombang gigi gergaji, seperti yang dijelaskan di artikel saya sebelumnya .
int beep(int freq, int ms) {
static void *pcm = NULL;
if (pcm == NULL) {
if (snd_pcm_open(&pcm, "default", 0, 0)) {
return -1;
}
snd_pcm_set_params(pcm, 1, 3, 1, 8000, 1, 20000);
}
unsigned char buf[2400];
long frames;
long phase;
for (int i = 0; i < ms / 50; i++) {
snd_pcm_prepare(pcm);
for (int j = 0; j < sizeof(buf); j++) {
buf[j] = freq > 0 ? (255 * j * freq / 8000) : 0;
}
int r = snd_pcm_writei(pcm, buf, sizeof(buf));
if (r < 0) {
snd_pcm_recover(pcm, r, 0);
}
}
return 0;
}
Di sini kami menggunakan API sinkron dan tidak memeriksa kesalahan untuk menjaga agar fungsinya tetap singkat dan sederhana. Pemblokiran sinkron I / O mungkin bukan pilihan terbaik untuk aplikasi audio yang serius, dan untungnya ALSA hadir dengan metode transfer dan mode operasi yang berbeda: tautan . Tetapi untuk percobaan sederhana kami, ini sudah cukup. Jika ragu, gunakan ALSA. Jika Anda harus berinteraksi dengan aplikasi audio lain, gunakan JACK.
MACOS
Dalam kasus MacOS, semuanya cukup sederhana, tetapi tidak cukup mendasar.
MacOS memiliki kerangka kerja CoreAudio untuk fungsi audio di desktop dan iOS. CoreAudio sendiri adalah API tingkat rendah yang terintegrasi erat dengan OS untuk mengoptimalkan latensi dan kinerja. Untuk memutar audio menggunakan CoreAudio, Anda perlu membuat AudioUnit (plugin audio). AudioUnit API agak panjang, tapi mudah dimengerti. Berikut cara membuat AudioUnit baru:
AudioComponent output;
AudioUnit unit;
AudioComponentDescription descr;
AURenderCallbackStruct cb;
AudioStreamBasicDescription stream;
descr.componentType = kAudioUnitType_Output,
descr.componentSubType = kAudioUnitSubType_DefaultOutput,
descr.componentManufacturer = kAudioUnitManufacturer_Apple,
// Actual sound will be generated asynchronously in the callback tone_cb
cb.inputProc = tone_cb;
stream.mFormatID = kAudioFormatLinearPCM;
stream.mFormatFlags = 0;
stream.mSampleRate = 8000;
stream.mBitsPerChannel = 8;
stream.mChannelsPerFrame = 1;
stream.mFramesPerPacket = 1;
stream.mBytesPerFrame = 1;
stream.mBytesPerPacket = 1;
output = AudioComponentFindNext(NULL, &descr);
AudioComponentInstanceNew(output, &unit);
AudioUnitSetProperty(unit, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, 0, &cb, sizeof(cb));
AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0, &stream, sizeof(stream));
AudioUnitInitialize(unit);
AudioOutputUnitStart(unit);
Kode ini hanya membuat dan memulai AudioUnit baru, pembuatan suara sebenarnya akan terjadi secara asinkron di callback:
static OSStatus tone_cb(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber,
UInt32 inNumberFrames, AudioBufferList *ioData) {
unsigned int frame;
unsigned char *buf = ioData->mBuffers[0].mData;
unsigned long i = 0;
for (i = 0; i < inNumberFrames; i++) {
buf[i] = beep_freq > 0 ? (255 * theta * beep_freq / 8000) : 0;
theta++;
counter--;
}
return 0;
}
Callback ini menghasilkan suara dengan cara yang sama seperti yang kita lakukan dengan ALSA, tetapi dipanggil secara asynchronous ketika CoreAudio menganggap bahwa buffer audio hampir kosong dan perlu diisi dengan sampel audio baru.
Pendekatan asinkron untuk menghasilkan suara ini sangat umum, dan hampir setiap perpustakaan audio modern mendukungnya. Jika Anda ingin membuat aplikasi musik, Anda harus mendesainnya dengan mempertimbangkan pemutaran asinkron.
Jika ragu, gunakan CoreAudio.
Kedengarannya rumit, bukan?
Jika Anda membuat aplikasi musik, Anda dapat mengikuti jalur yang sama dengan mengimplementasikan backend audio untuk WASAPI, ALSA, dan CoreAudio. Sebenarnya tidak sesulit itu. Anda dapat melihat kode sumber lengkap pager , ini sekitar 100 baris kode untuk ketiga platform.
Namun, ada sejumlah pustaka lintas platform yang bagus seperti:
- RtAudio + RtMidi (sangat mudah digunakan, satu file .cpp dan .h);
- PortAudio + PortMidi (ditulis dalam C dan sedikit lebih besar), memiliki banyak backend yang berbeda;
- SoundIO adalah perpustakaan kecil yang luar biasa dari pencipta Zig.
Beberapa orang lebih suka menggunakan JUCE untuk aplikasi audio lintas platform, tetapi JUCE memiliki keterbatasan.
Semua hal di atas mungkin tampak seperti tugas yang menakutkan, tetapi ada banyak penerapan, dan kebanyakan dari mereka bagus. Jadi, teruslah mencoba!
Saya harap Anda menikmati artikel ini. Anda dapat mengikuti berita dan proyek di Github , Twitter, atau berlangganan melalui RSS .