LINUX.ORG.RU

[C++]Программирование c Alsa с постоянным underrun

 


0

1

Доброго времени суток! Уважаемые, кто пользовался Alsa для программирования приложений, помогите. Пишу консольный плеер. Использовал примеры из инета, разные версии, но все работают одинаково :( Суть проблемы такова, что почему-то периодически возникает underrun буфера альсы. При чем это не зависит от того использую ли я callback или проверяю буфер в цикле. Использую ф-цию snd_pcm_avail(pcm_handle). В логе программы возвращает 12 при заполнении буфера (из возможных 15052, то есть буфер заполнен почти полностью)...а при следующем вызове callback'а уже выбрасывается ошибка underrun, то есть буфер опустел... Если опрашивать в цикле, происходит то же самое, т.к. используется та же функция. Пробовал разные функции, но результат тот же: музыка воспроизводится, даже иногда ровно по несколько секунд, без сбоев, но довольно часто проскакивает( Уже после недели топтания на одном месте перестаю понимать что вообще происходит и как работает этот дурацкий рингбуфер... В данном куске циклический опрос буфера.

#define ALSA_PCM_NEW_HW_PARAMS_API

#include <alsa/asoundlib.h>
#include <stdio.h>
#include <unistd.h>
#include <iostream>
#include <fstream>

using namespace std;

char *buffer;
int period_size;
ifstream is;

int main(int argc, char ** argv) {
  long loops;
  int rc;
  int size;
  snd_pcm_t *handle;
  snd_pcm_hw_params_t *params;
  snd_pcm_sw_params_t *sw_params;
  unsigned int val, val2;
  int dir;
  snd_pcm_uframes_t frames, frames_to_deliver;
  
  if (argc < 2) {
		printf( "%s : you must specified wav file name\n", argv[0] );
		return 0;
	}
  /* Open PCM device for playback. */
  rc = snd_pcm_open(&handle, "default",
                    SND_PCM_STREAM_PLAYBACK, 0);
  if (rc < 0) {
    fprintf(stderr,
            "unable to open pcm device: %s\n",
            snd_strerror(rc));
    exit(1);
  }

  snd_pcm_hw_params_alloca(&params);
  snd_pcm_hw_params_any(handle, params);
  snd_pcm_hw_params_set_access(handle, params,
                      SND_PCM_ACCESS_RW_INTERLEAVED);

  snd_pcm_hw_params_set_format(handle, params,
                              SND_PCM_FORMAT_S16_LE);
  snd_pcm_hw_params_set_channels(handle, params, 2);
  val = 44100;
  snd_pcm_hw_params_set_rate_near(handle, params,
                                  &val, &dir);
  frames = 12;
  snd_pcm_hw_params_set_period_size_near(handle,
                              params, &frames, &dir);
  rc = snd_pcm_hw_params(handle, params);
  if (rc < 0) {
    fprintf(stderr,
            "unable to set hw parameters: %s\n",
            snd_strerror(rc));
    exit(1);
  }

  
  /* Use a buffer large enough to hold one period */
  snd_pcm_hw_params_get_period_size(params, &frames,
                                    &dir);
  period_size = frames;
  size = frames * 4; /* 2 bytes/sample, 2 channels */
  buffer = (char *) malloc(size);
	
  
  
  printf("%d\n", (int)frames);
  frames_to_deliver = frames;
  /* We want to loop for 60 seconds */
  snd_pcm_hw_params_get_period_time(params, &val, &dir);

  loops = 60000000 / val;
	
	
	if ((rc = snd_pcm_sw_params_malloc (&sw_params)) < 0) {
		fprintf (stderr, "cannot allocate software parameters structure (%s)\n",
		 snd_strerror (rc));
		exit (1);
	}
	if ((rc = snd_pcm_sw_params_current (handle, sw_params)) < 0) {
		fprintf (stderr, "cannot initialize software parameters structure (%s)\n",
		 snd_strerror (rc));
		exit (1);
	}
	if ((rc = snd_pcm_sw_params_set_avail_min (handle, sw_params, period_size)) < 0) {
		fprintf (stderr, "cannot set minimum available count (%s)\n",
		 snd_strerror (rc));
		exit (1);
	}
	if ((rc = snd_pcm_sw_params_set_start_threshold (handle, sw_params, frames*2)) < 0) {
		fprintf (stderr, "cannot set start mode (%s)\n",
		 snd_strerror (rc));
		exit (1);
	}
	if ((rc = snd_pcm_sw_params (handle, sw_params)) < 0) {
		fprintf (stderr, "cannot set software parameters (%s)\n",
		 snd_strerror (rc));
		exit (1);
	}
 
  is.open (argv[1], ios::binary );
  int avail_frames;
  snd_pcm_hw_params_get_buffer_size(params, (snd_pcm_uframes_t *) &avail_frames);
  int loops_count = avail_frames/(int)frames;
  loops_count--;
  while (loops > 0) {
    loops--;
    
	if (((int)snd_pcm_avail_update(handle)) > ((int)frames)) {
		is.read((buffer),period_size*4);
		printf("!");
		rc = snd_pcm_writei(handle, buffer, frames_to_deliver);
		printf("Write return = %d (of buffer %d). Now availible %d\n", rc, frames_to_deliver, (int)snd_pcm_avail_update(handle));
	}
	    
    if (rc == -EPIPE) {
      /* EPIPE means underrun */
      fprintf(stderr, "underrun occurred, buffer = %d\n", (int)frames);
      snd_pcm_prepare(handle);
    } else if (rc < 0) {
      fprintf(stderr,
              "error from writei: %s\n",
              snd_strerror(rc));
    }  else if (rc != (int)frames) {
      fprintf(stderr,
              "short write, write %d frames\n", rc);
    }
	printf("%d of %d\n",(int)snd_pcm_avail_update(handle), avail_frames);
	long int delay = (int)frames;
	
	if ((frames_to_deliver = snd_pcm_avail_update (handle)) < 0) {
		if (frames_to_deliver == -EPIPE) {
			fprintf (stderr, "an xrun occured\n");
			break;
		} else {
			fprintf (stderr, "unknown ALSA avail update return value (%d)\n", 
			 frames_to_deliver);
			break;
		}
	}
	
	frames_to_deliver = frames_to_deliver > 4096 ? 4096 : frames_to_deliver;
	
  }
  snd_pcm_drain(handle);
  snd_pcm_close(handle);
  free(buffer);

  return 0;
}


Последнее исправление: Agathodaimon (всего исправлений: 5)

Ответ на: комментарий от DELIRIUM

Но зачем?

обучение же, многие с этого начинают, правда не все потом идут дальше

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

Но зачем?

Нуууу... это риторический вопрос.

Он должен будет работать в составе системы. А какой иной вариант? С gui? Нееет, данной системе gui не просто бесполезен, а даже навредит, поскольку его все равно никто не увидит. +Отсутствие иксов затруднит эту задачу.

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

Да...я вот тоже смотрел... проблема в том что snd_pcm_prepare(который у меня, вроде, на месте) сбрасывает буфер. Поэтому часть аудио данных записанных в него пропадает...

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

только snd_pcm_drain - ничего не воспроизводится. snd_pcm_writei возвращает -77.

snd_pcm_drain + snd_pcm_prepare - то же самое что и только snd_pcm_prepare; Воспроизводится с периодическим опустошения буфера.

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

Может быть что-то с проверкой не то?

(int)snd_pcm_avail_update(handle) > (int)frames_to_deliver

frames_to_deliver - фрэймов за период, у меня в данном случае 940...

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

period_size фактически равно frames_to_deliver. читается в 4 раза больше потому что фрэйм = 4 байта. Чтение идет в чарах. А записывается в альсу во фреймах. Поэтому читаем в 4 раза больше.

Кстати, кусок:

long int delay = (int)frames;
	
	if ((frames_to_deliver = snd_pcm_avail_update (handle)) < 0) {
		if (frames_to_deliver == -EPIPE) {
			fprintf (stderr, "an xrun occured\n");
			break;
		} else {
			fprintf (stderr, "unknown ALSA avail update return value (%d)\n", 
			 frames_to_deliver);
			break;
		}
	}
	
	frames_to_deliver = frames_to_deliver > 4096 ? 4096 : frames_to_deliver;
[[/code]]

Надо убрать, тут фигня написана. Но на проблему он не влияет... Так что не понятно...

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

frames_to_deliver же меняется в процессе, не? Да, еще я не понял, что происходит, если в буфере нет места, busywait? Почему бы не использовать snd_pcm_wait()/poll()/etc?

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

Нет, он пишет соразмерно периоду. Если в буфере появилось место для одного периода - запись frames_to_deliver (который равен period_size).

Если в буфере нет места то запись в этом цикле не происходит. Соответствующий if:

if (((int)snd_pcm_avail_update(handle)) > ((int)frames)) {

Счас вижу что куча избыточности в коде, но, извините, это от долгих стараний над проблемой... по-сути: frames = frames_to_deliver = period_size

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

Удивительно...но когда убрал вывод всех printf, за исключением stderr... все заиграло довольно хорошо, но все равно периодически вылазят underrun. Может он не успевать читать с диска и не успевать записывать в буфер альсы изза printf? Надо попробовать сделать два буфера...

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

Пытался использовать но, что-то с ним тоже не заладилось...попробую в ту сторону посмотреть еще раз...

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

Немного причесал код: результат тот же самый, даже с использованием snd_pcm_wait. Вообще не понимаю :(

#define ALSA_PCM_NEW_HW_PARAMS_API

#include <alsa/asoundlib.h>
#include <stdio.h>
#include <unistd.h>
#include <iostream>
#include <fstream>

using namespace std;

char **buffer_n;
int period_size;
ifstream is;

int main(int argc, char ** argv) {
	long loops;
	int rc;
	int size;
	snd_pcm_t *handle;
	snd_pcm_hw_params_t *params;
	snd_pcm_sw_params_t *sw_params;
	unsigned int val, val2;
	int dir;
	snd_pcm_uframes_t frames, frames_to_deliver;

	if (argc < 2) {
		printf( "%s : you must specified wav file name\n", argv[0] );
		return 0;
		}
	/* Open PCM device for playback. */
	rc = snd_pcm_open(&handle, "default",
					SND_PCM_STREAM_PLAYBACK, 0);
	if (rc < 0) {
		fprintf(stderr,
			"unable to open pcm device: %s\n",
			snd_strerror(rc));
		exit(1);
	}

	snd_pcm_hw_params_alloca(&params);
	snd_pcm_hw_params_any(handle, params);
	snd_pcm_hw_params_set_access(handle, params,
						SND_PCM_ACCESS_RW_INTERLEAVED);

	snd_pcm_hw_params_set_format(handle, params,
								SND_PCM_FORMAT_S16_LE);
	snd_pcm_hw_params_set_channels(handle, params, 2);
	val = 44100;
	snd_pcm_hw_params_set_rate_near(handle, params,
								&val, &dir);
	frames = 940;
	snd_pcm_hw_params_set_period_size_near(handle,
								params, &frames, &dir);
	rc = snd_pcm_hw_params(handle, params);
	if (rc < 0) {
		fprintf(stderr,
			"unable to set hw parameters: %s\n",
			snd_strerror(rc));
		exit(1);
	}
	/* Use a buffer large enough to hold one period */
	snd_pcm_hw_params_get_period_size(params, &frames, &dir);
	period_size = frames;
	size = period_size * 4;
	printf("%d\n", (int)frames);
	/* We want to loop for 60 seconds */
	snd_pcm_hw_params_get_period_time(params, &val, &dir);
	loops = 600000000 / val;
	int buffer_count = 3;
	int i = 0;
		buffer_n = (char**)malloc((buffer_count)*sizeof(char*));
		while ( i++ < buffer_count) {
			printf( "Init buffers %d of %d (%d each)\n", i, buffer_count, size);
			char * row_cell = (char*)malloc((size+2)*sizeof(char));
			buffer_n[i-1] = row_cell;
			//memset(*(buffer_n+i-1), 0, (size)*sizeof(char));
			if(*(buffer_n+i-1) == NULL) {
				printf( "Init buffers failed\n");
				return 0;
			}
		}
		
	is.open (argv[1], ios::binary );
	int frames_per_buffer, period_per_buffer;
	int curr_buffer = 0;
	snd_pcm_hw_params_get_buffer_size(params, (snd_pcm_uframes_t *) &frames_per_buffer);
	period_per_buffer = frames_per_buffer/(int)period_size;

	is.read(*(buffer_n),period_size*4);
	
	while (1) {
		
		while ((int)snd_pcm_avail_update(handle) > (int)period_size) {
			
			rc = snd_pcm_writei(handle, *(buffer_n+curr_buffer), period_size);
			printf("Write return = %d (of buffer %d, in period %d). Now availible %d\n", rc, frames_per_buffer, (int)period_size, (int)snd_pcm_avail_update(handle));
			if ((int)snd_pcm_avail_update(handle) > (int)frames_per_buffer) {
				snd_pcm_prepare(handle);
			}
			if (rc < 0) {
				if (rc == -EPIPE) {
					/* EPIPE means underrun */
					fprintf(stderr, "underrun occurred, buffer = %d\n", (int)period_size);
					//snd_pcm_drain(handle);
					snd_pcm_prepare(handle);
				}
				fprintf(stderr, "error from writei: %s\n",snd_strerror(rc));
			}
			curr_buffer++;
			if(curr_buffer == 3)
				curr_buffer = 0;
			is.read(*(buffer_n+curr_buffer),period_size*4);
			
		}
		if(snd_pcm_wait(handle, 100))
			printf("!");
	}
	snd_pcm_drain(handle);
	snd_pcm_close(handle);

	i = 0;
	while ( i++ < buffer_count) {
		if((buffer_n+i) != 0)
			free(*(buffer_n+i));
	}
	free(buffer_n);
	return 0;
}
[[/code]]
Agathodaimon
() автор топика

прогони через valgrind, часто помогает.

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

Как-то выглядит уж очень сложно. А если так:

#define ALSA_PCM_NEW_HW_PARAMS_API

#include <alsa/asoundlib.h>
#include <stdio.h>
#include <unistd.h>
#include <iostream>
#include <fstream>

using namespace std;

int period_size;
ifstream is;

int main(int argc, char ** argv) {
	long loops;
	int rc;
	int size;
	snd_pcm_t *handle;
	snd_pcm_hw_params_t *params;
	snd_pcm_sw_params_t *sw_params;
	unsigned int val, val2;
	int dir;
	snd_pcm_uframes_t frames, frames_to_deliver;

	if (argc < 2) {
		printf( "%s : you must specified wav file name\n", argv[0] );
		return 0;
		}
	/* Open PCM device for playback. */
	rc = snd_pcm_open(&handle, "default",
					SND_PCM_STREAM_PLAYBACK, 0);
	if (rc < 0) {
		fprintf(stderr,
			"unable to open pcm device: %s\n",
			snd_strerror(rc));
		exit(1);
	}

	snd_pcm_hw_params_alloca(&params);
	snd_pcm_hw_params_any(handle, params);
	snd_pcm_hw_params_set_access(handle, params,
						SND_PCM_ACCESS_RW_INTERLEAVED);

	snd_pcm_hw_params_set_format(handle, params,
								SND_PCM_FORMAT_S16_LE);
	snd_pcm_hw_params_set_channels(handle, params, 2);
	val = 44100;
	snd_pcm_hw_params_set_rate_near(handle, params,
								&val, &dir);
	frames = 940;
	snd_pcm_hw_params_set_period_size_near(handle,
								params, &frames, &dir);
	rc = snd_pcm_hw_params(handle, params);
	if (rc < 0) {
		fprintf(stderr,
			"unable to set hw parameters: %s\n",
			snd_strerror(rc));
		exit(1);
	}
	/* Use a buffer large enough to hold one period */
	snd_pcm_hw_params_get_period_size(params, &frames, &dir);
	period_size = frames;
	size = period_size * 4;
	printf("%d\n", (int)frames);
	/* We want to loop for 60 seconds */
	snd_pcm_hw_params_get_period_time(params, &val, &dir);
	loops = 600000000 / val;
	
	is.open (argv[1], ios::binary );
	
	int frames_per_buffer, period_per_buffer;
	int curr_buffer = 0;
	snd_pcm_hw_params_get_buffer_size(params, (snd_pcm_uframes_t *) &frames_per_buffer);
	period_per_buffer = frames_per_buffer/(int)period_size;
	
	char buffer[period_size * 4];
	
	while (!is.eof()) {
		while ((int)snd_pcm_avail_update(handle) > (int)period_size) {
			memset(buffer, 0, sizeof(buffer));
			is.read(buffer, period_size*4);
			rc = snd_pcm_writei(handle, buffer, period_size);
			printf("Write return = %d (of buffer %d, in period %d). Now availible %d\n", rc, frames_per_buffer, (int)period_size, (int)snd_pcm_avail_update(handle));
			if ((int)snd_pcm_avail_update(handle) > (int)frames_per_buffer) {
				snd_pcm_prepare(handle);
			}
			if (rc < 0) {
				if (rc == -EPIPE) {
					/* EPIPE means underrun */
					fprintf(stderr, "underrun occurred, buffer = %d\n", (int)period_size);
					//snd_pcm_drain(handle);
					snd_pcm_prepare(handle);
				}
				fprintf(stderr, "error from writei: %s\n",snd_strerror(rc));
			}
		}
		if(snd_pcm_wait(handle, 1000))
			printf("!");
	}
	snd_pcm_drain(handle);
	snd_pcm_close(handle);

	return 0;
}

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

printf(«…\n») делает fflush(stdout) на каждой итерации цикла…

Да, printf() не нужен. Разве не очевидно, что из окончательной версии отладочные принты нужно вычистить?

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

а как stdout связан с asoundlib? Тем не менее, если убрать все принтфы underrun все равно происходит.... Пусть совсем не часто, но звуки воспроизводятся не стабильно...

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

Вызов printf блокирующий. Если без него всё равно подтормаживания, значит нужно смотреть, почему ещё буффер не заполняется достаточно быстро.

anonymous
()

У меня в давнишнем проекте примерно вот такой код восстановления был:

if (err == -EPIPE) {
	err = snd_pcm_prepare(handle);
	if (err < 0) {
		log error;
	}
} else if (err == -ESTRPIPE) {
	while ((err = snd_pcm_resume(handle)) == -EAGAIN)
		usleep(10 * 1000);
	if (err < 0) {
		err = snd_pcm_prepare(handle);
		if (err < 0)
			log_error;
	}
} else {
	log error;
}
И никаких printf, recovery должно работать со скоростью удара серпом.

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

Не знаю, куда уж быстрее... работаю в runlevel 3 в openSuse 11.4. На виртуальной VMware. Ничего хитрого не стоит. Неужели, дело в производительности..? Хост Intel Core2Quad 2.66x4, 8Gb ram. Вообще чрезвычайно странно.

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

Вообще ничего не работает) на выходе только щелкания... А вы бы не могли привести немного больше кода, захватив тот кусок где происходит запись в буфер альсы?

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

Благодарю, за ссылку. Может быть перекочую на эту библиотеку. Но все таки, хотелось использовать более приближенный к аппаратной части путь. А Кто-нибудь может проверить мой код? он будет у кого-нибудь работать?

Как же люди пишут/переносят игры, они же наверняка должны пользоваться alsa||oss. И не возникает проблем)

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

Этот код идёт сразу после writei, в err - результат writei.

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

А Кто-нибудь может проверить мой код? он будет у кого-нибудь работать?

Попробуй тот кусок, что я запостил. Работает на относительно дохлом ARM'e, только принты вычисти

AptGet ★★★
()

Взято из моего старого проекта, тут опущенны некоторые детали, т.к. в оригинале много лишних для понимания вещей, вроде опционального direct rendering'а.

// skipped
// ....
        int pos = 0;
        int framesToWrite = bufferSize_;

        while(framesToWrite) {
            error = snd_pcm_writei(pcm_, &buffer_[pos], bufferSize_);
            if(error == -EAGAIN)
                continue;

            if(error < 0) {
                if((error = xrunRecovery(error)) < 0)
                    return false;

                break;
            }

            pos += error * bytesPerFrame_;
            framesToWrite -= error;
        }

// skipped
// ....


int AlsaDevice::xrunRecovery(int error)
{
    if(error == -EPIPE) {
        if((error = snd_pcm_prepare(pcm_) < 0))
            return 0;
    }
    else if(error == -ESTRPIPE) {
        while((error = snd_pcm_resume(pcm_)) == -EAGAIN)
            sleep(1);	// wait until the suspend flag is released

        if(error < 0)
            snd_pcm_prepare(pcm_);

        return 0;
    }

    return error;
}

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

Да, действительно... после того как убрал абсолютно все принты начало работать нормально. Однако, snd_pcm_recover тоже портит буфер, когда в параметрах указано выдача сообщений о результате восстановления.

Спасибо всем, кто откликнулся!

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

Теоретический вопрос: а если создать поток наследник с управлением альсы, будет ли дочерний sdtout влиять на него? или дескрипторы sdtout полностью независимы?

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