mirror of
				https://github.com/alsa-project/alsa-lib.git
				synced 2025-10-29 05:40:25 -04:00 
			
		
		
		
	Moved ring buffer pointers and added a mechanism to transfer them via shm
This commit is contained in:
		
							parent
							
								
									f063381430
								
							
						
					
					
						commit
						c941c548f8
					
				
					 26 changed files with 469 additions and 171 deletions
				
			
		|  | @ -271,6 +271,44 @@ static int pcm_handler(waiter_t *waiter, unsigned short events) | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
|  | static void pcm_shm_hw_ptr_changed(snd_pcm_t *pcm, snd_pcm_t *src ATTRIBUTE_UNUSED) | ||||||
|  | { | ||||||
|  | 	client_t *client = pcm->hw.private_data; | ||||||
|  | 	volatile snd_pcm_shm_ctrl_t *ctrl = client->transport.shm.ctrl; | ||||||
|  | 	snd_pcm_t *loop; | ||||||
|  | 
 | ||||||
|  | 	ctrl->hw.changed = 1; | ||||||
|  | 	if (pcm->hw.fd >= 0) { | ||||||
|  | 		ctrl->hw.use_mmap = 1; | ||||||
|  | 		ctrl->hw.offset = pcm->hw.offset; | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	ctrl->hw.use_mmap = 0; | ||||||
|  | 	ctrl->hw.ptr = pcm->hw.ptr ? *pcm->hw.ptr : 0; | ||||||
|  | 	for (loop = pcm->hw.master; loop; loop = loop->hw.master) | ||||||
|  | 		loop->hw.ptr = &ctrl->hw.ptr; | ||||||
|  | 	pcm->hw.ptr = &ctrl->hw.ptr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void pcm_shm_appl_ptr_changed(snd_pcm_t *pcm, snd_pcm_t *src ATTRIBUTE_UNUSED) | ||||||
|  | { | ||||||
|  | 	client_t *client = pcm->appl.private_data; | ||||||
|  | 	volatile snd_pcm_shm_ctrl_t *ctrl = client->transport.shm.ctrl; | ||||||
|  | 	snd_pcm_t *loop; | ||||||
|  | 
 | ||||||
|  | 	ctrl->appl.changed = 1; | ||||||
|  | 	if (pcm->appl.fd >= 0) { | ||||||
|  | 		ctrl->appl.use_mmap = 1; | ||||||
|  | 		ctrl->appl.offset = pcm->appl.offset; | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	ctrl->appl.use_mmap = 0; | ||||||
|  | 	ctrl->appl.ptr = pcm->appl.ptr ? *pcm->appl.ptr : 0; | ||||||
|  | 	for (loop = pcm->appl.master; loop; loop = loop->appl.master) | ||||||
|  | 		loop->appl.ptr = &ctrl->appl.ptr; | ||||||
|  | 	pcm->appl.ptr = &ctrl->appl.ptr; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static int pcm_shm_open(client_t *client, int *cookie) | static int pcm_shm_open(client_t *client, int *cookie) | ||||||
| { | { | ||||||
| 	int shmid; | 	int shmid; | ||||||
|  | @ -282,6 +320,10 @@ static int pcm_shm_open(client_t *client, int *cookie) | ||||||
| 		return err; | 		return err; | ||||||
| 	client->device.pcm.handle = pcm; | 	client->device.pcm.handle = pcm; | ||||||
| 	client->device.pcm.fd = _snd_pcm_poll_descriptor(pcm); | 	client->device.pcm.fd = _snd_pcm_poll_descriptor(pcm); | ||||||
|  | 	pcm->hw.private_data = client; | ||||||
|  | 	pcm->hw.changed = pcm_shm_hw_ptr_changed; | ||||||
|  | 	pcm->appl.private_data = client; | ||||||
|  | 	pcm->appl.changed = pcm_shm_appl_ptr_changed; | ||||||
| 
 | 
 | ||||||
| 	shmid = shmget(IPC_PRIVATE, PCM_SHM_SIZE, 0666); | 	shmid = shmget(IPC_PRIVATE, PCM_SHM_SIZE, 0666); | ||||||
| 	if (shmid < 0) { | 	if (shmid < 0) { | ||||||
|  | @ -361,6 +403,13 @@ static int shm_ack_fd(client_t *client, int fd) | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static int shm_rbptr_fd(client_t *client, snd_pcm_rbptr_t *rbptr) | ||||||
|  | { | ||||||
|  | 	if (rbptr->fd < 0) | ||||||
|  | 		return -EINVAL; | ||||||
|  | 	return shm_ack_fd(client, rbptr->fd); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static void async_handler(snd_async_handler_t *handler) | static void async_handler(snd_async_handler_t *handler) | ||||||
| { | { | ||||||
| 	client_t *client = snd_async_handler_get_callback_private(handler); | 	client_t *client = snd_async_handler_get_callback_private(handler); | ||||||
|  | @ -424,22 +473,15 @@ static int pcm_shm_cmd(client_t *client) | ||||||
| 		break; | 		break; | ||||||
| 	case SND_PCM_IOCTL_AVAIL_UPDATE: | 	case SND_PCM_IOCTL_AVAIL_UPDATE: | ||||||
| 		ctrl->result = snd_pcm_avail_update(pcm); | 		ctrl->result = snd_pcm_avail_update(pcm); | ||||||
| 		ctrl->hw_ptr = *pcm->hw_ptr; |  | ||||||
| 		break; | 		break; | ||||||
| 	case SNDRV_PCM_IOCTL_PREPARE: | 	case SNDRV_PCM_IOCTL_PREPARE: | ||||||
| 		ctrl->result = snd_pcm_prepare(pcm); | 		ctrl->result = snd_pcm_prepare(pcm); | ||||||
| 		ctrl->appl_ptr = *pcm->appl_ptr; |  | ||||||
| 		ctrl->hw_ptr = *pcm->hw_ptr; |  | ||||||
| 		break; | 		break; | ||||||
| 	case SNDRV_PCM_IOCTL_RESET: | 	case SNDRV_PCM_IOCTL_RESET: | ||||||
| 		ctrl->result = snd_pcm_reset(pcm); | 		ctrl->result = snd_pcm_reset(pcm); | ||||||
| 		ctrl->appl_ptr = *pcm->appl_ptr; |  | ||||||
| 		ctrl->hw_ptr = *pcm->hw_ptr; |  | ||||||
| 		break; | 		break; | ||||||
| 	case SNDRV_PCM_IOCTL_START: | 	case SNDRV_PCM_IOCTL_START: | ||||||
| 		ctrl->result = snd_pcm_start(pcm); | 		ctrl->result = snd_pcm_start(pcm); | ||||||
| 		ctrl->appl_ptr = *pcm->appl_ptr; |  | ||||||
| 		ctrl->hw_ptr = *pcm->hw_ptr; |  | ||||||
| 		break; | 		break; | ||||||
| 	case SNDRV_PCM_IOCTL_DRAIN: | 	case SNDRV_PCM_IOCTL_DRAIN: | ||||||
| 		ctrl->result = snd_pcm_drain(pcm); | 		ctrl->result = snd_pcm_drain(pcm); | ||||||
|  | @ -458,7 +500,6 @@ static int pcm_shm_cmd(client_t *client) | ||||||
| 		break; | 		break; | ||||||
| 	case SNDRV_PCM_IOCTL_REWIND: | 	case SNDRV_PCM_IOCTL_REWIND: | ||||||
| 		ctrl->result = snd_pcm_rewind(pcm, ctrl->u.rewind.frames); | 		ctrl->result = snd_pcm_rewind(pcm, ctrl->u.rewind.frames); | ||||||
| 		ctrl->appl_ptr = *pcm->appl_ptr; |  | ||||||
| 		break; | 		break; | ||||||
| 	case SNDRV_PCM_IOCTL_LINK: | 	case SNDRV_PCM_IOCTL_LINK: | ||||||
| 	{ | 	{ | ||||||
|  | @ -485,7 +526,6 @@ static int pcm_shm_cmd(client_t *client) | ||||||
| 		ctrl->result = snd_pcm_mmap_commit(pcm, | 		ctrl->result = snd_pcm_mmap_commit(pcm, | ||||||
| 						   ctrl->u.mmap_commit.offset, | 						   ctrl->u.mmap_commit.offset, | ||||||
| 						   ctrl->u.mmap_commit.frames); | 						   ctrl->u.mmap_commit.frames); | ||||||
| 		ctrl->appl_ptr = *pcm->appl_ptr; |  | ||||||
| 		break; | 		break; | ||||||
| 	case SND_PCM_IOCTL_POLL_DESCRIPTOR: | 	case SND_PCM_IOCTL_POLL_DESCRIPTOR: | ||||||
| 		ctrl->result = 0; | 		ctrl->result = 0; | ||||||
|  | @ -493,6 +533,10 @@ static int pcm_shm_cmd(client_t *client) | ||||||
| 	case SND_PCM_IOCTL_CLOSE: | 	case SND_PCM_IOCTL_CLOSE: | ||||||
| 		client->ops->close(client); | 		client->ops->close(client); | ||||||
| 		break; | 		break; | ||||||
|  | 	case SND_PCM_IOCTL_HW_PTR_FD: | ||||||
|  | 		return shm_rbptr_fd(client, &pcm->hw); | ||||||
|  | 	case SND_PCM_IOCTL_APPL_PTR_FD: | ||||||
|  | 		return shm_rbptr_fd(client, &pcm->appl); | ||||||
| 	default: | 	default: | ||||||
| 		ERROR("Bogus cmd: %x", ctrl->cmd); | 		ERROR("Bogus cmd: %x", ctrl->cmd); | ||||||
| 		ctrl->result = -ENOSYS; | 		ctrl->result = -ENOSYS; | ||||||
|  |  | ||||||
|  | @ -47,12 +47,21 @@ typedef enum _snd_transport_type { | ||||||
| #define SND_PCM_IOCTL_ASYNC		_IO ('A', 0xf6) | #define SND_PCM_IOCTL_ASYNC		_IO ('A', 0xf6) | ||||||
| #define SND_PCM_IOCTL_CLOSE		_IO ('A', 0xf7) | #define SND_PCM_IOCTL_CLOSE		_IO ('A', 0xf7) | ||||||
| #define SND_PCM_IOCTL_POLL_DESCRIPTOR	_IO ('A', 0xf8) | #define SND_PCM_IOCTL_POLL_DESCRIPTOR	_IO ('A', 0xf8) | ||||||
|  | #define SND_PCM_IOCTL_HW_PTR_FD		_IO ('A', 0xf9) | ||||||
|  | #define SND_PCM_IOCTL_APPL_PTR_FD	_IO ('A', 0xfa) | ||||||
|  | 
 | ||||||
|  | typedef struct { | ||||||
|  | 	snd_pcm_uframes_t ptr; | ||||||
|  | 	int use_mmap; | ||||||
|  | 	off_t offset;		/* for mmap */ | ||||||
|  | 	int changed; | ||||||
|  | } snd_pcm_shm_rbptr_t; | ||||||
| 
 | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
| 	long result; | 	long result; | ||||||
| 	int cmd; | 	int cmd; | ||||||
| 	snd_pcm_uframes_t hw_ptr; | 	snd_pcm_shm_rbptr_t hw; | ||||||
| 	snd_pcm_uframes_t appl_ptr; | 	snd_pcm_shm_rbptr_t appl; | ||||||
| 	union { | 	union { | ||||||
| 		struct { | 		struct { | ||||||
| 			int sig; | 			int sig; | ||||||
|  | @ -80,6 +89,11 @@ typedef struct { | ||||||
| 			snd_pcm_uframes_t offset; | 			snd_pcm_uframes_t offset; | ||||||
| 			snd_pcm_uframes_t frames; | 			snd_pcm_uframes_t frames; | ||||||
| 		} mmap_commit; | 		} mmap_commit; | ||||||
|  | 		struct { | ||||||
|  | 			char use_mmap; | ||||||
|  | 			int shmid; | ||||||
|  | 			off_t offset; | ||||||
|  | 		} rbptr; | ||||||
| 	} u; | 	} u; | ||||||
| 	char data[0]; | 	char data[0]; | ||||||
| } snd_pcm_shm_ctrl_t; | } snd_pcm_shm_ctrl_t; | ||||||
|  |  | ||||||
|  | @ -139,6 +139,7 @@ typedef enum _snd_set_mode { | ||||||
| 
 | 
 | ||||||
| size_t page_align(size_t size); | size_t page_align(size_t size); | ||||||
| size_t page_size(void); | size_t page_size(void); | ||||||
|  | size_t page_ptr(size_t object_offset, size_t object_size, size_t *offset, size_t *mmap_offset); | ||||||
| 
 | 
 | ||||||
| int safe_strtol(const char *str, long *val); | int safe_strtol(const char *str, long *val); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -827,8 +827,8 @@ int snd_pcm_format_set_silence(snd_pcm_format_t format, void *buf, unsigned int | ||||||
| 
 | 
 | ||||||
| snd_pcm_sframes_t snd_pcm_bytes_to_frames(snd_pcm_t *pcm, ssize_t bytes); | snd_pcm_sframes_t snd_pcm_bytes_to_frames(snd_pcm_t *pcm, ssize_t bytes); | ||||||
| ssize_t snd_pcm_frames_to_bytes(snd_pcm_t *pcm, snd_pcm_sframes_t frames); | ssize_t snd_pcm_frames_to_bytes(snd_pcm_t *pcm, snd_pcm_sframes_t frames); | ||||||
| int snd_pcm_bytes_to_samples(snd_pcm_t *pcm, ssize_t bytes); | long snd_pcm_bytes_to_samples(snd_pcm_t *pcm, ssize_t bytes); | ||||||
| ssize_t snd_pcm_samples_to_bytes(snd_pcm_t *pcm, int samples); | ssize_t snd_pcm_samples_to_bytes(snd_pcm_t *pcm, long samples); | ||||||
| 
 | 
 | ||||||
| int snd_pcm_area_silence(const snd_pcm_channel_area_t *dst_channel, snd_pcm_uframes_t dst_offset, | int snd_pcm_area_silence(const snd_pcm_channel_area_t *dst_channel, snd_pcm_uframes_t dst_offset, | ||||||
| 			 unsigned int samples, snd_pcm_format_t format); | 			 unsigned int samples, snd_pcm_format_t format); | ||||||
|  |  | ||||||
							
								
								
									
										117
									
								
								src/pcm/pcm.c
									
										
									
									
									
								
							
							
						
						
									
										117
									
								
								src/pcm/pcm.c
									
										
									
									
									
								
							|  | @ -1528,7 +1528,7 @@ ssize_t snd_pcm_frames_to_bytes(snd_pcm_t *pcm, snd_pcm_sframes_t frames) | ||||||
|  * \param bytes quantity in bytes |  * \param bytes quantity in bytes | ||||||
|  * \return quantity expressed in samples |  * \return quantity expressed in samples | ||||||
|  */ |  */ | ||||||
| int snd_pcm_bytes_to_samples(snd_pcm_t *pcm, ssize_t bytes) | long snd_pcm_bytes_to_samples(snd_pcm_t *pcm, ssize_t bytes) | ||||||
| { | { | ||||||
| 	assert(pcm); | 	assert(pcm); | ||||||
| 	assert(pcm->setup); | 	assert(pcm->setup); | ||||||
|  | @ -1541,7 +1541,7 @@ int snd_pcm_bytes_to_samples(snd_pcm_t *pcm, ssize_t bytes) | ||||||
|  * \param samples quantity in samples |  * \param samples quantity in samples | ||||||
|  * \return quantity expressed in bytes |  * \return quantity expressed in bytes | ||||||
|  */ |  */ | ||||||
| ssize_t snd_pcm_samples_to_bytes(snd_pcm_t *pcm, int samples) | ssize_t snd_pcm_samples_to_bytes(snd_pcm_t *pcm, long samples) | ||||||
| { | { | ||||||
| 	assert(pcm); | 	assert(pcm); | ||||||
| 	assert(pcm->setup); | 	assert(pcm->setup); | ||||||
|  | @ -5131,7 +5131,7 @@ int snd_pcm_mmap_begin(snd_pcm_t *pcm, | ||||||
| 		*areas = pcm->stopped_areas; | 		*areas = pcm->stopped_areas; | ||||||
| 	else | 	else | ||||||
| 		*areas = pcm->running_areas; | 		*areas = pcm->running_areas; | ||||||
| 	*offset = *pcm->appl_ptr % pcm->buffer_size; | 	*offset = *pcm->appl.ptr % pcm->buffer_size; | ||||||
| 	cont = pcm->buffer_size - *offset; | 	cont = pcm->buffer_size - *offset; | ||||||
| 	f = *frames; | 	f = *frames; | ||||||
| 	avail = snd_pcm_mmap_avail(pcm); | 	avail = snd_pcm_mmap_avail(pcm); | ||||||
|  | @ -5202,7 +5202,7 @@ snd_pcm_sframes_t snd_pcm_mmap_commit(snd_pcm_t *pcm, | ||||||
| 				      snd_pcm_uframes_t frames) | 				      snd_pcm_uframes_t frames) | ||||||
| { | { | ||||||
| 	assert(pcm); | 	assert(pcm); | ||||||
| 	assert(offset == *pcm->appl_ptr % pcm->buffer_size); | 	assert(offset == *pcm->appl.ptr % pcm->buffer_size); | ||||||
| 	assert(frames <= snd_pcm_mmap_avail(pcm)); | 	assert(frames <= snd_pcm_mmap_avail(pcm)); | ||||||
| 	return pcm->fast_ops->mmap_commit(pcm->fast_op_arg, offset, frames); | 	return pcm->fast_ops->mmap_commit(pcm->fast_op_arg, offset, frames); | ||||||
| } | } | ||||||
|  | @ -5421,7 +5421,7 @@ snd_pcm_sframes_t snd_pcm_write_areas(snd_pcm_t *pcm, const snd_pcm_channel_area | ||||||
| 
 | 
 | ||||||
| snd_pcm_uframes_t _snd_pcm_mmap_hw_ptr(snd_pcm_t *pcm) | snd_pcm_uframes_t _snd_pcm_mmap_hw_ptr(snd_pcm_t *pcm) | ||||||
| { | { | ||||||
| 	return *pcm->hw_ptr; | 	return *pcm->hw.ptr; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| snd_pcm_uframes_t _snd_pcm_boundary(snd_pcm_t *pcm) | snd_pcm_uframes_t _snd_pcm_boundary(snd_pcm_t *pcm) | ||||||
|  | @ -5581,4 +5581,111 @@ int snd_pcm_conf_generic_id(const char *id) | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void snd_pcm_set_ptr(snd_pcm_t *pcm, snd_pcm_rbptr_t *rbptr, | ||||||
|  | 			    volatile snd_pcm_uframes_t *hw_ptr, int fd, off_t offset) | ||||||
|  | { | ||||||
|  | 	rbptr->master = NULL;	/* I'm master */ | ||||||
|  | 	rbptr->ptr = hw_ptr; | ||||||
|  | 	rbptr->fd = fd; | ||||||
|  | 	rbptr->offset = offset; | ||||||
|  | 	if (rbptr->changed) | ||||||
|  | 		rbptr->changed(pcm, NULL); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void snd_pcm_set_hw_ptr(snd_pcm_t *pcm, volatile snd_pcm_uframes_t *hw_ptr, int fd, off_t offset) | ||||||
|  | { | ||||||
|  | 	assert(pcm); | ||||||
|  | 	assert(hw_ptr); | ||||||
|  | 	snd_pcm_set_ptr(pcm, &pcm->hw, hw_ptr, fd, offset); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void snd_pcm_set_appl_ptr(snd_pcm_t *pcm, volatile snd_pcm_uframes_t *appl_ptr, int fd, off_t offset) | ||||||
|  | { | ||||||
|  | 	assert(pcm); | ||||||
|  | 	assert(appl_ptr); | ||||||
|  | 	snd_pcm_set_ptr(pcm, &pcm->appl, appl_ptr, fd, offset); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void snd_pcm_link_ptr(snd_pcm_t *pcm, snd_pcm_rbptr_t *pcm_rbptr, | ||||||
|  | 			     snd_pcm_t *slave, snd_pcm_rbptr_t *slave_rbptr) | ||||||
|  | { | ||||||
|  | 	snd_pcm_t **a; | ||||||
|  | 	int idx; | ||||||
|  | 	 | ||||||
|  | 	a = slave_rbptr->link_dst; | ||||||
|  | 	for (idx = 0; idx < slave_rbptr->link_dst_count; idx++) | ||||||
|  | 		if (a[idx] == NULL) { | ||||||
|  | 			a[idx] = pcm; | ||||||
|  | 			goto __found_free_place; | ||||||
|  | 		} | ||||||
|  | 	a = realloc(a, sizeof(snd_pcm_t *) * (slave_rbptr->link_dst_count + 1)); | ||||||
|  | 	if (a == NULL) { | ||||||
|  | 		pcm_rbptr->ptr = NULL; | ||||||
|  | 		pcm_rbptr->fd = -1; | ||||||
|  | 		pcm_rbptr->offset = 0UL; | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	a[slave_rbptr->link_dst_count++] = pcm; | ||||||
|  |       __found_free_place: | ||||||
|  | 	pcm_rbptr->master = slave_rbptr->master ? slave_rbptr->master : slave; | ||||||
|  | 	pcm_rbptr->ptr = slave_rbptr->ptr; | ||||||
|  | 	pcm_rbptr->fd = slave_rbptr->fd; | ||||||
|  | 	pcm_rbptr->offset = slave_rbptr->offset; | ||||||
|  | 	slave_rbptr->link_dst = a; | ||||||
|  | 	if (pcm_rbptr->changed) | ||||||
|  | 		pcm_rbptr->changed(pcm, slave); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void snd_pcm_unlink_ptr(snd_pcm_t *pcm, snd_pcm_rbptr_t *pcm_rbptr, | ||||||
|  | 			       snd_pcm_t *slave, snd_pcm_rbptr_t *slave_rbptr) | ||||||
|  | { | ||||||
|  | 	snd_pcm_t **a; | ||||||
|  | 	int idx; | ||||||
|  | 
 | ||||||
|  | 	a = slave_rbptr->link_dst; | ||||||
|  | 	for (idx = 0; idx < slave_rbptr->link_dst_count; idx++) | ||||||
|  | 		if (a[idx] == pcm) { | ||||||
|  | 			a[idx] = NULL; | ||||||
|  | 			goto __found; | ||||||
|  | 		} | ||||||
|  | 	assert(0); | ||||||
|  | 	return; | ||||||
|  | 
 | ||||||
|  |       __found: | ||||||
|  |       	pcm_rbptr->master = NULL; | ||||||
|  | 	pcm_rbptr->ptr = NULL; | ||||||
|  | 	pcm_rbptr->fd = -1; | ||||||
|  | 	pcm_rbptr->offset = 0UL; | ||||||
|  | 	if (pcm_rbptr->changed) | ||||||
|  | 		pcm_rbptr->changed(pcm, slave); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void snd_pcm_link_hw_ptr(snd_pcm_t *pcm, snd_pcm_t *slave) | ||||||
|  | { | ||||||
|  | 	assert(pcm); | ||||||
|  | 	assert(slave); | ||||||
|  | 	snd_pcm_link_ptr(pcm, &pcm->hw, slave, &slave->hw); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void snd_pcm_link_appl_ptr(snd_pcm_t *pcm, snd_pcm_t *slave) | ||||||
|  | { | ||||||
|  | 	assert(pcm); | ||||||
|  | 	assert(slave); | ||||||
|  | 	snd_pcm_link_ptr(pcm, &pcm->appl, slave, &slave->appl); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void snd_pcm_unlink_hw_ptr(snd_pcm_t *pcm, snd_pcm_t *slave) | ||||||
|  | { | ||||||
|  | 	assert(pcm); | ||||||
|  | 	assert(slave); | ||||||
|  | 	snd_pcm_unlink_ptr(pcm, &pcm->hw, slave, &slave->hw); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void snd_pcm_unlink_appl_ptr(snd_pcm_t *pcm, snd_pcm_t *slave) | ||||||
|  | { | ||||||
|  | 	assert(pcm); | ||||||
|  | 	assert(slave); | ||||||
|  | 	snd_pcm_unlink_ptr(pcm, &pcm->appl, slave, &slave->appl); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | @ -569,8 +569,8 @@ int snd_pcm_adpcm_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sfor | ||||||
| 	pcm->fast_ops = &snd_pcm_plugin_fast_ops; | 	pcm->fast_ops = &snd_pcm_plugin_fast_ops; | ||||||
| 	pcm->private_data = adpcm; | 	pcm->private_data = adpcm; | ||||||
| 	pcm->poll_fd = slave->poll_fd; | 	pcm->poll_fd = slave->poll_fd; | ||||||
| 	pcm->hw_ptr = &adpcm->plug.hw_ptr; | 	snd_pcm_set_hw_ptr(pcm, &adpcm->plug.hw_ptr, -1, 0); | ||||||
| 	pcm->appl_ptr = &adpcm->plug.appl_ptr; | 	snd_pcm_set_appl_ptr(pcm, &adpcm->plug.appl_ptr, -1, 0); | ||||||
| 	*pcmp = pcm; | 	*pcmp = pcm; | ||||||
| 
 | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
|  |  | ||||||
|  | @ -442,8 +442,8 @@ int snd_pcm_alaw_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sform | ||||||
| 	pcm->fast_ops = &snd_pcm_plugin_fast_ops; | 	pcm->fast_ops = &snd_pcm_plugin_fast_ops; | ||||||
| 	pcm->private_data = alaw; | 	pcm->private_data = alaw; | ||||||
| 	pcm->poll_fd = slave->poll_fd; | 	pcm->poll_fd = slave->poll_fd; | ||||||
| 	pcm->hw_ptr = &alaw->plug.hw_ptr; | 	snd_pcm_set_hw_ptr(pcm, &alaw->plug.hw_ptr, -1, 0); | ||||||
| 	pcm->appl_ptr = &alaw->plug.appl_ptr; | 	snd_pcm_set_appl_ptr(pcm, &alaw->plug.appl_ptr, -1, 0); | ||||||
| 	*pcmp = pcm; | 	*pcmp = pcm; | ||||||
| 
 | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
|  |  | ||||||
|  | @ -205,8 +205,8 @@ int snd_pcm_copy_open(snd_pcm_t **pcmp, const char *name, snd_pcm_t *slave, int | ||||||
| 	pcm->fast_ops = &snd_pcm_plugin_fast_ops; | 	pcm->fast_ops = &snd_pcm_plugin_fast_ops; | ||||||
| 	pcm->private_data = copy; | 	pcm->private_data = copy; | ||||||
| 	pcm->poll_fd = slave->poll_fd; | 	pcm->poll_fd = slave->poll_fd; | ||||||
| 	pcm->hw_ptr = ©->plug.hw_ptr; | 	snd_pcm_set_hw_ptr(pcm, ©->plug.hw_ptr, -1, 0); | ||||||
| 	pcm->appl_ptr = ©->plug.appl_ptr; | 	snd_pcm_set_appl_ptr(pcm, ©->plug.appl_ptr, -1, 0); | ||||||
| 	*pcmp = pcm; | 	*pcmp = pcm; | ||||||
| 
 | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
|  |  | ||||||
|  | @ -485,8 +485,8 @@ int snd_pcm_file_open(snd_pcm_t **pcmp, const char *name, | ||||||
| 	pcm->fast_ops = &snd_pcm_file_fast_ops; | 	pcm->fast_ops = &snd_pcm_file_fast_ops; | ||||||
| 	pcm->private_data = file; | 	pcm->private_data = file; | ||||||
| 	pcm->poll_fd = slave->poll_fd; | 	pcm->poll_fd = slave->poll_fd; | ||||||
| 	pcm->hw_ptr = slave->hw_ptr; | 	snd_pcm_link_hw_ptr(pcm, slave); | ||||||
| 	pcm->appl_ptr = slave->appl_ptr; | 	snd_pcm_link_appl_ptr(pcm, slave); | ||||||
| 	*pcmp = pcm; | 	*pcmp = pcm; | ||||||
| 
 | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
|  |  | ||||||
|  | @ -344,8 +344,8 @@ int snd_pcm_hooks_open(snd_pcm_t **pcmp, const char *name, snd_pcm_t *slave, int | ||||||
| 	pcm->fast_ops = &snd_pcm_hooks_fast_ops; | 	pcm->fast_ops = &snd_pcm_hooks_fast_ops; | ||||||
| 	pcm->private_data = h; | 	pcm->private_data = h; | ||||||
| 	pcm->poll_fd = slave->poll_fd; | 	pcm->poll_fd = slave->poll_fd; | ||||||
| 	pcm->hw_ptr = slave->hw_ptr; | 	snd_pcm_link_hw_ptr(pcm, slave); | ||||||
| 	pcm->appl_ptr = slave->appl_ptr; | 	snd_pcm_link_appl_ptr(pcm, slave); | ||||||
| 	*pcmp = pcm; | 	*pcmp = pcm; | ||||||
| 
 | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
|  |  | ||||||
|  | @ -29,6 +29,7 @@ | ||||||
|    |    | ||||||
| #include <stdio.h> | #include <stdio.h> | ||||||
| #include <stdlib.h> | #include <stdlib.h> | ||||||
|  | #include <stddef.h> | ||||||
| #include <unistd.h> | #include <unistd.h> | ||||||
| #include <signal.h> | #include <signal.h> | ||||||
| #include <string.h> | #include <string.h> | ||||||
|  | @ -256,10 +257,10 @@ static int snd_pcm_hw_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params) | ||||||
| 		if (hw->mmap_shm) { | 		if (hw->mmap_shm) { | ||||||
| 			hw->shadow_appl_ptr = 1; | 			hw->shadow_appl_ptr = 1; | ||||||
| 			hw->appl_ptr = 0; | 			hw->appl_ptr = 0; | ||||||
| 			pcm->appl_ptr = &hw->appl_ptr; | 			snd_pcm_set_appl_ptr(pcm, &hw->appl_ptr, -1, 0); | ||||||
| 		} else { | 		} else { | ||||||
| 			hw->shadow_appl_ptr = 0; | 			hw->shadow_appl_ptr = 0; | ||||||
| 			pcm->appl_ptr = &hw->mmap_control->appl_ptr; | 			snd_pcm_set_appl_ptr(pcm, &hw->mmap_control->appl_ptr, hw->fd, SNDRV_PCM_MMAP_OFFSET_CONTROL); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return 0; | 	return 0; | ||||||
|  | @ -529,7 +530,7 @@ static int snd_pcm_hw_mmap_status(snd_pcm_t *pcm) | ||||||
| 		return -errno; | 		return -errno; | ||||||
| 	} | 	} | ||||||
| 	hw->mmap_status = ptr; | 	hw->mmap_status = ptr; | ||||||
| 	pcm->hw_ptr = &hw->mmap_status->hw_ptr; | 	snd_pcm_set_hw_ptr(pcm, &hw->mmap_status->hw_ptr, hw->fd, SNDRV_PCM_MMAP_OFFSET_STATUS + offsetof(struct sndrv_pcm_mmap_status, hw_ptr)); | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -545,7 +546,7 @@ static int snd_pcm_hw_mmap_control(snd_pcm_t *pcm) | ||||||
| 		return -errno; | 		return -errno; | ||||||
| 	} | 	} | ||||||
| 	hw->mmap_control = ptr; | 	hw->mmap_control = ptr; | ||||||
| 	pcm->appl_ptr = &hw->mmap_control->appl_ptr; | 	snd_pcm_set_appl_ptr(pcm, &hw->mmap_control->appl_ptr, hw->fd, SNDRV_PCM_MMAP_OFFSET_CONTROL); | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1173,8 +1173,8 @@ int snd_pcm_ladspa_open(snd_pcm_t **pcmp, const char *name, | ||||||
| 	pcm->fast_ops = &snd_pcm_plugin_fast_ops; | 	pcm->fast_ops = &snd_pcm_plugin_fast_ops; | ||||||
| 	pcm->private_data = ladspa; | 	pcm->private_data = ladspa; | ||||||
| 	pcm->poll_fd = slave->poll_fd; | 	pcm->poll_fd = slave->poll_fd; | ||||||
| 	pcm->hw_ptr = &ladspa->plug.hw_ptr; | 	snd_pcm_set_hw_ptr(pcm, &ladspa->plug.hw_ptr, -1, 0); | ||||||
| 	pcm->appl_ptr = &ladspa->plug.appl_ptr; | 	snd_pcm_set_appl_ptr(pcm, &ladspa->plug.appl_ptr, -1, 0); | ||||||
| 	*pcmp = pcm; | 	*pcmp = pcm; | ||||||
| 
 | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
|  |  | ||||||
|  | @ -407,8 +407,8 @@ int snd_pcm_lfloat_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sfo | ||||||
| 	pcm->fast_ops = &snd_pcm_plugin_fast_ops; | 	pcm->fast_ops = &snd_pcm_plugin_fast_ops; | ||||||
| 	pcm->private_data = lfloat; | 	pcm->private_data = lfloat; | ||||||
| 	pcm->poll_fd = slave->poll_fd; | 	pcm->poll_fd = slave->poll_fd; | ||||||
| 	pcm->hw_ptr = &lfloat->plug.hw_ptr; | 	snd_pcm_set_hw_ptr(pcm, &lfloat->plug.hw_ptr, -1, 0); | ||||||
| 	pcm->appl_ptr = &lfloat->plug.appl_ptr; | 	snd_pcm_set_appl_ptr(pcm, &lfloat->plug.appl_ptr, -1, 0); | ||||||
| 	*pcmp = pcm; | 	*pcmp = pcm; | ||||||
| 	 | 	 | ||||||
| 	return 0; | 	return 0; | ||||||
|  |  | ||||||
|  | @ -346,8 +346,8 @@ int snd_pcm_linear_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sfo | ||||||
| 	pcm->fast_ops = &snd_pcm_plugin_fast_ops; | 	pcm->fast_ops = &snd_pcm_plugin_fast_ops; | ||||||
| 	pcm->private_data = linear; | 	pcm->private_data = linear; | ||||||
| 	pcm->poll_fd = slave->poll_fd; | 	pcm->poll_fd = slave->poll_fd; | ||||||
| 	pcm->hw_ptr = &linear->plug.hw_ptr; | 	snd_pcm_set_hw_ptr(pcm, &linear->plug.hw_ptr, -1, 0); | ||||||
| 	pcm->appl_ptr = &linear->plug.appl_ptr; | 	snd_pcm_set_appl_ptr(pcm, &linear->plug.appl_ptr, -1, 0); | ||||||
| 	*pcmp = pcm; | 	*pcmp = pcm; | ||||||
| 
 | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
|  |  | ||||||
|  | @ -91,6 +91,17 @@ typedef enum sndrv_pcm_hw_param snd_pcm_hw_param_t; | ||||||
| /** device can do a kind of synchronized start */ | /** device can do a kind of synchronized start */ | ||||||
| #define SND_PCM_INFO_SYNC_START SNDRV_PCM_INFO_SYNC_START | #define SND_PCM_INFO_SYNC_START SNDRV_PCM_INFO_SYNC_START | ||||||
| 
 | 
 | ||||||
|  | typedef struct _snd_pcm_rbptr { | ||||||
|  | 	snd_pcm_t *master; | ||||||
|  | 	volatile snd_pcm_uframes_t *ptr; | ||||||
|  | 	int fd; | ||||||
|  | 	off_t offset; | ||||||
|  | 	int link_dst_count; | ||||||
|  | 	snd_pcm_t **link_dst; | ||||||
|  | 	void *private_data; | ||||||
|  | 	void (*changed)(snd_pcm_t *pcm, snd_pcm_t *src); | ||||||
|  | } snd_pcm_rbptr_t; | ||||||
|  | 
 | ||||||
| typedef struct _snd_pcm_channel_info { | typedef struct _snd_pcm_channel_info { | ||||||
| 	unsigned int channel; | 	unsigned int channel; | ||||||
| 	void *addr;			/* base address of channel samples */ | 	void *addr;			/* base address of channel samples */ | ||||||
|  | @ -178,9 +189,9 @@ struct _snd_pcm { | ||||||
| 	snd_pcm_uframes_t buffer_size; | 	snd_pcm_uframes_t buffer_size; | ||||||
| 	unsigned int sample_bits; | 	unsigned int sample_bits; | ||||||
| 	unsigned int frame_bits; | 	unsigned int frame_bits; | ||||||
| 	snd_pcm_uframes_t *appl_ptr; | 	snd_pcm_rbptr_t appl; | ||||||
|  | 	snd_pcm_rbptr_t hw; | ||||||
| 	snd_pcm_uframes_t min_align; | 	snd_pcm_uframes_t min_align; | ||||||
| 	volatile snd_pcm_uframes_t *hw_ptr; |  | ||||||
| 	int mmap_rw; | 	int mmap_rw; | ||||||
| 	snd_pcm_channel_info_t *mmap_channels; | 	snd_pcm_channel_info_t *mmap_channels; | ||||||
| 	snd_pcm_channel_area_t *running_areas; | 	snd_pcm_channel_area_t *running_areas; | ||||||
|  | @ -207,6 +218,12 @@ int snd_pcm_async(snd_pcm_t *pcm, int sig, pid_t pid); | ||||||
| int snd_pcm_mmap(snd_pcm_t *pcm); | int snd_pcm_mmap(snd_pcm_t *pcm); | ||||||
| int snd_pcm_munmap(snd_pcm_t *pcm); | int snd_pcm_munmap(snd_pcm_t *pcm); | ||||||
| int snd_pcm_mmap_ready(snd_pcm_t *pcm); | int snd_pcm_mmap_ready(snd_pcm_t *pcm); | ||||||
|  | void snd_pcm_set_hw_ptr(snd_pcm_t *pcm, volatile snd_pcm_uframes_t *hw_ptr, int fd, off_t offset); | ||||||
|  | void snd_pcm_set_appl_ptr(snd_pcm_t *pcm, volatile snd_pcm_uframes_t *appl_ptr, int fd, off_t offset); | ||||||
|  | void snd_pcm_link_hw_ptr(snd_pcm_t *pcm, snd_pcm_t *slave); | ||||||
|  | void snd_pcm_link_appl_ptr(snd_pcm_t *pcm, snd_pcm_t *slave); | ||||||
|  | void snd_pcm_unlink_hw_ptr(snd_pcm_t *pcm, snd_pcm_t *slave); | ||||||
|  | void snd_pcm_unlink_appl_ptr(snd_pcm_t *pcm, snd_pcm_t *slave); | ||||||
| snd_pcm_sframes_t snd_pcm_mmap_appl_ptr(snd_pcm_t *pcm, off_t offset); | snd_pcm_sframes_t snd_pcm_mmap_appl_ptr(snd_pcm_t *pcm, off_t offset); | ||||||
| void snd_pcm_mmap_appl_backward(snd_pcm_t *pcm, snd_pcm_uframes_t frames); | void snd_pcm_mmap_appl_backward(snd_pcm_t *pcm, snd_pcm_uframes_t frames); | ||||||
| void snd_pcm_mmap_appl_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames); | void snd_pcm_mmap_appl_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames); | ||||||
|  | @ -238,7 +255,7 @@ int _snd_pcm_poll_descriptor(snd_pcm_t *pcm); | ||||||
| static inline snd_pcm_uframes_t snd_pcm_mmap_playback_avail(snd_pcm_t *pcm) | static inline snd_pcm_uframes_t snd_pcm_mmap_playback_avail(snd_pcm_t *pcm) | ||||||
| { | { | ||||||
| 	snd_pcm_sframes_t avail; | 	snd_pcm_sframes_t avail; | ||||||
| 	avail = *pcm->hw_ptr + pcm->buffer_size - *pcm->appl_ptr; | 	avail = *pcm->hw.ptr + pcm->buffer_size - *pcm->appl.ptr; | ||||||
| 	if (avail < 0) | 	if (avail < 0) | ||||||
| 		avail += pcm->boundary; | 		avail += pcm->boundary; | ||||||
| 	else if ((snd_pcm_uframes_t) avail >= pcm->boundary) | 	else if ((snd_pcm_uframes_t) avail >= pcm->boundary) | ||||||
|  | @ -249,7 +266,7 @@ static inline snd_pcm_uframes_t snd_pcm_mmap_playback_avail(snd_pcm_t *pcm) | ||||||
| static inline snd_pcm_uframes_t snd_pcm_mmap_capture_avail(snd_pcm_t *pcm) | static inline snd_pcm_uframes_t snd_pcm_mmap_capture_avail(snd_pcm_t *pcm) | ||||||
| { | { | ||||||
| 	snd_pcm_sframes_t avail; | 	snd_pcm_sframes_t avail; | ||||||
| 	avail = *pcm->hw_ptr - *pcm->appl_ptr; | 	avail = *pcm->hw.ptr - *pcm->appl.ptr; | ||||||
| 	if (avail < 0) | 	if (avail < 0) | ||||||
| 		avail += pcm->boundary; | 		avail += pcm->boundary; | ||||||
| 	return avail; | 	return avail; | ||||||
|  | @ -276,7 +293,7 @@ static inline snd_pcm_sframes_t snd_pcm_mmap_capture_hw_avail(snd_pcm_t *pcm) | ||||||
| static inline snd_pcm_sframes_t snd_pcm_mmap_hw_avail(snd_pcm_t *pcm) | static inline snd_pcm_sframes_t snd_pcm_mmap_hw_avail(snd_pcm_t *pcm) | ||||||
| { | { | ||||||
| 	snd_pcm_sframes_t avail; | 	snd_pcm_sframes_t avail; | ||||||
| 	avail = *pcm->hw_ptr - *pcm->appl_ptr; | 	avail = *pcm->hw.ptr - *pcm->appl.ptr; | ||||||
| 	if (pcm->stream == SND_PCM_STREAM_PLAYBACK) | 	if (pcm->stream == SND_PCM_STREAM_PLAYBACK) | ||||||
| 		avail += pcm->buffer_size; | 		avail += pcm->buffer_size; | ||||||
| 	if (avail < 0) | 	if (avail < 0) | ||||||
|  | @ -295,13 +312,13 @@ static inline const snd_pcm_channel_area_t *snd_pcm_mmap_areas(snd_pcm_t *pcm) | ||||||
| static inline snd_pcm_uframes_t snd_pcm_mmap_offset(snd_pcm_t *pcm) | static inline snd_pcm_uframes_t snd_pcm_mmap_offset(snd_pcm_t *pcm) | ||||||
| { | { | ||||||
|         assert(pcm); |         assert(pcm); | ||||||
| 	return *pcm->appl_ptr % pcm->buffer_size; | 	return *pcm->appl.ptr % pcm->buffer_size; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static inline snd_pcm_uframes_t snd_pcm_mmap_hw_offset(snd_pcm_t *pcm) | static inline snd_pcm_uframes_t snd_pcm_mmap_hw_offset(snd_pcm_t *pcm) | ||||||
| { | { | ||||||
|         assert(pcm); |         assert(pcm); | ||||||
| 	return *pcm->hw_ptr % pcm->buffer_size; | 	return *pcm->hw.ptr % pcm->buffer_size; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #define snd_pcm_mmap_playback_delay snd_pcm_mmap_playback_hw_avail | #define snd_pcm_mmap_playback_delay snd_pcm_mmap_playback_hw_avail | ||||||
|  |  | ||||||
|  | @ -105,7 +105,7 @@ static void snd_pcm_meter_update_main(snd_pcm_t *pcm) | ||||||
| 	int locked; | 	int locked; | ||||||
| 	locked = (pthread_mutex_trylock(&meter->update_mutex) >= 0); | 	locked = (pthread_mutex_trylock(&meter->update_mutex) >= 0); | ||||||
| 	areas = snd_pcm_mmap_areas(pcm); | 	areas = snd_pcm_mmap_areas(pcm); | ||||||
| 	rptr = *pcm->hw_ptr; | 	rptr = *pcm->hw.ptr; | ||||||
| 	old_rptr = meter->rptr; | 	old_rptr = meter->rptr; | ||||||
| 	meter->rptr = rptr; | 	meter->rptr = rptr; | ||||||
| 	frames = rptr - old_rptr; | 	frames = rptr - old_rptr; | ||||||
|  | @ -131,7 +131,7 @@ static int snd_pcm_meter_update_scope(snd_pcm_t *pcm) | ||||||
| 	pthread_mutex_lock(&meter->update_mutex); | 	pthread_mutex_lock(&meter->update_mutex); | ||||||
| 	areas = snd_pcm_mmap_areas(pcm); | 	areas = snd_pcm_mmap_areas(pcm); | ||||||
|  _again: |  _again: | ||||||
| 	rptr = *pcm->hw_ptr; | 	rptr = *pcm->hw.ptr; | ||||||
| 	old_rptr = meter->rptr; | 	old_rptr = meter->rptr; | ||||||
| 	rmb(); | 	rmb(); | ||||||
| 	if (atomic_read(&meter->reset)) { | 	if (atomic_read(&meter->reset)) { | ||||||
|  | @ -333,9 +333,9 @@ static int snd_pcm_meter_prepare(snd_pcm_t *pcm) | ||||||
| 	err = snd_pcm_prepare(meter->slave); | 	err = snd_pcm_prepare(meter->slave); | ||||||
| 	if (err >= 0) { | 	if (err >= 0) { | ||||||
| 		if (pcm->stream == SND_PCM_STREAM_PLAYBACK) | 		if (pcm->stream == SND_PCM_STREAM_PLAYBACK) | ||||||
| 			meter->rptr = *pcm->appl_ptr; | 			meter->rptr = *pcm->appl.ptr; | ||||||
| 		else | 		else | ||||||
| 			meter->rptr = *pcm->hw_ptr; | 			meter->rptr = *pcm->hw.ptr; | ||||||
| 	} | 	} | ||||||
| 	return err; | 	return err; | ||||||
| } | } | ||||||
|  | @ -346,7 +346,7 @@ static int snd_pcm_meter_reset(snd_pcm_t *pcm) | ||||||
| 	int err = snd_pcm_reset(meter->slave); | 	int err = snd_pcm_reset(meter->slave); | ||||||
| 	if (err >= 0) { | 	if (err >= 0) { | ||||||
| 		if (pcm->stream == SND_PCM_STREAM_PLAYBACK) | 		if (pcm->stream == SND_PCM_STREAM_PLAYBACK) | ||||||
| 			meter->rptr = *pcm->appl_ptr; | 			meter->rptr = *pcm->appl.ptr; | ||||||
| 	} | 	} | ||||||
| 	return err; | 	return err; | ||||||
| } | } | ||||||
|  | @ -386,7 +386,7 @@ static snd_pcm_sframes_t snd_pcm_meter_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t | ||||||
| 	snd_pcm_meter_t *meter = pcm->private_data; | 	snd_pcm_meter_t *meter = pcm->private_data; | ||||||
| 	snd_pcm_sframes_t err = snd_pcm_rewind(meter->slave, frames); | 	snd_pcm_sframes_t err = snd_pcm_rewind(meter->slave, frames); | ||||||
| 	if (err > 0 && pcm->stream == SND_PCM_STREAM_PLAYBACK) | 	if (err > 0 && pcm->stream == SND_PCM_STREAM_PLAYBACK) | ||||||
| 		meter->rptr = *pcm->appl_ptr; | 		meter->rptr = *pcm->appl.ptr; | ||||||
| 	return err; | 	return err; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -401,13 +401,13 @@ static snd_pcm_sframes_t snd_pcm_meter_mmap_commit(snd_pcm_t *pcm, | ||||||
| 						   snd_pcm_uframes_t size) | 						   snd_pcm_uframes_t size) | ||||||
| { | { | ||||||
| 	snd_pcm_meter_t *meter = pcm->private_data; | 	snd_pcm_meter_t *meter = pcm->private_data; | ||||||
| 	snd_pcm_uframes_t old_rptr = *pcm->appl_ptr; | 	snd_pcm_uframes_t old_rptr = *pcm->appl.ptr; | ||||||
| 	snd_pcm_sframes_t result = snd_pcm_mmap_commit(meter->slave, offset, size); | 	snd_pcm_sframes_t result = snd_pcm_mmap_commit(meter->slave, offset, size); | ||||||
| 	if (result <= 0) | 	if (result <= 0) | ||||||
| 		return result; | 		return result; | ||||||
| 	if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { | 	if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { | ||||||
| 		snd_pcm_meter_add_frames(pcm, snd_pcm_mmap_areas(pcm), old_rptr, result); | 		snd_pcm_meter_add_frames(pcm, snd_pcm_mmap_areas(pcm), old_rptr, result); | ||||||
| 		meter->rptr = *pcm->appl_ptr; | 		meter->rptr = *pcm->appl.ptr; | ||||||
| 	} | 	} | ||||||
| 	return result; | 	return result; | ||||||
| } | } | ||||||
|  | @ -648,8 +648,8 @@ int snd_pcm_meter_open(snd_pcm_t **pcmp, const char *name, unsigned int frequenc | ||||||
| 	pcm->fast_ops = &snd_pcm_meter_fast_ops; | 	pcm->fast_ops = &snd_pcm_meter_fast_ops; | ||||||
| 	pcm->private_data = meter; | 	pcm->private_data = meter; | ||||||
| 	pcm->poll_fd = slave->poll_fd; | 	pcm->poll_fd = slave->poll_fd; | ||||||
| 	pcm->hw_ptr = slave->hw_ptr; | 	snd_pcm_link_hw_ptr(pcm, slave); | ||||||
| 	pcm->appl_ptr = slave->appl_ptr; | 	snd_pcm_link_appl_ptr(pcm, slave); | ||||||
| 	*pcmp = pcm; | 	*pcmp = pcm; | ||||||
| 
 | 
 | ||||||
| 	pthread_mutex_init(&meter->update_mutex, NULL); | 	pthread_mutex_init(&meter->update_mutex, NULL); | ||||||
|  |  | ||||||
|  | @ -36,48 +36,66 @@ size_t page_size(void) | ||||||
| size_t page_align(size_t size) | size_t page_align(size_t size) | ||||||
| { | { | ||||||
| 	size_t r; | 	size_t r; | ||||||
| 	long psz = sysconf(_SC_PAGE_SIZE); | 	long psz = page_size(); | ||||||
| 	assert(psz > 0); |  | ||||||
| 	r = size % psz; | 	r = size % psz; | ||||||
| 	if (r) | 	if (r) | ||||||
| 		return size + psz - r; | 		return size + psz - r; | ||||||
| 	return size; | 	return size; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | size_t page_ptr(size_t object_offset, size_t object_size, size_t *offset, size_t *mmap_offset) | ||||||
|  | { | ||||||
|  | 	size_t r; | ||||||
|  | 	long psz = page_size(); | ||||||
|  | 	assert(offset); | ||||||
|  | 	assert(mmap_offset); | ||||||
|  | 	*mmap_offset = object_offset; | ||||||
|  | 	object_offset %= psz; | ||||||
|  | 	*mmap_offset -= object_offset; | ||||||
|  | 	object_size += object_offset; | ||||||
|  | 	r = object_size % psz; | ||||||
|  | 	if (r) | ||||||
|  | 		r = object_size + psz - r; | ||||||
|  | 	else | ||||||
|  | 		r = object_size; | ||||||
|  | 	*offset = object_offset; | ||||||
|  | 	return r; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void snd_pcm_mmap_appl_backward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) | void snd_pcm_mmap_appl_backward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) | ||||||
| { | { | ||||||
| 	snd_pcm_sframes_t appl_ptr = *pcm->appl_ptr; | 	snd_pcm_sframes_t appl_ptr = *pcm->appl.ptr; | ||||||
| 	appl_ptr -= frames; | 	appl_ptr -= frames; | ||||||
| 	if (appl_ptr < 0) | 	if (appl_ptr < 0) | ||||||
| 		appl_ptr += pcm->boundary; | 		appl_ptr += pcm->boundary; | ||||||
| 	*pcm->appl_ptr = appl_ptr; | 	*pcm->appl.ptr = appl_ptr; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void snd_pcm_mmap_appl_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) | void snd_pcm_mmap_appl_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) | ||||||
| { | { | ||||||
| 	snd_pcm_uframes_t appl_ptr = *pcm->appl_ptr; | 	snd_pcm_uframes_t appl_ptr = *pcm->appl.ptr; | ||||||
| 	appl_ptr += frames; | 	appl_ptr += frames; | ||||||
| 	if (appl_ptr >= pcm->boundary) | 	if (appl_ptr >= pcm->boundary) | ||||||
| 		appl_ptr -= pcm->boundary; | 		appl_ptr -= pcm->boundary; | ||||||
| 	*pcm->appl_ptr = appl_ptr; | 	*pcm->appl.ptr = appl_ptr; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void snd_pcm_mmap_hw_backward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) | void snd_pcm_mmap_hw_backward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) | ||||||
| { | { | ||||||
| 	snd_pcm_sframes_t hw_ptr = *pcm->hw_ptr; | 	snd_pcm_sframes_t hw_ptr = *pcm->hw.ptr; | ||||||
| 	hw_ptr -= frames; | 	hw_ptr -= frames; | ||||||
| 	if (hw_ptr < 0) | 	if (hw_ptr < 0) | ||||||
| 		hw_ptr += pcm->boundary; | 		hw_ptr += pcm->boundary; | ||||||
| 	*pcm->hw_ptr = hw_ptr; | 	*pcm->hw.ptr = hw_ptr; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void snd_pcm_mmap_hw_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) | void snd_pcm_mmap_hw_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) | ||||||
| { | { | ||||||
| 	snd_pcm_uframes_t hw_ptr = *pcm->hw_ptr; | 	snd_pcm_uframes_t hw_ptr = *pcm->hw.ptr; | ||||||
| 	hw_ptr += frames; | 	hw_ptr += frames; | ||||||
| 	if (hw_ptr >= pcm->boundary) | 	if (hw_ptr >= pcm->boundary) | ||||||
| 		hw_ptr -= pcm->boundary; | 		hw_ptr -= pcm->boundary; | ||||||
| 	*pcm->hw_ptr = hw_ptr; | 	*pcm->hw.ptr = hw_ptr; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static snd_pcm_sframes_t snd_pcm_mmap_write_areas(snd_pcm_t *pcm, | static snd_pcm_sframes_t snd_pcm_mmap_write_areas(snd_pcm_t *pcm, | ||||||
|  |  | ||||||
|  | @ -457,8 +457,8 @@ int snd_pcm_mulaw_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sfor | ||||||
| 	pcm->fast_ops = &snd_pcm_plugin_fast_ops; | 	pcm->fast_ops = &snd_pcm_plugin_fast_ops; | ||||||
| 	pcm->private_data = mulaw; | 	pcm->private_data = mulaw; | ||||||
| 	pcm->poll_fd = slave->poll_fd; | 	pcm->poll_fd = slave->poll_fd; | ||||||
| 	pcm->hw_ptr = &mulaw->plug.hw_ptr; | 	snd_pcm_set_hw_ptr(pcm, &mulaw->plug.hw_ptr, -1, 0); | ||||||
| 	pcm->appl_ptr = &mulaw->plug.appl_ptr; | 	snd_pcm_set_appl_ptr(pcm, &mulaw->plug.appl_ptr, -1, 0); | ||||||
| 	*pcmp = pcm; | 	*pcmp = pcm; | ||||||
| 
 | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
|  |  | ||||||
|  | @ -690,8 +690,8 @@ int snd_pcm_multi_open(snd_pcm_t **pcmp, const char *name, | ||||||
| 	pcm->fast_ops = &snd_pcm_multi_fast_ops; | 	pcm->fast_ops = &snd_pcm_multi_fast_ops; | ||||||
| 	pcm->private_data = multi; | 	pcm->private_data = multi; | ||||||
| 	pcm->poll_fd = multi->slaves[master_slave].pcm->poll_fd; | 	pcm->poll_fd = multi->slaves[master_slave].pcm->poll_fd; | ||||||
| 	pcm->hw_ptr = multi->slaves[master_slave].pcm->hw_ptr; | 	snd_pcm_link_hw_ptr(pcm, multi->slaves[master_slave].pcm); | ||||||
| 	pcm->appl_ptr = multi->slaves[master_slave].pcm->appl_ptr; | 	snd_pcm_link_appl_ptr(pcm, multi->slaves[master_slave].pcm); | ||||||
| 	*pcmp = pcm; | 	*pcmp = pcm; | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -112,16 +112,15 @@ static int snd_pcm_null_prepare(snd_pcm_t *pcm) | ||||||
| { | { | ||||||
| 	snd_pcm_null_t *null = pcm->private_data; | 	snd_pcm_null_t *null = pcm->private_data; | ||||||
| 	null->state = SND_PCM_STATE_PREPARED; | 	null->state = SND_PCM_STATE_PREPARED; | ||||||
| 	null->appl_ptr = 0; | 	*pcm->appl.ptr = 0; | ||||||
| 	null->hw_ptr = 0; | 	*pcm->hw.ptr = 0; | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static int snd_pcm_null_reset(snd_pcm_t *pcm) | static int snd_pcm_null_reset(snd_pcm_t *pcm) | ||||||
| { | { | ||||||
| 	snd_pcm_null_t *null = pcm->private_data; | 	*pcm->appl.ptr = 0; | ||||||
| 	null->appl_ptr = 0; | 	*pcm->hw.ptr = 0; | ||||||
| 	null->hw_ptr = 0; |  | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -131,9 +130,9 @@ static int snd_pcm_null_start(snd_pcm_t *pcm) | ||||||
| 	assert(null->state == SND_PCM_STATE_PREPARED); | 	assert(null->state == SND_PCM_STATE_PREPARED); | ||||||
| 	null->state = SND_PCM_STATE_RUNNING; | 	null->state = SND_PCM_STATE_RUNNING; | ||||||
| 	if (pcm->stream == SND_PCM_STREAM_CAPTURE) | 	if (pcm->stream == SND_PCM_STREAM_CAPTURE) | ||||||
| 		*pcm->hw_ptr = *pcm->appl_ptr + pcm->buffer_size; | 		*pcm->hw.ptr = *pcm->appl.ptr + pcm->buffer_size; | ||||||
| 	else | 	else | ||||||
| 		*pcm->hw_ptr = *pcm->appl_ptr; | 		*pcm->hw.ptr = *pcm->appl.ptr; | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -387,8 +386,8 @@ int snd_pcm_null_open(snd_pcm_t **pcmp, const char *name, snd_pcm_stream_t strea | ||||||
| 	pcm->fast_ops = &snd_pcm_null_fast_ops; | 	pcm->fast_ops = &snd_pcm_null_fast_ops; | ||||||
| 	pcm->private_data = null; | 	pcm->private_data = null; | ||||||
| 	pcm->poll_fd = fd; | 	pcm->poll_fd = fd; | ||||||
| 	pcm->hw_ptr = &null->hw_ptr; | 	snd_pcm_set_hw_ptr(pcm, &null->hw_ptr, -1, 0); | ||||||
| 	pcm->appl_ptr = &null->appl_ptr; | 	snd_pcm_set_appl_ptr(pcm, &null->appl_ptr, -1, 0); | ||||||
| 	*pcmp = pcm; | 	*pcmp = pcm; | ||||||
| 
 | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
|  |  | ||||||
|  | @ -832,8 +832,10 @@ static int snd_pcm_plug_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) | ||||||
| 		snd_pcm_plug_clear(pcm); | 		snd_pcm_plug_clear(pcm); | ||||||
| 		return err; | 		return err; | ||||||
| 	} | 	} | ||||||
| 	pcm->hw_ptr = slave->hw_ptr; | 	snd_pcm_unlink_hw_ptr(pcm, plug->req_slave); | ||||||
| 	pcm->appl_ptr = slave->appl_ptr; | 	snd_pcm_unlink_appl_ptr(pcm, plug->req_slave); | ||||||
|  | 	snd_pcm_link_hw_ptr(pcm, slave); | ||||||
|  | 	snd_pcm_link_appl_ptr(pcm, slave); | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -915,6 +917,7 @@ int snd_pcm_plug_open(snd_pcm_t **pcmp, | ||||||
| 	snd_pcm_plug_t *plug; | 	snd_pcm_plug_t *plug; | ||||||
| 	int err; | 	int err; | ||||||
| 	assert(pcmp && slave); | 	assert(pcmp && slave); | ||||||
|  | 
 | ||||||
| 	plug = calloc(1, sizeof(snd_pcm_plug_t)); | 	plug = calloc(1, sizeof(snd_pcm_plug_t)); | ||||||
| 	if (!plug) | 	if (!plug) | ||||||
| 		return -ENOMEM; | 		return -ENOMEM; | ||||||
|  | @ -939,8 +942,8 @@ int snd_pcm_plug_open(snd_pcm_t **pcmp, | ||||||
| 	pcm->fast_op_arg = slave->fast_op_arg; | 	pcm->fast_op_arg = slave->fast_op_arg; | ||||||
| 	pcm->private_data = plug; | 	pcm->private_data = plug; | ||||||
| 	pcm->poll_fd = slave->poll_fd; | 	pcm->poll_fd = slave->poll_fd; | ||||||
| 	pcm->hw_ptr = slave->hw_ptr; | 	snd_pcm_link_hw_ptr(pcm, slave); | ||||||
| 	pcm->appl_ptr = slave->appl_ptr; | 	snd_pcm_link_appl_ptr(pcm, slave); | ||||||
| 	*pcmp = pcm; | 	*pcmp = pcm; | ||||||
| 
 | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
|  |  | ||||||
|  | @ -211,8 +211,8 @@ int snd_pcm_plugin_prepare(snd_pcm_t *pcm) | ||||||
| 		snd_atomic_write_end(&plugin->watom); | 		snd_atomic_write_end(&plugin->watom); | ||||||
| 		return err; | 		return err; | ||||||
| 	} | 	} | ||||||
| 	plugin->hw_ptr = 0; | 	*pcm->hw.ptr = 0; | ||||||
| 	plugin->appl_ptr = 0; | 	*pcm->appl.ptr = 0; | ||||||
| 	snd_atomic_write_end(&plugin->watom); | 	snd_atomic_write_end(&plugin->watom); | ||||||
| 	if (plugin->init) { | 	if (plugin->init) { | ||||||
| 		err = plugin->init(pcm); | 		err = plugin->init(pcm); | ||||||
|  | @ -232,8 +232,8 @@ static int snd_pcm_plugin_reset(snd_pcm_t *pcm) | ||||||
| 		snd_atomic_write_end(&plugin->watom); | 		snd_atomic_write_end(&plugin->watom); | ||||||
| 		return err; | 		return err; | ||||||
| 	} | 	} | ||||||
| 	plugin->hw_ptr = 0; | 	*pcm->hw.ptr = 0; | ||||||
| 	plugin->appl_ptr = 0; | 	*pcm->appl.ptr = 0; | ||||||
| 	snd_atomic_write_end(&plugin->watom); | 	snd_atomic_write_end(&plugin->watom); | ||||||
| 	if (plugin->init) { | 	if (plugin->init) { | ||||||
| 		err = plugin->init(pcm); | 		err = plugin->init(pcm); | ||||||
|  | @ -504,12 +504,12 @@ snd_pcm_sframes_t snd_pcm_plugin_avail_update(snd_pcm_t *pcm) | ||||||
| 	    pcm->access != SND_PCM_ACCESS_RW_NONINTERLEAVED) | 	    pcm->access != SND_PCM_ACCESS_RW_NONINTERLEAVED) | ||||||
| 		goto _capture; | 		goto _capture; | ||||||
| 	if (plugin->client_frames) { | 	if (plugin->client_frames) { | ||||||
| 		plugin->hw_ptr = plugin->client_frames(pcm, *slave->hw_ptr); | 		*pcm->hw.ptr = plugin->client_frames(pcm, *slave->hw.ptr); | ||||||
| 		if (slave_size <= 0) | 		if (slave_size <= 0) | ||||||
| 			return slave_size; | 			return slave_size; | ||||||
| 		return plugin->client_frames(pcm, slave_size); | 		return plugin->client_frames(pcm, slave_size); | ||||||
| 	} else { | 	} else { | ||||||
| 		plugin->hw_ptr = *slave->hw_ptr; | 		*pcm->hw.ptr = *slave->hw.ptr; | ||||||
| 		return slave_size; | 		return slave_size; | ||||||
| 	} | 	} | ||||||
|  _capture: |  _capture: | ||||||
|  | @ -599,8 +599,8 @@ int snd_pcm_plugin_status(snd_pcm_t *pcm, snd_pcm_status_t * status) | ||||||
| 		snd_atomic_read_ok(&ratom); | 		snd_atomic_read_ok(&ratom); | ||||||
| 		return err; | 		return err; | ||||||
| 	} | 	} | ||||||
| 	status->appl_ptr = plugin->appl_ptr; | 	status->appl_ptr = *pcm->appl.ptr; | ||||||
| 	status->hw_ptr = plugin->hw_ptr; | 	status->hw_ptr = *pcm->hw.ptr; | ||||||
| 	status->avail = pcm->buffer_size; | 	status->avail = pcm->buffer_size; | ||||||
| 	snd_pcm_plugin_delay(pcm, &status->delay); | 	snd_pcm_plugin_delay(pcm, &status->delay); | ||||||
| 	if (!snd_atomic_read_ok(&ratom)) { | 	if (!snd_atomic_read_ok(&ratom)) { | ||||||
|  |  | ||||||
|  | @ -593,8 +593,8 @@ int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sform | ||||||
| 	pcm->fast_ops = &snd_pcm_plugin_fast_ops; | 	pcm->fast_ops = &snd_pcm_plugin_fast_ops; | ||||||
| 	pcm->private_data = rate; | 	pcm->private_data = rate; | ||||||
| 	pcm->poll_fd = slave->poll_fd; | 	pcm->poll_fd = slave->poll_fd; | ||||||
| 	pcm->hw_ptr = &rate->plug.hw_ptr; | 	snd_pcm_set_hw_ptr(pcm, &rate->plug.hw_ptr, -1, 0); | ||||||
| 	pcm->appl_ptr = &rate->plug.appl_ptr; | 	snd_pcm_set_appl_ptr(pcm, &rate->plug.appl_ptr, -1, 0); | ||||||
| 	*pcmp = pcm; | 	*pcmp = pcm; | ||||||
| 
 | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
|  |  | ||||||
|  | @ -800,8 +800,8 @@ int snd_pcm_route_open(snd_pcm_t **pcmp, const char *name, | ||||||
| 	pcm->fast_ops = &snd_pcm_plugin_fast_ops; | 	pcm->fast_ops = &snd_pcm_plugin_fast_ops; | ||||||
| 	pcm->private_data = route; | 	pcm->private_data = route; | ||||||
| 	pcm->poll_fd = slave->poll_fd; | 	pcm->poll_fd = slave->poll_fd; | ||||||
| 	pcm->hw_ptr = &route->plug.hw_ptr; | 	snd_pcm_set_hw_ptr(pcm, &route->plug.hw_ptr, -1, 0); | ||||||
| 	pcm->appl_ptr = &route->plug.appl_ptr; | 	snd_pcm_set_appl_ptr(pcm, &route->plug.appl_ptr, -1, 0); | ||||||
| 	err = route_load_ttable(&route->params, pcm->stream, tt_ssize, ttable, tt_cused, tt_sused); | 	err = route_load_ttable(&route->params, pcm->stream, tt_ssize, ttable, tt_cused, tt_sused); | ||||||
| 	if (err < 0) { | 	if (err < 0) { | ||||||
| 		snd_pcm_close(pcm); | 		snd_pcm_close(pcm); | ||||||
|  |  | ||||||
|  | @ -123,7 +123,7 @@ static snd_pcm_uframes_t snd_pcm_share_slave_avail(snd_pcm_share_slave_t *slave) | ||||||
| { | { | ||||||
| 	snd_pcm_sframes_t avail; | 	snd_pcm_sframes_t avail; | ||||||
| 	snd_pcm_t *pcm = slave->pcm; | 	snd_pcm_t *pcm = slave->pcm; | ||||||
|   	avail = slave->hw_ptr - *pcm->appl_ptr; |   	avail = slave->hw_ptr - *pcm->appl.ptr; | ||||||
| 	if (pcm->stream == SND_PCM_STREAM_PLAYBACK) | 	if (pcm->stream == SND_PCM_STREAM_PLAYBACK) | ||||||
| 		avail += pcm->buffer_size; | 		avail += pcm->buffer_size; | ||||||
| 	if (avail < 0) | 	if (avail < 0) | ||||||
|  | @ -147,7 +147,7 @@ static snd_pcm_uframes_t _snd_pcm_share_slave_forward(snd_pcm_share_slave_t *sla | ||||||
| 	buffer_size = slave->pcm->buffer_size; | 	buffer_size = slave->pcm->buffer_size; | ||||||
| 	min_frames = slave_avail; | 	min_frames = slave_avail; | ||||||
| 	max_frames = 0; | 	max_frames = 0; | ||||||
| 	slave_appl_ptr = *slave->pcm->appl_ptr; | 	slave_appl_ptr = *slave->pcm->appl.ptr; | ||||||
| 	list_for_each(i, &slave->clients) { | 	list_for_each(i, &slave->clients) { | ||||||
| 		snd_pcm_share_t *share = list_entry(i, snd_pcm_share_t, list); | 		snd_pcm_share_t *share = list_entry(i, snd_pcm_share_t, list); | ||||||
| 		snd_pcm_t *pcm = share->pcm; | 		snd_pcm_t *pcm = share->pcm; | ||||||
|  | @ -336,7 +336,7 @@ static snd_pcm_uframes_t _snd_pcm_share_slave_missing(snd_pcm_share_slave_t *sla | ||||||
| 	snd_pcm_uframes_t missing = INT_MAX; | 	snd_pcm_uframes_t missing = INT_MAX; | ||||||
| 	struct list_head *i; | 	struct list_head *i; | ||||||
| 	/* snd_pcm_sframes_t avail = */ snd_pcm_avail_update(slave->pcm); | 	/* snd_pcm_sframes_t avail = */ snd_pcm_avail_update(slave->pcm); | ||||||
| 	slave->hw_ptr = *slave->pcm->hw_ptr; | 	slave->hw_ptr = *slave->pcm->hw.ptr; | ||||||
| 	list_for_each(i, &slave->clients) { | 	list_for_each(i, &slave->clients) { | ||||||
| 		snd_pcm_share_t *share = list_entry(i, snd_pcm_share_t, list); | 		snd_pcm_share_t *share = list_entry(i, snd_pcm_share_t, list); | ||||||
| 		snd_pcm_t *pcm = share->pcm; | 		snd_pcm_t *pcm = share->pcm; | ||||||
|  | @ -353,6 +353,7 @@ static void *snd_pcm_share_thread(void *data) | ||||||
| 	snd_pcm_t *spcm = slave->pcm; | 	snd_pcm_t *spcm = slave->pcm; | ||||||
| 	struct pollfd pfd[2]; | 	struct pollfd pfd[2]; | ||||||
| 	int err; | 	int err; | ||||||
|  | 
 | ||||||
| 	pfd[0].fd = slave->poll[0]; | 	pfd[0].fd = slave->poll[0]; | ||||||
| 	pfd[0].events = POLLIN; | 	pfd[0].events = POLLIN; | ||||||
| 	err = snd_pcm_poll_descriptors(spcm, &pfd[1], 1); | 	err = snd_pcm_poll_descriptors(spcm, &pfd[1], 1); | ||||||
|  | @ -373,7 +374,7 @@ static void *snd_pcm_share_thread(void *data) | ||||||
| 			if (hw_ptr >= spcm->boundary) | 			if (hw_ptr >= spcm->boundary) | ||||||
| 				hw_ptr -= spcm->boundary; | 				hw_ptr -= spcm->boundary; | ||||||
| 			hw_ptr -= hw_ptr % spcm->period_size; | 			hw_ptr -= hw_ptr % spcm->period_size; | ||||||
| 			avail_min = hw_ptr - *spcm->appl_ptr; | 			avail_min = hw_ptr - *spcm->appl.ptr; | ||||||
| 			if (spcm->stream == SND_PCM_STREAM_PLAYBACK) | 			if (spcm->stream == SND_PCM_STREAM_PLAYBACK) | ||||||
| 				avail_min += spcm->buffer_size; | 				avail_min += spcm->buffer_size; | ||||||
| 			if (avail_min < 0) | 			if (avail_min < 0) | ||||||
|  | @ -408,7 +409,7 @@ static void _snd_pcm_share_update(snd_pcm_t *pcm) | ||||||
| 	snd_pcm_t *spcm = slave->pcm; | 	snd_pcm_t *spcm = slave->pcm; | ||||||
| 	snd_pcm_uframes_t missing; | 	snd_pcm_uframes_t missing; | ||||||
| 	/* snd_pcm_sframes_t avail = */ snd_pcm_avail_update(spcm); | 	/* snd_pcm_sframes_t avail = */ snd_pcm_avail_update(spcm); | ||||||
| 	slave->hw_ptr = *slave->pcm->hw_ptr; | 	slave->hw_ptr = *slave->pcm->hw.ptr; | ||||||
| 	missing = _snd_pcm_share_missing(pcm); | 	missing = _snd_pcm_share_missing(pcm); | ||||||
| 	// printf("missing %ld\n", missing);
 | 	// printf("missing %ld\n", missing);
 | ||||||
| 	if (!slave->polling) { | 	if (!slave->polling) { | ||||||
|  | @ -423,7 +424,7 @@ static void _snd_pcm_share_update(snd_pcm_t *pcm) | ||||||
| 		if (hw_ptr >= spcm->boundary) | 		if (hw_ptr >= spcm->boundary) | ||||||
| 			hw_ptr -= spcm->boundary; | 			hw_ptr -= spcm->boundary; | ||||||
| 		hw_ptr -= hw_ptr % spcm->period_size; | 		hw_ptr -= hw_ptr % spcm->period_size; | ||||||
| 		avail_min = hw_ptr - *spcm->appl_ptr; | 		avail_min = hw_ptr - *spcm->appl.ptr; | ||||||
| 		if (spcm->stream == SND_PCM_STREAM_PLAYBACK) | 		if (spcm->stream == SND_PCM_STREAM_PLAYBACK) | ||||||
| 			avail_min += spcm->buffer_size; | 			avail_min += spcm->buffer_size; | ||||||
| 		if (avail_min < 0) | 		if (avail_min < 0) | ||||||
|  | @ -760,7 +761,7 @@ static snd_pcm_sframes_t snd_pcm_share_avail_update(snd_pcm_t *pcm) | ||||||
| 			Pthread_mutex_unlock(&slave->mutex); | 			Pthread_mutex_unlock(&slave->mutex); | ||||||
| 			return avail; | 			return avail; | ||||||
| 		} | 		} | ||||||
| 		share->hw_ptr = *slave->pcm->hw_ptr; | 		share->hw_ptr = *slave->pcm->hw.ptr; | ||||||
| 	} | 	} | ||||||
| 	Pthread_mutex_unlock(&slave->mutex); | 	Pthread_mutex_unlock(&slave->mutex); | ||||||
| 	avail = snd_pcm_mmap_avail(pcm); | 	avail = snd_pcm_mmap_avail(pcm); | ||||||
|  | @ -781,7 +782,7 @@ static snd_pcm_sframes_t _snd_pcm_share_mmap_commit(snd_pcm_t *pcm, | ||||||
| 	snd_pcm_sframes_t frames; | 	snd_pcm_sframes_t frames; | ||||||
| 	if (pcm->stream == SND_PCM_STREAM_PLAYBACK && | 	if (pcm->stream == SND_PCM_STREAM_PLAYBACK && | ||||||
| 	    share->state == SND_PCM_STATE_RUNNING) { | 	    share->state == SND_PCM_STATE_RUNNING) { | ||||||
| 		frames = *spcm->appl_ptr - share->appl_ptr; | 		frames = *spcm->appl.ptr - share->appl_ptr; | ||||||
| 		if (frames > (snd_pcm_sframes_t)pcm->buffer_size) | 		if (frames > (snd_pcm_sframes_t)pcm->buffer_size) | ||||||
| 			frames -= pcm->boundary; | 			frames -= pcm->boundary; | ||||||
| 		else if (frames < -(snd_pcm_sframes_t)pcm->buffer_size) | 		else if (frames < -(snd_pcm_sframes_t)pcm->buffer_size) | ||||||
|  | @ -860,7 +861,7 @@ static int snd_pcm_share_reset(snd_pcm_t *pcm) | ||||||
| 	/* FIXME? */ | 	/* FIXME? */ | ||||||
| 	Pthread_mutex_lock(&slave->mutex); | 	Pthread_mutex_lock(&slave->mutex); | ||||||
| 	snd_pcm_areas_silence(pcm->running_areas, 0, pcm->channels, pcm->buffer_size, pcm->format); | 	snd_pcm_areas_silence(pcm->running_areas, 0, pcm->channels, pcm->buffer_size, pcm->format); | ||||||
| 	share->hw_ptr = *slave->pcm->hw_ptr; | 	share->hw_ptr = *slave->pcm->hw.ptr; | ||||||
| 	share->appl_ptr = share->hw_ptr; | 	share->appl_ptr = share->hw_ptr; | ||||||
| 	Pthread_mutex_unlock(&slave->mutex); | 	Pthread_mutex_unlock(&slave->mutex); | ||||||
| 	return err; | 	return err; | ||||||
|  | @ -893,8 +894,8 @@ static int snd_pcm_share_start(snd_pcm_t *pcm) | ||||||
| 				goto _end; | 				goto _end; | ||||||
| 		} | 		} | ||||||
| 		assert(share->hw_ptr == 0); | 		assert(share->hw_ptr == 0); | ||||||
| 		share->hw_ptr = *spcm->hw_ptr; | 		share->hw_ptr = *spcm->hw.ptr; | ||||||
| 		share->appl_ptr = *spcm->appl_ptr; | 		share->appl_ptr = *spcm->appl.ptr; | ||||||
| 		while (xfer < hw_avail) { | 		while (xfer < hw_avail) { | ||||||
| 			snd_pcm_uframes_t frames = hw_avail - xfer; | 			snd_pcm_uframes_t frames = hw_avail - xfer; | ||||||
| 			snd_pcm_uframes_t offset = snd_pcm_mmap_offset(pcm); | 			snd_pcm_uframes_t offset = snd_pcm_mmap_offset(pcm); | ||||||
|  | @ -1142,6 +1143,7 @@ static int snd_pcm_share_close(snd_pcm_t *pcm) | ||||||
| 	snd_pcm_share_t *share = pcm->private_data; | 	snd_pcm_share_t *share = pcm->private_data; | ||||||
| 	snd_pcm_share_slave_t *slave = share->slave; | 	snd_pcm_share_slave_t *slave = share->slave; | ||||||
| 	int err = 0; | 	int err = 0; | ||||||
|  | 
 | ||||||
| 	Pthread_mutex_lock(&snd_pcm_share_slaves_mutex); | 	Pthread_mutex_lock(&snd_pcm_share_slaves_mutex); | ||||||
| 	Pthread_mutex_lock(&slave->mutex); | 	Pthread_mutex_lock(&slave->mutex); | ||||||
| 	slave->open_count--; | 	slave->open_count--; | ||||||
|  | @ -1353,7 +1355,9 @@ int snd_pcm_share_open(snd_pcm_t **pcmp, const char *name, const char *sname, | ||||||
| 			free(share); | 			free(share); | ||||||
| 			return err; | 			return err; | ||||||
| 		} | 		} | ||||||
| 		slave = calloc(1, sizeof(*slave)); | 		/* FIXME: bellow is a real ugly hack to get things working */ | ||||||
|  | 		/* there is a memory leak somewhere, but I'm unable to trace it --jk */ | ||||||
|  | 		slave = calloc(1, sizeof(snd_pcm_share_slave_t) * 8); | ||||||
| 		if (!slave) { | 		if (!slave) { | ||||||
| 			Pthread_mutex_unlock(&snd_pcm_share_slaves_mutex); | 			Pthread_mutex_unlock(&snd_pcm_share_slaves_mutex); | ||||||
| 			snd_pcm_close(spcm); | 			snd_pcm_close(spcm); | ||||||
|  | @ -1408,8 +1412,8 @@ int snd_pcm_share_open(snd_pcm_t **pcmp, const char *name, const char *sname, | ||||||
| 	pcm->fast_ops = &snd_pcm_share_fast_ops; | 	pcm->fast_ops = &snd_pcm_share_fast_ops; | ||||||
| 	pcm->private_data = share; | 	pcm->private_data = share; | ||||||
| 	pcm->poll_fd = share->client_socket; | 	pcm->poll_fd = share->client_socket; | ||||||
| 	pcm->hw_ptr = &share->hw_ptr; | 	snd_pcm_set_hw_ptr(pcm, &share->hw_ptr, -1, 0); | ||||||
| 	pcm->appl_ptr = &share->appl_ptr; | 	snd_pcm_set_appl_ptr(pcm, &share->appl_ptr, -1, 0); | ||||||
| 
 | 
 | ||||||
| 	slave->open_count++; | 	slave->open_count++; | ||||||
| 	list_add_tail(&share->list, &slave->clients); | 	list_add_tail(&share->list, &slave->clients); | ||||||
|  | @ -1424,7 +1428,11 @@ int snd_pcm_share_open(snd_pcm_t **pcmp, const char *name, const char *sname, | ||||||
| 
 | 
 | ||||||
| \section pcm_plugins_share Plugin: Share | \section pcm_plugins_share Plugin: Share | ||||||
| 
 | 
 | ||||||
| This plugin allows sharing of multiple channels with more clients. | This plugin allows sharing of multiple channels with more clients. The access | ||||||
|  | to each channel is exlusive (samples are not mixed together). It means, if | ||||||
|  | the channel zero is used with first client, the channel cannot be used with | ||||||
|  | second one. If you are looking for a mixing plugin, use the | ||||||
|  | \ref pcm_plugins_smix "smix plugin". | ||||||
| 
 | 
 | ||||||
| \code | \code | ||||||
| pcm.name { | pcm.name { | ||||||
|  | @ -1433,8 +1441,6 @@ pcm.name { | ||||||
|         # or |         # or | ||||||
|         slave {                 # Slave definition |         slave {                 # Slave definition | ||||||
|                 pcm STR         # Slave PCM name |                 pcm STR         # Slave PCM name | ||||||
|                 # or |  | ||||||
|                 pcm { }         # Slave PCM definition |  | ||||||
|         } |         } | ||||||
| 	bindings { | 	bindings { | ||||||
| 		N INT		# Slave channel INT for client channel N | 		N INT		# Slave channel INT for client channel N | ||||||
|  | @ -1520,7 +1526,7 @@ int _snd_pcm_share_open(snd_pcm_t **pcmp, const char *name, | ||||||
| 	err = snd_config_get_string(sconf, &sname); | 	err = snd_config_get_string(sconf, &sname); | ||||||
| 	sname = err >= 0 && sname ? strdup(sname) : NULL; | 	sname = err >= 0 && sname ? strdup(sname) : NULL; | ||||||
| 	snd_config_delete(sconf); | 	snd_config_delete(sconf); | ||||||
| 	if (err < 0) { | 	if (sname == NULL) { | ||||||
| 		SNDERR("slave.pcm is not a string"); | 		SNDERR("slave.pcm is not a string"); | ||||||
| 		return err; | 		return err; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -93,12 +93,67 @@ int receive_fd(int sock, void *data, size_t len, int *fd) | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| static long snd_pcm_shm_action(snd_pcm_t *pcm) | static long snd_pcm_shm_action_fd0(snd_pcm_t *pcm, int *fd) | ||||||
| { | { | ||||||
| 	snd_pcm_shm_t *shm = pcm->private_data; | 	snd_pcm_shm_t *shm = pcm->private_data; | ||||||
| 	int err; | 	int err; | ||||||
| 	char buf[1]; | 	char buf[1]; | ||||||
| 	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl; | 	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl; | ||||||
|  | 
 | ||||||
|  | 	err = write(shm->socket, buf, 1); | ||||||
|  | 	if (err != 1) | ||||||
|  | 		return -EBADFD; | ||||||
|  | 	err = receive_fd(shm->socket, buf, 1, fd); | ||||||
|  | 	if (err != 1) | ||||||
|  | 		return -EBADFD; | ||||||
|  | 	if (ctrl->cmd) { | ||||||
|  | 		SNDERR("Server has not done the cmd"); | ||||||
|  | 		return -EBADFD; | ||||||
|  | 	} | ||||||
|  | 	return ctrl->result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int snd_pcm_shm_new_rbptr(snd_pcm_t *pcm, snd_pcm_shm_t *shm, | ||||||
|  | 				 snd_pcm_rbptr_t *rbptr, volatile snd_pcm_shm_rbptr_t *shm_rbptr) | ||||||
|  | { | ||||||
|  | 	if (!shm_rbptr->use_mmap) { | ||||||
|  | 		if (&pcm->hw == rbptr) | ||||||
|  | 			snd_pcm_set_hw_ptr(pcm, &shm_rbptr->ptr, -1, 0); | ||||||
|  | 		else | ||||||
|  | 			snd_pcm_set_appl_ptr(pcm, &shm_rbptr->ptr, -1, 0); | ||||||
|  | 	} else { | ||||||
|  | 		void *ptr; | ||||||
|  | 		size_t mmap_size, mmap_offset, offset; | ||||||
|  | 		int fd; | ||||||
|  | 		long result; | ||||||
|  | 		 | ||||||
|  | 		shm->ctrl->cmd = &pcm->hw == rbptr ? SND_PCM_IOCTL_HW_PTR_FD : SND_PCM_IOCTL_APPL_PTR_FD; | ||||||
|  | 		result = snd_pcm_shm_action_fd0(pcm, &fd); | ||||||
|  | 		if (result < 0) | ||||||
|  | 			return result; | ||||||
|  | 		mmap_size = page_ptr(shm_rbptr->offset, sizeof(snd_pcm_uframes_t), &offset, &mmap_offset); | ||||||
|  | 		ptr = mmap(NULL, mmap_size, PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, fd, mmap_offset); | ||||||
|  | 		if (ptr == MAP_FAILED || ptr == NULL) { | ||||||
|  | 			SYSERR("shm rbptr mmap failed"); | ||||||
|  | 			return -errno; | ||||||
|  | 		} | ||||||
|  | 		if (&pcm->hw == rbptr) | ||||||
|  | 			snd_pcm_set_hw_ptr(pcm, (snd_pcm_uframes_t *)((char *)ptr + offset), fd, shm_rbptr->offset); | ||||||
|  | 		else | ||||||
|  | 			snd_pcm_set_appl_ptr(pcm, (snd_pcm_uframes_t *)((char *)ptr + offset), fd, shm_rbptr->offset); | ||||||
|  | 	} | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static long snd_pcm_shm_action(snd_pcm_t *pcm) | ||||||
|  | { | ||||||
|  | 	snd_pcm_shm_t *shm = pcm->private_data; | ||||||
|  | 	int err, result; | ||||||
|  | 	char buf[1]; | ||||||
|  | 	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl; | ||||||
|  | 
 | ||||||
|  | 	if (ctrl->hw.changed || ctrl->appl.changed) | ||||||
|  | 		return -EBADFD; | ||||||
| 	err = write(shm->socket, buf, 1); | 	err = write(shm->socket, buf, 1); | ||||||
| 	if (err != 1) | 	if (err != 1) | ||||||
| 		return -EBADFD; | 		return -EBADFD; | ||||||
|  | @ -109,7 +164,20 @@ static long snd_pcm_shm_action(snd_pcm_t *pcm) | ||||||
| 		SNDERR("Server has not done the cmd"); | 		SNDERR("Server has not done the cmd"); | ||||||
| 		return -EBADFD; | 		return -EBADFD; | ||||||
| 	} | 	} | ||||||
| 	return ctrl->result; | 	result = ctrl->result; | ||||||
|  | 	if (ctrl->hw.changed) { | ||||||
|  | 		err = snd_pcm_shm_new_rbptr(pcm, shm, &pcm->hw, &ctrl->hw); | ||||||
|  | 		if (err < 0) | ||||||
|  | 			return err; | ||||||
|  | 		ctrl->hw.changed = 0; | ||||||
|  | 	} | ||||||
|  | 	if (ctrl->appl.changed) { | ||||||
|  | 		err = snd_pcm_shm_new_rbptr(pcm, shm, &pcm->appl, &ctrl->appl); | ||||||
|  | 		if (err < 0) | ||||||
|  | 			return err; | ||||||
|  | 		ctrl->appl.changed = 0; | ||||||
|  | 	} | ||||||
|  | 	return result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static long snd_pcm_shm_action_fd(snd_pcm_t *pcm, int *fd) | static long snd_pcm_shm_action_fd(snd_pcm_t *pcm, int *fd) | ||||||
|  | @ -118,6 +186,9 @@ static long snd_pcm_shm_action_fd(snd_pcm_t *pcm, int *fd) | ||||||
| 	int err; | 	int err; | ||||||
| 	char buf[1]; | 	char buf[1]; | ||||||
| 	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl; | 	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl; | ||||||
|  | 
 | ||||||
|  | 	if (ctrl->hw.changed || ctrl->appl.changed) | ||||||
|  | 		return -EBADFD; | ||||||
| 	err = write(shm->socket, buf, 1); | 	err = write(shm->socket, buf, 1); | ||||||
| 	if (err != 1) | 	if (err != 1) | ||||||
| 		return -EBADFD; | 		return -EBADFD; | ||||||
|  | @ -128,6 +199,18 @@ static long snd_pcm_shm_action_fd(snd_pcm_t *pcm, int *fd) | ||||||
| 		SNDERR("Server has not done the cmd"); | 		SNDERR("Server has not done the cmd"); | ||||||
| 		return -EBADFD; | 		return -EBADFD; | ||||||
| 	} | 	} | ||||||
|  | 	if (ctrl->hw.changed) { | ||||||
|  | 		err = snd_pcm_shm_new_rbptr(pcm, shm, &pcm->hw, &ctrl->hw); | ||||||
|  | 		if (err < 0) | ||||||
|  | 			return err; | ||||||
|  | 		ctrl->hw.changed = 0; | ||||||
|  | 	} | ||||||
|  | 	if (ctrl->appl.changed) { | ||||||
|  | 		err = snd_pcm_shm_new_rbptr(pcm, shm, &pcm->appl, &ctrl->appl); | ||||||
|  | 		if (err < 0) | ||||||
|  | 			return err; | ||||||
|  | 		ctrl->appl.changed = 0; | ||||||
|  | 	} | ||||||
| 	return ctrl->result; | 	return ctrl->result; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -422,8 +505,13 @@ static int snd_pcm_shm_drain(snd_pcm_t *pcm) | ||||||
| 	snd_pcm_shm_t *shm = pcm->private_data; | 	snd_pcm_shm_t *shm = pcm->private_data; | ||||||
| 	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl; | 	volatile snd_pcm_shm_ctrl_t *ctrl = shm->ctrl; | ||||||
| 	int err; | 	int err; | ||||||
|  | 	do { | ||||||
| 		ctrl->cmd = SNDRV_PCM_IOCTL_DRAIN; | 		ctrl->cmd = SNDRV_PCM_IOCTL_DRAIN; | ||||||
| 		err = snd_pcm_shm_action(pcm); | 		err = snd_pcm_shm_action(pcm); | ||||||
|  | 		if (err != -EAGAIN) | ||||||
|  | 			break; | ||||||
|  | 		usleep(10000); | ||||||
|  | 	} while (1); | ||||||
| 	if (err < 0) | 	if (err < 0) | ||||||
| 		return err; | 		return err; | ||||||
| 	if (!(pcm->mode & SND_PCM_NONBLOCK)) | 	if (!(pcm->mode & SND_PCM_NONBLOCK)) | ||||||
|  | @ -691,8 +779,8 @@ int snd_pcm_shm_open(snd_pcm_t **pcmp, const char *name, | ||||||
| 		return err; | 		return err; | ||||||
| 	} | 	} | ||||||
| 	pcm->poll_fd = err; | 	pcm->poll_fd = err; | ||||||
| 	pcm->hw_ptr = &ctrl->hw_ptr; | 	snd_pcm_set_hw_ptr(pcm, &ctrl->hw.ptr, -1, 0); | ||||||
| 	pcm->appl_ptr = &ctrl->appl_ptr; | 	snd_pcm_set_appl_ptr(pcm, &ctrl->appl.ptr, -1, 0); | ||||||
| 	*pcmp = pcm; | 	*pcmp = pcm; | ||||||
| 	return 0; | 	return 0; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Jaroslav Kysela
						Jaroslav Kysela