LINUX.ORG.RU

Программирование alsa (не могу добиться нормального звука)


0

0

Пытаюсь написать программку, генерирующую синусоиду (а вообще - еще и другие сигналы). На OSS все работало, но не факт, что oss будет поддерживаться в дальнейшем, поэтому пытаюсь сделать аналог на ALSA. Компилирую, запускаю - раздаются какие-то щелчки и гул. Подскажите, пожалуйста, кто программировал на ALSA, как сгенерировать нормальную синусоиду? Может быть, здесь:

s1 = c1+A1*cos((time+((double)i)*rate)*_2pi*freq1);
что-то не так?

Сама программка:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sched.h>
#include <errno.h>
#include <getopt.h>
#include <alsa/asoundlib.h>
#include <sys/time.h>
#include <math.h>
static char *device = "plughw:0,0"; /* playback device */
static snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE; /* sample format */
#define METHOD SND_PCM_ACCESS_RW_INTERLEAVED
static unsigned int rate = 44100; /* stream rate */
static unsigned int channels = 2; /* count of channels */
static unsigned int period_time; /* period time in us */
double freq1 = 440; /* sinusoidal wave frequency in Hz */
double freq2 = 440; /* sinusoidal wave frequency in Hz */
short c1 = 0, c2 = 0, A1 = 32700, A2 = 32700;
int size;
static int verbose = 0; /* verbose flag */
static snd_output_t *output = NULL;
double dtime(){
	struct timeval ct;
	struct timezone tz;
	gettimeofday(&ct, &tz);
	return (ct.tv_sec + ct.tv_usec/1e6);
}
static int xrun_recovery(snd_pcm_t *handle, int err){
	if (err == -EPIPE) { /* under-run */
		err = snd_pcm_prepare(handle);
		if (err < 0)
		 printf("Can't recovery from underrun, prepare failed: %s\n", snd_strerror(err));
		return 0;
	} else if (err == -ESTRPIPE) {
		while ((err = snd_pcm_resume(handle)) == -EAGAIN)
		 usleep(1); /* wait until the suspend flag is released */
		if (err < 0) {
		 err = snd_pcm_prepare(handle);
		 if (err < 0)
		 printf("Can't recovery from suspend, prepare failed: %s\n", snd_strerror(err));
		}
		return 0;
	}
	return err;
}
static int write_loop(snd_pcm_t *handle_o){
	double time0, time, _2pi = 2.*M_PI;
	unsigned char tmp;
	int i, s1, s2, _ss;
	snd_pcm_sframes_t err;
	double rnd1, rnd2, rate;
	time0 = dtime();
	char *rdata = (char *) malloc(size);
	_ss = size / 4;
	rate = period_time / _ss;
	short *data = (short*) rdata;
	snd_pcm_nonblock(handle_o, 1);
	while(1){
		time = dtime() - time0;
		for(i = 0; i < _ss; i+=2){
			s1 = c1+A1*cos((time+((double)i)*rate)*_2pi*freq1);
			s2 = c2+A2*cos((time+((double)i)*rate)*_2pi*freq2);
			if(s1 < -32700)	data[i] = -32700;
			else if(s1 > 32700) data[i] = 32700;
			else data[i] = s1;
			if(s2 < -32700)	data[i+1] = -32700;
			else if(s2 > 32700) data[i+1] = 32700;
			else data[i+1] = s2;
		}
		err = snd_pcm_writei(handle_o, rdata, size);
		if (err < 0)
			err = snd_pcm_recover(handle_o, err, 0);
		if (err < 0){
			printf("snd_pcm_writei failed\n");
			break;
		}
	}
}
int main(int argc, char *argv[]){
	snd_pcm_t *handle_o;
	int err, morehelp;
	snd_pcm_hw_params_t *hwparams_o;
	snd_pcm_sw_params_t *swparams_o;
	snd_pcm_uframes_t frames;
	unsigned int chn, val;
	if(argc==2) freq1 = freq2 = atof(argv[1]);
	else if(argc == 3){
		freq1 = atof(argv[1]);
		freq2 = atof(argv[2]);
	}
	printf("Playback device is %s\n", device);
	printf("Stream parameters are %iHz, %s, %i channels\n", rate, snd_pcm_format_name(format), channels);
	printf("Sine wave rate is (%.4f/%.4f)Hz\n", freq1, freq2);
	if ((err = snd_pcm_open(&handle_o, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
		printf("Playback open error: %s\n", snd_strerror(err));
		return 0;
	}
	snd_pcm_hw_params_alloca(&hwparams_o);
	snd_pcm_hw_params_any(handle_o, hwparams_o);
	snd_pcm_hw_params_set_access(handle_o, hwparams_o, METHOD);
	snd_pcm_hw_params_set_format(handle_o, hwparams_o, format);
	snd_pcm_hw_params_set_channels(handle_o, hwparams_o, 2);
	val = 44100;
	snd_pcm_hw_params_set_rate_near(handle_o, hwparams_o, &val, &err);
	frames = 128;
	snd_pcm_hw_params_set_period_size_near(handle_o, hwparams_o, &frames, &err);
	printf("set period size = %d: %s\n", frames, snd_strerror(err));
	err = snd_pcm_hw_params(handle_o, hwparams_o);
	if (err < 0) {
		printf("unable to set hw parameters: %s\n", snd_strerror(err));
		exit(1);
	}
	snd_pcm_hw_params_get_period_size(hwparams_o, &frames, &err);
	size = frames * 4;
	snd_pcm_hw_params_get_period_time(hwparams_o, &period_time, &err);
	printf("Period size: %dx4, period time: %d\n", frames, period_time);
	if (verbose > 0){
		snd_pcm_dump(handle_o, output);
	}
	snd_pcm_prepare(handle_o);
	err = write_loop(handle_o);
	if (err < 0)
		printf("Transfer failed: %s\n", snd_strerror(err));
	snd_pcm_close(handle_o);
	return 0;
}

☆☆☆☆☆

Писец. Я слыщал, что АПИ там страшное, но чтоб так размазать синусоиду...

anonymous
()
Ответ на: комментарий от anonymous

но чтоб так размазать синусоиду...

:)

В oss та же синусоида занимала намного меньше места (плюс еще и громкость настраивалась на максимум). ALSA - вообще жуть какая-то. P.S. Ты бы посмотрел пример с сайта alsa, там эту синусоиду вообще на 900 с лишним строк «размазали».

Eddy_Em ☆☆☆☆☆
() автор топика
Ответ на: комментарий от anonymous

Не сомневаюсь, OSS - наше все, но сейчас наблюдается какая-то странная тенденция перехода на alsa. Вот и хочу научиться и с ней работать на всякий случай.

Eddy_Em ☆☆☆☆☆
() автор топика
Ответ на: комментарий от arsi

Спасибо! Правда, я так и не понял, почему у вас работает, а у меня нет. Ну, да ладно. Я, все-таки, не программист :) Как менять частоту разберусь. Осталось только найти, как через эту идиотскую ALSA без OSS-ного /dev/mixer менять громкость.

P.S. А зачем конструкции do{}while(0)? Что, без них разве точно так же работать не будет?

Eddy_Em ☆☆☆☆☆
() автор топика
Ответ на: комментарий от arsi

Разобрался, спасибо :) Можно частоту еще и через period_size менять.

P.S. А все-таки, как быть с микшером?

Eddy_Em ☆☆☆☆☆
() автор топика
Ответ на: комментарий от Eddy_Em

имхо, удобное апи — это стабильное апи. не долгоживущее или постоянно изменяющееся приведёт к ещё большему геморрою когда придётся переписывать тонны кода после нового «революционного» релиза или перехода на другое. хотя я хз, что там будет с OpenAL, что-то оно пока не очень живое, но было бы неплохо получить нормальное дополнение к OpenGL. так ещё OpenInput и получим свой OpenX %)

arsi ★★★★★
()
Ответ на: комментарий от Eddy_Em

> P.S. А все-таки, как быть с микшером?

с микшером я не разбирался, только с выводом звука работал (после декодирования libffmpeg). могу порекомендовать посмотреть исходники alsa-utils/alsamixer (в mixer_controls.c основной код, если не ошибаюсь). приду домой — попробую разобраться.

arsi ★★★★★
()
Ответ на: комментарий от arsi

Я их уже смотрел, попытаюсь еще поковыряться, но с первого взгляда это жуть какая-то (особенно если сравнить с компактным ossmixer).

Eddy_Em ☆☆☆☆☆
() автор топика

А зачем вам такое низкоуровневое API? Возьмите что-нибудь уровнем повыше.

mv ★★★★★
()
Ответ на: комментарий от mv

Синусоида - только пример. Мне надо работать с сигналами «напрямую», т.е. использовать звуковую карту как многоканальный ЦАП и двухканальный АЦП. Поэтому и хочется уровень пониже, т.к. та же ALSA требует заполнения некоторого буфера, иначе - underrun и непонятно что на выходе. А мне нужно, например, менять напряжение на ЦАП (асинхронно), и так же асинхронно считывать данные с АЦП. В OSS это все решалось достаточно просто.

В общем, смотрю я, лучше на OSS и оставаться :(

Eddy_Em ☆☆☆☆☆
() автор топика

Я тебе скажу так.

Пока писал через библиотеки SDL и SDL_mixer с использованием /dev/dsp, то все было нормально, звук был такой, как я и ожидал.

Затем в свяи с тем, что такой подход не инициализировался на некоторых дешевых материнках со встроенным AC звучком, пришлось использовать вместо /dev/dsp девайс alsa. С ним прога стала инитить звук и на проблемных карточках. Но качество звука зависит дистра, версии alsa и фазы луны.

У меня на SBLive 5.1 звук пердит в Debian 5.0.1 и в Ubuntu 7.04. В то же время в ALTMaster2.4 и Knoppix 6.x звук нормальный. C чем все это связано не знаю.

Но раздражает безумно. Как бы 2010 год на дворе, нормальное железо, и пердит.

xintrea
()

Да уж, api у альсы кошмарно. Потому-то я и юзаю Jack.

kranky ★★★★★
()
Ответ на: комментарий от arsi

Спасибо :)

Я думал, будет намного больше кода, а так по размеру - 91 строка у вас, и 64 - для oss. Вполне нормально.

Eddy_Em ☆☆☆☆☆
() автор топика
Ответ на: комментарий от Eddy_Em

да пожалуйста) не так страшна альса, как её рисуют ;)

пара замечаний: 1) в последнем коде (микшер): с апи микшера я практически не разбирался, просто надёргал кусков с гугла и доки, так что, вполне возможно, там нет необходимых освобождений выделенных ресурсов, рекомендую уточнить этот момент дабы избежать ненужных утечек.

2) по поводу вывода звука: если использовать неблокирующий режим вывода (SND_PCM_NONBLOCK в snd_pcm_open) в паре с ожиданием опустошения половины буфера [размер буфера можно узнать/установить через snd_pcm_hw_params_set_buffer_time_min(…)], можно получить идеально чистый звук, что бы там не говорили приверженцы других аудио-фронтэндов ;)

arsi ★★★★★
()
Ответ на: комментарий от x905

Спасибо, С++ не хочу. Да и с ALSA уже разобрался более-менее (по крайней мере, частоту и громкость менять могу).

Eddy_Em ☆☆☆☆☆
() автор топика
Ответ на: комментарий от arsi

Не знаете: можно ли записывать и читать звук в ALSA без задержки? То есть я выдаю сигнал на LineOut и тут же принимаю его, но слегка изменённый с LineIn/Mic.

А то сейчас у меня задержка чуть ли не в секунду. Весь смысл идеи теряется.

kde4-hater
()
Ответ на: комментарий от kde4-hater

С oss у меня получалось сделать практически полный дуплекс (т.е. читать и писать одновременно). С alsa сколько ни бился, пока такого сделать не смог.

Eddy_Em ☆☆☆☆☆
() автор топика
Ответ на: комментарий от kde4-hater

хм… с захватом аудио не работал, но, возможно, проблема в приёмном буфере (точнее, в его размере). попробуйте уменьшить его размер и делать сам захват в блокирующем режиме, без callback'ов и прочего.

а ещё libastral мне подсказывает, что проблема может быть в самой архитектуре альсы, которая, в отличие от осс, умеет микшировать аудиовывод из разных приложений, и далеко не факт, что все приложения будут использовать ту же частоту дискретизации, и для нормального микширования необходим немаленький буфер… хотя, должен заметить, что именно сам вывод идёт без задержек: код из первого моего сообщения в этой теме — фрагмент собственного почти_видеоплеера, и рассинхронизации аудио и видео я никогда не замечал. правда, при синхронизации a/v идёт сброс буферов альсы и контроль их заполнения во время воспроизведения (с ресинхронизацией вдруг чего)…

arsi ★★★★★
()
Ответ на: комментарий от arsi

В том то и дело, что альса уж слишком буферизует. Если сделать большой буфер - будут задержки. Если маленький - возможны проблемы с переполнением буфера. OSS работает на более низком уровне. Думаю, вполне можно провести аналогию: alsa/oss и fwrite,fread/write,read. Только если у fwrite,fread буферы сбрасываются достаточно легко, то у альсы с большим размером буфера может возникнуть задержка. А без задержки придется терять данные.

Eddy_Em ☆☆☆☆☆
() автор топика
Ответ на: комментарий от Eddy_Em

> OSS работает на более низком уровне.

/me вспоминает, как много лет назад программировал на асме вывод в soundblaster16 под досом через собственные аппаратные прерывания и управление dma на низком уровне… вот это золотые дни были, да! куда там нынешним неуправляемым и непредсказуемым альсам, джекам, пульсам… ;)

arsi ★★★★★
()
Ответ на: комментарий от deathmagnetic

Так джек же через альсу работает. Это же еще хуже получится.

много лет назад программировал на асме вывод в soundblaster16

arsi, я бы с удовольствием сделал и на более низком уровне. Но боюсь, разбираться придется год, чтобы все через ioctl'ы реализовать.

Eddy_Em ☆☆☆☆☆
() автор топика
Ответ на: комментарий от Eddy_Em

чуть выше я упоминал о сбросе буферов для синхронизации A/V в собственном приложении. хоть я и делал это для синхронизации вывода звука, но подозреваю, что то же самое можно (нужно?) сделать и при захвате.

arsi ★★★★★
()
Ответ на: комментарий от deathmagnetic

dmix - расширение для дурацких встроенных карточек, не умеющих аппаратное микширование. На всех трех карточках, с которыми я работал, аппаратное микширование было.

Eddy_Em ☆☆☆☆☆
() автор топика
Ответ на: комментарий от Eddy_Em

>dmix - расширение для дурацких встроенных карточек, не умеющих аппаратное микширование. На всех трех карточках, с которыми я работал, аппаратное микширование было.

ясно, значит dmix не при делах. а отвечал то я если заметите на вот это:

умеет микшировать аудиовывод из разных приложений, и далеко не факт, что все приложения будут использовать ту же частоту дискретизации, и для нормального микширования необходим немаленький буфер…


про jack. как-то по приколу использовал его с LADSPA для рилтайм-обработки гитары. на слух задержки не было, а значит это порядка 10 мс. неплохо ведь? исходя из это и был совет посмотреть каким макаром он работает с alsa-устройсвами

deathmagnetic
()
Ответ на: комментарий от deathmagnetic

значит это порядка 10 мс. неплохо ведь?

Если заниматься DSP, или управлением чем-нибудь, то 10мс может быть много...

Eddy_Em ☆☆☆☆☆
() автор топика
Ответ на: комментарий от Eddy_Em

тогда да, alsa не вариант и надо опускаться ниже. у меня что в винде через asio, что в linux через jack/alsa минимум выходил в 5-6 мс

deathmagnetic
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.