1/* 2 * PCM Interface - mmap 3 * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org> 4 * 5 * This library is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU Lesser General Public License as 7 * published by the Free Software Foundation; either version 2.1 of 8 * the License, or (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU Lesser General Public License for more details. 14 * 15 * You should have received a copy of the GNU Lesser General Public 16 * License along with this library; if not, write to the Free Software 17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 * 19 */ 20 21#include <stdio.h> 22#include <malloc.h> 23#include <string.h> 24#include <sys/poll.h> 25#include <sys/mman.h> 26#include <sys/shm.h> 27#include "pcm_local.h" 28 29size_t page_size(void) 30{ 31 long s = sysconf(_SC_PAGE_SIZE); 32 assert(s > 0); 33 return s; 34} 35 36size_t page_align(size_t size) 37{ 38 size_t r; 39 long psz = page_size(); 40 r = size % psz; 41 if (r) 42 return size + psz - r; 43 return size; 44} 45 46size_t page_ptr(size_t object_offset, size_t object_size, size_t *offset, size_t *mmap_offset) 47{ 48 size_t r; 49 long psz = page_size(); 50 assert(offset); 51 assert(mmap_offset); 52 *mmap_offset = object_offset; 53 object_offset %= psz; 54 *mmap_offset -= object_offset; 55 object_size += object_offset; 56 r = object_size % psz; 57 if (r) 58 r = object_size + psz - r; 59 else 60 r = object_size; 61 *offset = object_offset; 62 return r; 63} 64 65void snd_pcm_mmap_appl_backward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) 66{ 67 snd_pcm_sframes_t appl_ptr = *pcm->appl.ptr; 68 appl_ptr -= frames; 69 if (appl_ptr < 0) 70 appl_ptr += pcm->boundary; 71 *pcm->appl.ptr = appl_ptr; 72} 73 74void snd_pcm_mmap_appl_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) 75{ 76 snd_pcm_uframes_t appl_ptr = *pcm->appl.ptr; 77 appl_ptr += frames; 78 if (appl_ptr >= pcm->boundary) 79 appl_ptr -= pcm->boundary; 80 *pcm->appl.ptr = appl_ptr; 81} 82 83void snd_pcm_mmap_hw_backward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) 84{ 85 snd_pcm_sframes_t hw_ptr = *pcm->hw.ptr; 86 hw_ptr -= frames; 87 if (hw_ptr < 0) 88 hw_ptr += pcm->boundary; 89 *pcm->hw.ptr = hw_ptr; 90} 91 92void snd_pcm_mmap_hw_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) 93{ 94 snd_pcm_uframes_t hw_ptr = *pcm->hw.ptr; 95 hw_ptr += frames; 96 if (hw_ptr >= pcm->boundary) 97 hw_ptr -= pcm->boundary; 98 *pcm->hw.ptr = hw_ptr; 99} 100 101static snd_pcm_sframes_t snd_pcm_mmap_write_areas(snd_pcm_t *pcm, 102 const snd_pcm_channel_area_t *areas, 103 snd_pcm_uframes_t offset, 104 snd_pcm_uframes_t size) 105{ 106 snd_pcm_uframes_t xfer = 0; 107 108 if (snd_pcm_mmap_playback_avail(pcm) < size) { 109 SNDMSG("too short avail %ld to size %ld", snd_pcm_mmap_playback_avail(pcm), size); 110 return -EPIPE; 111 } 112 while (size > 0) { 113 const snd_pcm_channel_area_t *pcm_areas; 114 snd_pcm_uframes_t pcm_offset; 115 snd_pcm_uframes_t frames = size; 116 snd_pcm_sframes_t result; 117 118 snd_pcm_mmap_begin(pcm, &pcm_areas, &pcm_offset, &frames); 119 snd_pcm_areas_copy(pcm_areas, pcm_offset, 120 areas, offset, 121 pcm->channels, 122 frames, pcm->format); 123 result = snd_pcm_mmap_commit(pcm, pcm_offset, frames); 124 if (result < 0) 125 return xfer > 0 ? (snd_pcm_sframes_t)xfer : result; 126 offset += result; 127 xfer += result; 128 size -= result; 129 } 130 return (snd_pcm_sframes_t)xfer; 131} 132 133static snd_pcm_sframes_t snd_pcm_mmap_read_areas(snd_pcm_t *pcm, 134 const snd_pcm_channel_area_t *areas, 135 snd_pcm_uframes_t offset, 136 snd_pcm_uframes_t size) 137{ 138 snd_pcm_uframes_t xfer = 0; 139 140 if (snd_pcm_mmap_capture_avail(pcm) < size) { 141 SNDMSG("too short avail %ld to size %ld", snd_pcm_mmap_capture_avail(pcm), size); 142 return -EPIPE; 143 } 144 while (size > 0) { 145 const snd_pcm_channel_area_t *pcm_areas; 146 snd_pcm_uframes_t pcm_offset; 147 snd_pcm_uframes_t frames = size; 148 snd_pcm_sframes_t result; 149 150 snd_pcm_mmap_begin(pcm, &pcm_areas, &pcm_offset, &frames); 151 snd_pcm_areas_copy(areas, offset, 152 pcm_areas, pcm_offset, 153 pcm->channels, 154 frames, pcm->format); 155 result = snd_pcm_mmap_commit(pcm, pcm_offset, frames); 156 if (result < 0) 157 return xfer > 0 ? (snd_pcm_sframes_t)xfer : result; 158 offset += result; 159 xfer += result; 160 size -= result; 161 } 162 return (snd_pcm_sframes_t)xfer; 163} 164 165/** 166 * \brief Write interleaved frames to a PCM using direct buffer (mmap) 167 * \param pcm PCM handle 168 * \param buffer frames containing buffer 169 * \param size frames to be written 170 * \return a positive number of frames actually written otherwise a 171 * negative error code 172 * \retval -EBADFD PCM is not in the right state (#SND_PCM_STATE_PREPARED or #SND_PCM_STATE_RUNNING) 173 * \retval -EPIPE an underrun occurred 174 * \retval -ESTRPIPE a suspend event occurred (stream is suspended and waiting for an application recovery) 175 * 176 * If the blocking behaviour is selected, then routine waits until 177 * all requested bytes are played or put to the playback ring buffer. 178 * The count of bytes can be less only if a signal or underrun occurred. 179 * 180 * If the non-blocking behaviour is selected, then routine doesn't wait at all. 181 */ 182snd_pcm_sframes_t snd_pcm_mmap_writei(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size) 183{ 184 snd_pcm_channel_area_t areas[pcm->channels]; 185 snd_pcm_areas_from_buf(pcm, areas, (void*)buffer); 186 return snd_pcm_write_areas(pcm, areas, 0, size, 187 snd_pcm_mmap_write_areas); 188} 189 190/** 191 * \brief Write non interleaved frames to a PCM using direct buffer (mmap) 192 * \param pcm PCM handle 193 * \param bufs frames containing buffers (one for each channel) 194 * \param size frames to be written 195 * \return a positive number of frames actually written otherwise a 196 * negative error code 197 * \retval -EBADFD PCM is not in the right state (#SND_PCM_STATE_PREPARED or #SND_PCM_STATE_RUNNING) 198 * \retval -EPIPE an underrun occurred 199 * \retval -ESTRPIPE a suspend event occurred (stream is suspended and waiting for an application recovery) 200 * 201 * If the blocking behaviour is selected, then routine waits until 202 * all requested bytes are played or put to the playback ring buffer. 203 * The count of bytes can be less only if a signal or underrun occurred. 204 * 205 * If the non-blocking behaviour is selected, then routine doesn't wait at all. 206 */ 207snd_pcm_sframes_t snd_pcm_mmap_writen(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size) 208{ 209 snd_pcm_channel_area_t areas[pcm->channels]; 210 snd_pcm_areas_from_bufs(pcm, areas, bufs); 211 return snd_pcm_write_areas(pcm, areas, 0, size, 212 snd_pcm_mmap_write_areas); 213} 214 215/** 216 * \brief Read interleaved frames from a PCM using direct buffer (mmap) 217 * \param pcm PCM handle 218 * \param buffer frames containing buffer 219 * \param size frames to be written 220 * \return a positive number of frames actually read otherwise a 221 * negative error code 222 * \retval -EBADFD PCM is not in the right state (#SND_PCM_STATE_PREPARED or #SND_PCM_STATE_RUNNING) 223 * \retval -EPIPE an overrun occurred 224 * \retval -ESTRPIPE a suspend event occurred (stream is suspended and waiting for an application recovery) 225 * 226 * If the blocking behaviour was selected, then routine waits until 227 * all requested bytes are filled. The count of bytes can be less only 228 * if a signal or underrun occurred. 229 * 230 * If the non-blocking behaviour is selected, then routine doesn't wait at all. 231 */ 232snd_pcm_sframes_t snd_pcm_mmap_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size) 233{ 234 snd_pcm_channel_area_t areas[pcm->channels]; 235 snd_pcm_areas_from_buf(pcm, areas, buffer); 236 return snd_pcm_read_areas(pcm, areas, 0, size, 237 snd_pcm_mmap_read_areas); 238} 239 240/** 241 * \brief Read non interleaved frames to a PCM using direct buffer (mmap) 242 * \param pcm PCM handle 243 * \param bufs frames containing buffers (one for each channel) 244 * \param size frames to be written 245 * \return a positive number of frames actually read otherwise a 246 * negative error code 247 * \retval -EBADFD PCM is not in the right state (#SND_PCM_STATE_PREPARED or #SND_PCM_STATE_RUNNING) 248 * \retval -EPIPE an overrun occurred 249 * \retval -ESTRPIPE a suspend event occurred (stream is suspended and waiting for an application recovery) 250 * 251 * If the blocking behaviour was selected, then routine waits until 252 * all requested bytes are filled. The count of bytes can be less only 253 * if a signal or underrun occurred. 254 * 255 * If the non-blocking behaviour is selected, then routine doesn't wait at all. 256 */ 257snd_pcm_sframes_t snd_pcm_mmap_readn(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size) 258{ 259 snd_pcm_channel_area_t areas[pcm->channels]; 260 snd_pcm_areas_from_bufs(pcm, areas, bufs); 261 return snd_pcm_read_areas(pcm, areas, 0, size, 262 snd_pcm_mmap_read_areas); 263} 264 265int snd_pcm_channel_info_shm(snd_pcm_t *pcm, snd_pcm_channel_info_t *info, int shmid) 266{ 267 switch (pcm->access) { 268 case SND_PCM_ACCESS_MMAP_INTERLEAVED: 269 case SND_PCM_ACCESS_RW_INTERLEAVED: 270 info->first = info->channel * pcm->sample_bits; 271 info->step = pcm->frame_bits; 272 break; 273 case SND_PCM_ACCESS_MMAP_NONINTERLEAVED: 274 case SND_PCM_ACCESS_RW_NONINTERLEAVED: 275 info->first = 0; 276 info->step = pcm->sample_bits; 277 break; 278 default: 279 SNDMSG("invalid access type %d", pcm->access); 280 return -EINVAL; 281 } 282 info->addr = 0; 283 if (pcm->hw_flags & SND_PCM_HW_PARAMS_EXPORT_BUFFER) { 284 info->type = SND_PCM_AREA_SHM; 285 info->u.shm.shmid = shmid; 286 info->u.shm.area = NULL; 287 } else 288 info->type = SND_PCM_AREA_LOCAL; 289 return 0; 290} 291 292int snd_pcm_mmap(snd_pcm_t *pcm) 293{ 294 int err; 295 unsigned int c; 296 assert(pcm); 297 if (CHECK_SANITY(! pcm->setup)) { 298 SNDMSG("PCM not set up"); 299 return -EIO; 300 } 301 if (CHECK_SANITY(pcm->mmap_channels || pcm->running_areas)) { 302 SNDMSG("Already mmapped"); 303 return -EBUSY; 304 } 305 err = pcm->ops->mmap(pcm); 306 if (err < 0) 307 return err; 308 if (pcm->mmap_shadow) 309 return 0; 310 pcm->mmap_channels = calloc(pcm->channels, sizeof(pcm->mmap_channels[0])); 311 if (!pcm->mmap_channels) 312 return -ENOMEM; 313 pcm->running_areas = calloc(pcm->channels, sizeof(pcm->running_areas[0])); 314 if (!pcm->running_areas) { 315 free(pcm->mmap_channels); 316 pcm->mmap_channels = NULL; 317 return -ENOMEM; 318 } 319 for (c = 0; c < pcm->channels; ++c) { 320 snd_pcm_channel_info_t *i = &pcm->mmap_channels[c]; 321 i->channel = c; 322 err = snd_pcm_channel_info(pcm, i); 323 if (err < 0) { 324 free(pcm->mmap_channels); 325 free(pcm->running_areas); 326 pcm->mmap_channels = NULL; 327 pcm->running_areas = NULL; 328 return err; 329 } 330 } 331 for (c = 0; c < pcm->channels; ++c) { 332 snd_pcm_channel_info_t *i = &pcm->mmap_channels[c]; 333 snd_pcm_channel_area_t *a = &pcm->running_areas[c]; 334 char *ptr; 335 size_t size; 336 unsigned int c1; 337 if (i->addr) { 338 a->addr = i->addr; 339 a->first = i->first; 340 a->step = i->step; 341 continue; 342 } 343 size = i->first + i->step * (pcm->buffer_size - 1) + pcm->sample_bits; 344 for (c1 = c + 1; c1 < pcm->channels; ++c1) { 345 snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1]; 346 size_t s; 347 if (i1->type != i->type) 348 continue; 349 switch (i1->type) { 350 case SND_PCM_AREA_MMAP: 351 if (i1->u.mmap.fd != i->u.mmap.fd || 352 i1->u.mmap.offset != i->u.mmap.offset) 353 continue; 354 break; 355 case SND_PCM_AREA_SHM: 356 if (i1->u.shm.shmid != i->u.shm.shmid) 357 continue; 358 break; 359 case SND_PCM_AREA_LOCAL: 360 break; 361 default: 362 assert(0); 363 } 364 s = i1->first + i1->step * (pcm->buffer_size - 1) + pcm->sample_bits; 365 if (s > size) 366 size = s; 367 } 368 size = (size + 7) / 8; 369 size = page_align(size); 370 switch (i->type) { 371 case SND_PCM_AREA_MMAP: 372 ptr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED, i->u.mmap.fd, i->u.mmap.offset); 373 if (ptr == MAP_FAILED) { 374 SYSERR("mmap failed"); 375 return -errno; 376 } 377 i->addr = ptr; 378 break; 379 case SND_PCM_AREA_SHM: 380 if (i->u.shm.shmid < 0) { 381 int id; 382 /* FIXME: safer permission? */ 383 id = shmget(IPC_PRIVATE, size, 0666); 384 if (id < 0) { 385 SYSERR("shmget failed"); 386 return -errno; 387 } 388 i->u.shm.shmid = id; 389 ptr = shmat(i->u.shm.shmid, 0, 0); 390 if (ptr == (void *) -1) { 391 SYSERR("shmat failed"); 392 return -errno; 393 } 394 /* automatically remove segment if not used */ 395 if (shmctl(id, IPC_RMID, NULL) < 0){ 396 SYSERR("shmctl mark remove failed"); 397 return -errno; 398 } 399 i->u.shm.area = snd_shm_area_create(id, ptr); 400 if (i->u.shm.area == NULL) { 401 SYSERR("snd_shm_area_create failed"); 402 return -ENOMEM; 403 } 404 if (pcm->access == SND_PCM_ACCESS_MMAP_INTERLEAVED || 405 pcm->access == SND_PCM_ACCESS_RW_INTERLEAVED) { 406 unsigned int c1; 407 for (c1 = c + 1; c1 < pcm->channels; c1++) { 408 snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1]; 409 if (i1->u.shm.shmid < 0) { 410 i1->u.shm.shmid = id; 411 i1->u.shm.area = snd_shm_area_share(i->u.shm.area); 412 } 413 } 414 } 415 } else { 416 ptr = shmat(i->u.shm.shmid, 0, 0); 417 if (ptr == (void*) -1) { 418 SYSERR("shmat failed"); 419 return -errno; 420 } 421 } 422 i->addr = ptr; 423 break; 424 case SND_PCM_AREA_LOCAL: 425 ptr = malloc(size); 426 if (ptr == NULL) { 427 SYSERR("malloc failed"); 428 return -errno; 429 } 430 i->addr = ptr; 431 break; 432 default: 433 assert(0); 434 } 435 for (c1 = c + 1; c1 < pcm->channels; ++c1) { 436 snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1]; 437 if (i1->type != i->type) 438 continue; 439 switch (i1->type) { 440 case SND_PCM_AREA_MMAP: 441 if (i1->u.mmap.fd != i->u.mmap.fd || 442 i1->u.mmap.offset != i->u.mmap.offset) 443 continue; 444 break; 445 case SND_PCM_AREA_SHM: 446 if (i1->u.shm.shmid != i->u.shm.shmid) 447 continue; 448 /* follow thru */ 449 case SND_PCM_AREA_LOCAL: 450 if (pcm->access != SND_PCM_ACCESS_MMAP_INTERLEAVED && 451 pcm->access != SND_PCM_ACCESS_RW_INTERLEAVED) 452 continue; 453 break; 454 default: 455 assert(0); 456 } 457 i1->addr = i->addr; 458 } 459 a->addr = i->addr; 460 a->first = i->first; 461 a->step = i->step; 462 } 463 return 0; 464} 465 466int snd_pcm_munmap(snd_pcm_t *pcm) 467{ 468 int err; 469 unsigned int c; 470 assert(pcm); 471 if (CHECK_SANITY(! pcm->mmap_channels)) { 472 SNDMSG("Not mmapped"); 473 return -ENXIO; 474 } 475 if (pcm->mmap_shadow) 476 return pcm->ops->munmap(pcm); 477 for (c = 0; c < pcm->channels; ++c) { 478 snd_pcm_channel_info_t *i = &pcm->mmap_channels[c]; 479 unsigned int c1; 480 size_t size = i->first + i->step * (pcm->buffer_size - 1) + pcm->sample_bits; 481 if (!i->addr) 482 continue; 483 for (c1 = c + 1; c1 < pcm->channels; ++c1) { 484 snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1]; 485 size_t s; 486 if (i1->addr != i->addr) 487 continue; 488 i1->addr = NULL; 489 s = i1->first + i1->step * (pcm->buffer_size - 1) + pcm->sample_bits; 490 if (s > size) 491 size = s; 492 } 493 size = (size + 7) / 8; 494 size = page_align(size); 495 switch (i->type) { 496 case SND_PCM_AREA_MMAP: 497 err = munmap(i->addr, size); 498 if (err < 0) { 499 SYSERR("mmap failed"); 500 return -errno; 501 } 502 errno = 0; 503 break; 504 case SND_PCM_AREA_SHM: 505 if (i->u.shm.area) { 506 snd_shm_area_destroy(i->u.shm.area); 507 i->u.shm.area = NULL; 508 if (pcm->access == SND_PCM_ACCESS_MMAP_INTERLEAVED || 509 pcm->access == SND_PCM_ACCESS_RW_INTERLEAVED) { 510 unsigned int c1; 511 for (c1 = c + 1; c1 < pcm->channels; c1++) { 512 snd_pcm_channel_info_t *i1 = &pcm->mmap_channels[c1]; 513 if (i1->u.shm.area) { 514 snd_shm_area_destroy(i1->u.shm.area); 515 i1->u.shm.area = NULL; 516 } 517 } 518 } 519 } 520 break; 521 case SND_PCM_AREA_LOCAL: 522 free(i->addr); 523 break; 524 default: 525 assert(0); 526 } 527 i->addr = NULL; 528 } 529 err = pcm->ops->munmap(pcm); 530 if (err < 0) 531 return err; 532 free(pcm->mmap_channels); 533 free(pcm->running_areas); 534 pcm->mmap_channels = NULL; 535 pcm->running_areas = NULL; 536 return 0; 537} 538 539snd_pcm_sframes_t snd_pcm_write_mmap(snd_pcm_t *pcm, snd_pcm_uframes_t offset, 540 snd_pcm_uframes_t size) 541{ 542 snd_pcm_uframes_t xfer = 0; 543 snd_pcm_sframes_t err = 0; 544 if (! size) 545 return 0; 546 while (xfer < size) { 547 snd_pcm_uframes_t frames = size - xfer; 548 snd_pcm_uframes_t cont = pcm->buffer_size - offset; 549 if (cont < frames) 550 frames = cont; 551 switch (pcm->access) { 552 case SND_PCM_ACCESS_MMAP_INTERLEAVED: 553 { 554 const snd_pcm_channel_area_t *a = snd_pcm_mmap_areas(pcm); 555 const char *buf = snd_pcm_channel_area_addr(a, offset); 556 err = _snd_pcm_writei(pcm, buf, frames); 557 if (err >= 0) 558 frames = err; 559 break; 560 } 561 case SND_PCM_ACCESS_MMAP_NONINTERLEAVED: 562 { 563 unsigned int channels = pcm->channels; 564 unsigned int c; 565 void *bufs[channels]; 566 const snd_pcm_channel_area_t *areas = snd_pcm_mmap_areas(pcm); 567 for (c = 0; c < channels; ++c) { 568 const snd_pcm_channel_area_t *a = &areas[c]; 569 bufs[c] = snd_pcm_channel_area_addr(a, offset); 570 } 571 err = _snd_pcm_writen(pcm, bufs, frames); 572 if (err >= 0) 573 frames = err; 574 break; 575 } 576 default: 577 SNDMSG("invalid access type %d", pcm->access); 578 return -EINVAL; 579 } 580 if (err < 0) 581 break; 582 xfer += frames; 583 offset = (offset + frames) % pcm->buffer_size; 584 } 585 if (xfer > 0) 586 return xfer; 587 return err; 588} 589 590snd_pcm_sframes_t snd_pcm_read_mmap(snd_pcm_t *pcm, snd_pcm_uframes_t offset, 591 snd_pcm_uframes_t size) 592{ 593 snd_pcm_uframes_t xfer = 0; 594 snd_pcm_sframes_t err = 0; 595 if (! size) 596 return 0; 597 while (xfer < size) { 598 snd_pcm_uframes_t frames = size - xfer; 599 snd_pcm_uframes_t cont = pcm->buffer_size - offset; 600 if (cont < frames) 601 frames = cont; 602 switch (pcm->access) { 603 case SND_PCM_ACCESS_MMAP_INTERLEAVED: 604 { 605 const snd_pcm_channel_area_t *a = snd_pcm_mmap_areas(pcm); 606 char *buf = snd_pcm_channel_area_addr(a, offset); 607 err = _snd_pcm_readi(pcm, buf, frames); 608 if (err >= 0) 609 frames = err; 610 break; 611 } 612 case SND_PCM_ACCESS_MMAP_NONINTERLEAVED: 613 { 614 snd_pcm_uframes_t channels = pcm->channels; 615 unsigned int c; 616 void *bufs[channels]; 617 const snd_pcm_channel_area_t *areas = snd_pcm_mmap_areas(pcm); 618 for (c = 0; c < channels; ++c) { 619 const snd_pcm_channel_area_t *a = &areas[c]; 620 bufs[c] = snd_pcm_channel_area_addr(a, offset); 621 } 622 err = _snd_pcm_readn(pcm->fast_op_arg, bufs, frames); 623 if (err >= 0) 624 frames = err; 625 break; 626 } 627 default: 628 SNDMSG("invalid access type %d", pcm->access); 629 return -EINVAL; 630 } 631 if (err < 0) 632 break; 633 xfer += frames; 634 offset = (offset + frames) % pcm->buffer_size; 635 } 636 if (xfer > 0) 637 return xfer; 638 return err; 639} 640