본문 바로가기
IT/programming

[C/C++] ALSA PCM capture(recoding) 소스 코드 공유

by 어느해겨울 2021. 12. 28.

ALSA PCM capture, ALSA recoding

https://github.com/muabow/home/tree/main/src/cpp/pcm_capture

 

GitHub - muabow/home: C/C++, PHP, GO source/library

C/C++, PHP, GO source/library. Contribute to muabow/home development by creating an account on GitHub.

github.com

 

Linux ALSA driver를 이용한 audio capture 프로그램 샘플을 작성하였다.

 

코드 내에 주어진 파라미터 대로 오디오를 읽어 capture event handler를 통해 pcm raw 파일로 저장한다.

동작을 위한 내용은 README.md 를 참고하면 되고,

capture 파라미터의 수정은 src/main.cpp 파일을 참고하면 된다.

 

C++ Class로 구성하여서 단일 또는 복수개의 instance를 생성하여 N 채널 입력의 처리도 가능하다.

코드와 동작 내용을 포스팅하려 했지만 코드 블록을 썼음에도 너무 길어져서 위의 git repository를 공유한다.

 

아래는 PCM_CaptureHandler class 내 ALSA parameter를 세팅하는 set_pcm_driver method이다.

ALSA 제어는 어떠한 포맷의 데이터를 어떻게 capture interrupt/playback feeding의 빈도를 맞추어 읽고 쓰는지가 중요하기 때문에 세팅 부분이 가장 중요하다 볼 수 있다.

 

bool PCM_CaptureHandler::set_pcm_driver(void) {
	int 	err;

	snd_pcm_hw_params_t *hw_params = NULL;
	snd_pcm_hw_params_alloca(&hw_params);

	snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE;
	snd_pcm_stream_t stream = SND_PCM_STREAM_CAPTURE;

	if( this->t_pcm_handler != NULL ) {
		this->print_debug_info("set_pcm_driver() resetting capture parameter\n");
		snd_pcm_drop(this->t_pcm_handler);
		snd_pcm_drain(this->t_pcm_handler);
		snd_pcm_close(this->t_pcm_handler);

		this->t_pcm_handler = NULL;
	}

	if( (err = snd_pcm_open(&this->t_pcm_handler, this->device_name, stream, 0)) < 0 ) {
		this->print_debug_info("set_pcm_driver() cannot open audio device [%s] : %s\n",this->device_name , snd_strerror(err));

		return false;
	}

	if( (err = snd_pcm_hw_params_any(this->t_pcm_handler, hw_params)) < 0 ) {
		this->print_debug_info("set_pcm_driver() cannot initialize hardware parameter structure : %s\n", snd_strerror(err));

		return false;
	}

	if( (err = snd_pcm_hw_params_set_access(this->t_pcm_handler, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0 ) {
		this->print_debug_info("set_pcm_driver() cannot set access type : %s\n", snd_strerror(err));

		return false;
	}

	if( (err = snd_pcm_hw_params_set_channels(this->t_pcm_handler, hw_params, this->channels)) < 0 ) {
		this->print_debug_info("set_pcm_driver() cannot set channel count : %s\n", snd_strerror(err));
		this->print_debug_info("set_pcm_driver() near set channel..\n");

		return false;
	}

	if( (err = snd_pcm_hw_params_set_format(this->t_pcm_handler, hw_params, format)) < 0 ) {
		this->print_debug_info("set_pcm_driver() cannot set sample format : %s\n", snd_strerror(err));

		return false; 
	}

	if( (err = snd_pcm_hw_params_set_rate(this->t_pcm_handler, hw_params, this->sample_rate, 0) ) < 0) {
	   this->print_debug_info("set_pcm_driver() cannot set near sample rate : %s\n", snd_strerror(err));

	   return false;
	}


	if( (err = snd_pcm_hw_params_set_periods(this->t_pcm_handler, hw_params, this->size_pcm_periods, 0)) < 0) {
		this->print_debug_info("set_pcm_driver() error setting periods : %s\n", snd_strerror(err));

		return false;
	}

	if( (err = snd_pcm_nonblock(this->t_pcm_handler, 0)) < 0 ) {
		this->print_debug_info("set_pcm_driver() nonblock failed : %s\n", snd_strerror(err));

		return false;
	}

	snd_pcm_uframes_t t_buffer_size = (this->chunk_size / this->channels * this->size_pcm_periods) >> 2;
	if( (err = snd_pcm_hw_params_set_buffer_size_near(this->t_pcm_handler, hw_params, &t_buffer_size)) < 0 ) {
		this->print_debug_info("set_pcm_driver() buffer size failed : %s\n", snd_strerror(err));
		
		return false;
	}

	if( (err = snd_pcm_hw_params(this->t_pcm_handler, hw_params)) < 0 ) {
		this->print_debug_info("set_pcm_driver() cannot set parameters : %s\n", snd_strerror(err));

		return false;
	}

	if( (err = snd_pcm_prepare(this->t_pcm_handler)) < 0 ) { 
		this->print_debug_info("set_pcm_driver() cannot prepare audio interface for use : %s\n", snd_strerror(err));

		return false;
	}

	snd_pcm_hw_params_get_channels(hw_params, 	 &this->channels);
	snd_pcm_hw_params_get_buffer_time(hw_params, &this->buffer_size, 0);
	snd_pcm_hw_params_get_period_time(hw_params, &this->period_size, 0);
	
	snd_pcm_uframes_t t_info_buffer_size, t_info_period_size;
	snd_pcm_hw_params_get_buffer_size(hw_params, &t_info_buffer_size);
	snd_pcm_hw_params_get_period_size(hw_params, &t_info_period_size, 0);

	this->frame_bytes 	= snd_pcm_frames_to_bytes(this->t_pcm_handler, 1);
	this->frame_latency = (double)(this->chunk_size / this->channels) / this->frame_bytes / this->sample_rate;
	
	this->print_debug_info("set_pcm_driver() h/w params set information..\n");
	this->print_debug_info("set_pcm_driver() h/w params set - sample rate     : [%d]\n", this->sample_rate);
	this->print_debug_info("set_pcm_driver() h/w params set - channels        : [%d]\n", this->channels);
	this->print_debug_info("set_pcm_driver() h/w params set - pcm buffer time : [%d]\n", this->buffer_size);
	this->print_debug_info("set_pcm_driver() h/w params set - pcm period time : [%d]\n", this->period_size);
	this->print_debug_info("set_pcm_driver() h/w params set - pcm buffer size : [%d]\n", t_info_buffer_size);
	this->print_debug_info("set_pcm_driver() h/w params set - pcm period size : [%d]\n", t_info_period_size);
	this->print_debug_info("set_pcm_driver() h/w params set - pcm frame bytes : [%d]\n", this->frame_bytes);
	this->print_debug_info("set_pcm_driver() h/w params set - frame latency   : [%lf]\n",this->frame_latency);
	this->print_debug_info("set_pcm_driver() h/w params set - pcm width       : [%d]\n", snd_pcm_format_width(SND_PCM_FORMAT_S16_LE));

	return true;
}

 

ALSA와 관련된 정보 및 파라미터는 아래에서 자세히 확인할 수 있다.

https://www.alsa-project.org/wiki/Main_Page

 

AlsaProject

 

www.alsa-project.org

 

 

댓글