Генерация звуков на C
Требуется сгенерировать звук определённой частоты и продолжительности, но без спикера, а через вывод звука. Что-то наподобие такого:
Beep(350, 500);
Есть ли такие библиотеки под C? Или в каком направлении смотреть?
Ответы (2 шт):
Интересная у вас задача. Только что решил написать удобную небольшую библиотеку-обёртку, использующую мультимедийную кроссплатформенную библиотеку SDL (написанную на C).
Получилась обёртка, которая может исполняться на любой популярной ОС (Windows, Linux, MacOS, Android, iOS).
Также можете посмотреть мою аналогичную библиотеку-обёртку проигрывания WAV также используя SDL.
Библиотека содержит 3 файла - play_sine.h заголовочный файл, play_sine.c файл реализующий функции, play_sine_usage_example.c пример использования библиотеки.
Библиотека кроме функции проигрывания содержит дополнительный функционал и макросы для сборки и вывода возможных ошибок, что занимает значительную часть библиотеки. Если интересна только функция проигрывания звука то смотрите только PlaySine(...) в play_sine.c.
Использовать очень просто - достаточно вызвать для примера PlaySine(500, 5000) аналогично Beep(500, 5000) и возможно проверить ошибку как в примере. Также можно выставить уровень громкости до этого, но не обязательно, через PlaySineSetVolume(0.3) (на 30%), по умолчанию уровень громкости в районе 80%.
Скомпилировать код можно следующим образом:
Под Windows (используя MSVC):
Скачиваем SDL, например последнюю версию на сегодня.
Помещаем содержимое (файлы) из
./include/(в архиве) в папкуSDL2/. Папку./lib/из архива просто копируем.Запускаем MSVC консоль.
Компилируем:
cl play_sine_usage_example.c play_sine.c ./lib/x64/SDL2.lib -I.. Здесьx64для 64-битного приложения иx86для 32-битного.Приложение EXE готово.
Из
./lib/папки рядом с EXE кладём соответствующую DLL. Если наличие DLL не удобно (меня например это не устраивает в моих проектах), то можно скачав SDL исходники скомпилировать их в статическую.lib.Запускаем
play_sine_usage_example.exe.
Под Linux (используя GCC):
Установить SDL через
sudo apt install libsdl2-dev. Также альтернативно можно скачать как для Windows исходники с сайта SDL и скомпилировать статическую версию.Скомпилировать код моего примера и библиотеки через
gcc -o play_sine_usage_example play_sine_usage_example.c play_sine.c -lm -lSDL2.Запустить
./play_sine_usage_example.Пример компиляции и запуска под Linux на удалённом сервере можно найти тут (конечно удалённый сервер звук не проиграет, но программа соберётся и запустится).
play_sine_usage_example.c:
// play_sine_usage_example.c
#include "play_sine.h"
#include <stdio.h>
int main() {
if (PlaySineSetVolume(0.3) != 0)
printf("Error:\n%s\n", PlaySineError(1));
if (PlaySine(500, 5000) != 0)
printf("Error:\n%s\n", PlaySineError(1));
}
play_sine.h:
// play_sine.h
#ifndef _PLAY_WAV_H_
#define _PLAY_WAV_H_
#include <stdint.h>
// Play Sine Wave.
// Returns 0 on success, and non-0 on error.
uint32_t PlaySine(uint32_t freq, uint32_t duration_ms);
// Set volume, from 0.0 to 1.0.
// Returns 0 on success, and non-0 on error.
uint32_t PlaySineSetVolume(double vol);
// Returns error stack, clears errors stack if clear_errors != 0.
char const * PlaySineError(int clear_errors);
#endif // _PLAY_WAV_H_
play_sine.c:
// play_sine.c
#include "play_sine.h"
#include <SDL2/SDL.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
enum PlayWavErrors {
ERR_PLAY_SINE_SDL_FAILED = 0xC8B30001,
ERR_PLAY_SINE_WRONG_VOLUME,
ERR_PLAY_SINE_WRONG_FREQ,
ERR_PLAY_SINE_MALLOC_FAILED,
};
#define MEMZERO(obj) memset(&(obj), 0, sizeof(obj))
#define MAX(a, b) ((a) >= (b) ? (a) : (b))
#define MIN(a, b) ((a) <= (b) ? (a) : (b))
#ifndef M_PI
#define M_PI 3.14159265358979
#endif
typedef uint32_t Error;
typedef int16_t Int16;
struct ErrInfo {
uint32_t line;
Error err;
char const * serr;
char const * func;
char const * code;
char const * file;
char msg[256];
};
enum { c_max_errors_cnt = 128 };
static struct ErrInfo g_errors[c_max_errors_cnt];
static uint32_t g_errors_cnt = 0;
static void ClearErrors() {
MEMZERO(g_errors);
g_errors_cnt = 0;
}
#define ERRIFMSGFIN(cond, err_, msg_, fin_) \
if (cond) { \
if (g_errors_cnt < c_max_errors_cnt) { \
g_errors[g_errors_cnt].err = (err_); \
g_errors[g_errors_cnt].line = __LINE__; \
g_errors[g_errors_cnt].func = __FUNCTION__; \
g_errors[g_errors_cnt].file = __FILE__; \
g_errors[g_errors_cnt].code = #cond; \
g_errors[g_errors_cnt].serr = #err_; \
MEMZERO(g_errors[g_errors_cnt].msg); \
if (msg_) strncpy(g_errors[g_errors_cnt].msg, (msg_), sizeof(g_errors[g_errors_cnt].msg) - 1); \
++g_errors_cnt; \
} \
fin_; \
return (err_); \
}
#define ERRIFMSG(cond, err_, msg_) ERRIFMSGFIN(cond, err_, msg_, {})
#define ERRIF(cond, err) ERRIFMSG(cond, err, "")
#define SDLERRIF(cond) ERRIFMSG(cond, ERR_PLAY_SINE_SDL_FAILED, SDL_GetError())
#define SDLERRIFFIN(cond, fin) ERRIFMSGFIN(cond, ERR_PLAY_SINE_SDL_FAILED, SDL_GetError(), fin)
static bool g_sdl_inited = false;
static void SdlInit() {
if (!g_sdl_inited) {
SDL_Init(SDL_INIT_AUDIO);
g_sdl_inited = true;
}
}
static void SdlQuit() {
if (g_sdl_inited) {
SDL_Quit();
g_sdl_inited = false;
}
}
static uint32_t g_amplitude = 28000;
Error PlaySineSetVolume(double vol) {
ERRIFMSG((vol < 0 || vol > 1), ERR_PLAY_SINE_WRONG_VOLUME, "PlaySine: Wrong volume!");
g_amplitude = (uint32_t)(vol * 32766);
return 0;
}
Error PlaySine(uint32_t freq, uint32_t duration_ms) {
ERRIFMSG(freq > 44100, ERR_PLAY_SINE_WRONG_FREQ, "PlaySine: Wrong frequency!");
SdlInit();
uint32_t const base_freq = freq <= 4000 ? 8000 : freq <= 8000 ? 16000 : freq <= 11025 ? 22050 : 44100;
enum { samples_block = (1 << 17), samples_dev = 4096 };
// Set audio spec.
SDL_AudioSpec spec; MEMZERO(spec);
spec.freq = base_freq;
spec.format = AUDIO_S16SYS;
spec.channels = 1;
spec.samples = samples_dev;
// Open audio device.
SDL_AudioDeviceID dev = 0;
SDLERRIF((dev = SDL_OpenAudioDevice(NULL, 0, &spec, NULL, 0)) == 0);
// Gen sine wave.
uint32_t const nsamples = base_freq * duration_ms / 1000;
Int16 * samples = (Int16*)malloc(samples_block * sizeof(Int16));
ERRIFMSG(samples == 0, ERR_PLAY_SINE_MALLOC_FAILED, "PlaySine: Mem alloc failed!");
for (uint32_t i = 0; i < samples_block; ++i)
samples[i] = (Int16)(((double)g_amplitude) * sin(((double)freq) * i / base_freq * 2 * ((double)(M_PI))));
// stop if audio is playing
SDL_PauseAudioDevice(dev, 1);
// clear existing audio in queue
SDL_ClearQueuedAudio(dev);
// play audio
bool finished = false;
for (uint32_t nplayed = 0;; nplayed += samples_block) {
SDLERRIFFIN(SDL_QueueAudio(dev, samples, samples_block * sizeof(Int16)) != 0, {
free(samples);
});
if (nplayed == 0) {
// start playing audio
SDL_PauseAudioDevice(dev, 0);
}
while (1) {
Uint32 queued_size = SDL_GetQueuedAudioSize(dev);
if (nplayed + samples_block - queued_size / sizeof(Int16) >= nsamples) {
finished = true;
break;
}
if (queued_size <= (samples_dev * 3) * sizeof(Int16))
break;
SDL_Delay(10);
}
if (finished)
break;
}
free(samples);
// stop playing
SDL_PauseAudioDevice(dev, 1);
// clear existing audio in queue
SDL_ClearQueuedAudio(dev);
// clean up
SDL_CloseAudioDevice(dev);
return 0;
}
char const * PlaySineError(int clear_errors) {
static char buf[2048];
MEMZERO(buf);
for (uint32_t i = 0; i < g_errors_cnt; ++i)
snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "%d:%s:%d:%s():\"%s\":\"%s\"\n",
i, g_errors[i].file, g_errors[i].line, g_errors[i].func, /*g_errors[i].serr, g_errors[i].err,*/ g_errors[i].code, g_errors[i].msg);
if (clear_errors)
ClearErrors();
return buf;
}
Нашёл в интернете код на С++ решающий проблему топикстартера. Переделал его на Си, в Pelles C компилируется, работает.
#include <windows.h>
#include <math.h>
// подключаем LIB-файл для ф-ции PlaySound
#pragma comment(lib, "winmm.lib")
// описание заголовка файла WAV
typedef struct WAVHEADER
{
char sigRIFF[4]; // должно быть равно "RIFF"
DWORD sizeRIFFch; // размер чанка RIFF
char sigWAVE[4]; // должно быть равно "WAVE"
char sigFMT[4]; // должно быть равно "fmt "
DWORD sizeFMTch; // размер чанка FMT
WORD wFormatTag; // категория формата, для PCM = 1
WORD wChannels; // кол-во каналов: 1-моно 2-стерео
DWORD dwSamplesPerSec;// кол-во сэмплов в сек.
DWORD dwAvgBytesPerSec;// среднее число байт в сек
WORD wBlockAlign; // выравнивание данных в дата-чанке
WORD wBitPerSample; // бит в сэмпле
char sigDATA[4]; // должно быть равно "data"
DWORD sizeDATAch; // размер data-чанка
} WAVHEADER;
void SuperBeep(DWORD dwFrequency, DWORD dwMilliSeconds)
{
// частота дискретизации = 44100 Гц
// кол-во бит на сэмпл = 8
// если требуемая частота > 44100 или равна нулю, то выходим
if(!dwFrequency || dwFrequency>44100)
return;
// длина дорожки в байтах
DWORD numSamples = 44100 / dwFrequency;
// выделяем память под дорожку
DWORD size = sizeof(WAVHEADER) + numSamples;
void *buff = (char*)malloc(size);
// заполняем WAV-header
WAVHEADER *head = (WAVHEADER*)buff;
strcpy(head->sigRIFF, "RIFF");
strcpy(head->sigWAVE, "WAVE");
head->sizeRIFFch = size - 8;
strcpy(head->sigFMT, "fmt ");
head->sizeFMTch = 16;
head->wFormatTag = 1;
head->wChannels = 1; // моно
head->dwSamplesPerSec = 44100;
head->dwAvgBytesPerSec = 44100;
head->wBlockAlign = 1;
head->wBitPerSample = 8;
strcpy(head->sigDATA, "data");
head->sizeDATAch = size;
// заполняем дорожку периодом синуса
BYTE *samples = (BYTE*)(head+1);
for(DWORD i=0; i<numSamples; i++)
samples[i] = (BYTE)(255*sin(6.28*(double)i/(double)numSamples));
// проигрываем звук
PlaySound((const char*)buff, 0, SND_ASYNC|SND_LOOP|SND_MEMORY);
// ждём заданное количество миллисекунд
Sleep(dwMilliSeconds);
// останавливаем звук
PlaySound(0, 0, SND_ASYNC);
free(buff);
}
int main(int argc, char* argv[])
{
SuperBeep(20000, 2000);
SuperBeep(2000, 2000);
SuperBeep(200, 2000);
SuperBeep(100, 2000);
SuperBeep(20, 2000);
return 0;
}