summaryrefslogtreecommitdiffstats
path: root/plugins/alsa/alsa-core-pcm.c
blob: 3ef6012a918e58d824c44cd72d70aa890f0fcf05 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

@media only all and (prefers-color-scheme: dark) {
.highlight .hll { background-color: #49483e }
.highlight .c { color: #75715e } /* Comment */
.highlight .err { color: #960050; background-color: #1e0010 } /* Error */
.highlight .k { color: #66d9ef } /* Keyword */
.highlight .l { color: #ae81ff } /* Literal */
.highlight .n { color: #f8f8f2 } /* Name */
.highlight .o { color: #f92672 } /* Operator */
.highlight .p { color: #f8f8f2 } /* Punctuation */
.highlight .ch { color: #75715e } /* Comment.Hashbang */
.highlight .cm { color: #75715e } /* Comment.Multiline */
.highlight .cp { color: #75715e } /* Comment.Preproc */
.highlight .cpf { color: #75715e } /* Comment.PreprocFile */
.highlight .c1 { color: #75715e } /* Comment.Single */
.highlight .cs { color: #75715e } /* Comment.Special */
.highlight .gd { color: #f92672 } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gi { color: #a6e22e } /* Generic.Inserted */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #75715e } /* Generic.Subheading */
.highlight .kc { color: #66d9ef } /* Keyword.Constant */
.highlight .kd { color: #66d9ef } /* Keyword.Declaration */
.highlight .kn { color: #f92672 } /* Keyword.Namespace */
.highlight .kp { color: #66d9ef } /* Keyword.Pseudo */
.highlight .kr { color: #66d9ef } /* Keyword.Reserved */
.highlight .kt { color: #66d9ef } /* Keyword.Type */
.highlight .ld { color: #e6db74 } /* Literal.Date */
.highlight .m { color: #ae81ff } /* Literal.Number */
.highlight .s { color: #e6db74 } /* Literal.String */
.highlight .na { color: #a6e22e } /* Name.Attribute */
.highlight .nb { color: #f8f8f2 } /* Name.Builtin */
.highlight .nc { color: #a6e22e } /* Name.Class */
.highlight .no { color: #66d9ef } /* Name.Constant */
.highlight .nd { color: #a6e22e } /* Name.Decorator */
.highlight .ni { color: #f8f8f2 } /* Name.Entity */
.highlight .ne { color: #a6e22e } /* Name.Exception */
.highlight .nf { color: #a6e22e } /* Name.Function */
.highlight .nl { color: #f8f8f2 } /* Name.Label */
.highlight .nn { color: #f8f8f2 } /* Name.Namespace */
.highlight .nx { color: #a6e22e } /* Name.Other */
.highlight .py { color: #f8f8f2 } /* Name.Property */
.highlight .nt { color: #f92672 } /* Name.Tag */
.highlight .nv { color: #f8f8f2 } /* Name.Variable */
.highlight .ow { color: #f92672 } /* Operator.Word */
.highlight .w { color: #f8f8f2 } /* Text.Whitespace */
.highlight .mb { color: #ae81ff } /* Literal.Number.Bin */
.highlight .mf { color: #ae81ff } /* Literal.Number.Float */
.highlight .mh { color: #ae81ff } /* Literal.Number.Hex */
.highlight .mi { color: #ae81ff } /* Literal.Number.Integer */
.highlight .mo { color: #ae81ff } /* Literal.Number.Oct */
.highlight .sa { color: #e6db74 } /* Literal.String.Affix */
.highlight .sb { color: #e6db74 } /* Literal.String.Backtick */
.highlight .sc { color: #e6db74 } /* Literal.String.Char */
.highlight .dl { color: #e6db74 } /* Literal.String.Delimiter */
.highlight .sd { color: #e6db74 } /* Literal.String.Doc */
.highlight .s2 { color: #e6db74 } /* Literal.String.Double */
.highlight .se { color: #ae81ff } /* Literal.String.Escape */
.highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */
.highlight .si { color: #e6db74 } /* Literal.String.Interpol */
.highlight .sx { color: #e6db74 } /* Literal.String.Other */
.highlight .sr { color: #e6db74 } /* Literal.String.Regex */
.highlight .s1 { color: #e6db74 } /* Literal.String.Single */
.highlight .ss { color: #e6db74 } /* Literal.String.Symbol */
.highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #a6e22e } /* Name.Function.Magic */
.highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */
.highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */
.highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */
.highlight .vm { color: #f8f8f2 } /* Name.Variable.Magic */
.highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */
}
@media (prefers-color-scheme: light) {
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
}
###########################################################################
# Copyright 2015, 2016, 2017 IoT.bzh
#
# author: Fulup Ar Foll <fulup@iot.bzh>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
###########################################################################


# Add target to project dependency list
PROJECT_TARGET_ADD(audio)

    # Define project Targets
    ADD_LIBRARY(audio MODULE HighLevelApiConf.c  HighLevelBinding.c)

    # Binder exposes a unique public entry point
    SET_TARGET_PROPERTIES(audio PROPERTIES
	PREFIX "afb-"
        LABELS "BINDING"
	LINK_FLAGS  ${BINDINGS_LINK_FLAG}
        OUTPUT_NAME ${TARGET_NAME}

    )

    # Library dependencies (include updates automatically)
    TARGET_LINK_LIBRARIES(audio 
        audio-inter
        ${link_libraries}
    )

    # installation directory
    INSTALL(TARGETS audio
        LIBRARY DESTINATION ${BINDINGS_INSTALL_DIR})
355'>355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923
/*
 * Copyright(C) 2018 "IoT.bzh"
 * Author Fulup Ar Foll <fulup@iot.bzh>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http : //www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License

for the specific language governing permissions and
 * limitations under the License.
 *
 * reference :
 * https://github.com/zonque/simple-alsa-loop/blob/master/loop.c
 * https://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_8c-example.html#a31
 *
 */

#define _GNU_SOURCE  // needed for vasprintf

#include "alsa-softmixer.h"
#include <pthread.h>
#include <sys/syscall.h>
#include <sched.h>

#include <signal.h>
#include <poll.h>

#include "time_utils.h"

static int xrun(snd_pcm_t * pcm, int error);
static int suspend(snd_pcm_t * pcm, int error);

typedef enum {
	PCM_COPY_MUTE,
	PCM_COPY_UNMUTE,
	PCM_COPY_END,
	PCM_COPY_LAST	// Do not put anything after
} PcmCopyEventType;

typedef struct {
	PcmCopyEventType eventType;
} PcmCopyEvent;

STATIC int AlsaPeriodSize(snd_pcm_format_t pcmFormat) {
    int pcmSampleSize;

    switch (pcmFormat) {

        case SND_PCM_FORMAT_S8:
        case SND_PCM_FORMAT_U8:
            pcmSampleSize = 1;
            break;

        case SND_PCM_FORMAT_U16_LE:
        case SND_PCM_FORMAT_U16_BE:
        case SND_PCM_FORMAT_S16_LE:
        case SND_PCM_FORMAT_S16_BE:
            pcmSampleSize = 2;
            break;

        case SND_PCM_FORMAT_U24_LE:
        case SND_PCM_FORMAT_U24_BE:
        case SND_PCM_FORMAT_S24_LE:
        case SND_PCM_FORMAT_S24_BE:
            pcmSampleSize = 3;
            break;

        case SND_PCM_FORMAT_U32_LE:
        case SND_PCM_FORMAT_U32_BE:
        case SND_PCM_FORMAT_S32_LE:
        case SND_PCM_FORMAT_S32_BE:
            pcmSampleSize = 4;
            break;

        default:
            pcmSampleSize = 0;
    }

    return pcmSampleSize;
}

PUBLIC int AlsaPcmConf(SoftMixerT *mixer, AlsaPcmCtlT *pcm, int mode) {
    int error;
    snd_pcm_hw_params_t *pxmHwParams;
    snd_pcm_sw_params_t *pxmSwParams;
    snd_pcm_format_t format;
    snd_pcm_access_t access;

    AlsaPcmHwInfoT * opts = pcm->params;
	const char * card = pcm->cid.cardid;

    const char * modeS = mode==SND_PCM_STREAM_PLAYBACK?"PLAYBACK":"CAPTURE";

	AFB_API_DEBUG(mixer->api,
				"%s: mixer info %s uid %s, pcm %s, mode %s",
				__func__, mixer->info, mixer->uid, card, modeS);

    // retrieve hardware config from PCM
    snd_pcm_hw_params_alloca(&pxmHwParams);
    error = snd_pcm_hw_params_any(pcm->handle, pxmHwParams);
    if (error < 0) {
        AFB_API_ERROR(mixer->api, "%s: Failed to get parameters: %s", __func__, snd_strerror(error));
        goto OnErrorExit;
    }

	AFB_API_DEBUG(mixer->api, "(%s): PARAMS before:", card);
    AlsaDumpPcmParams(mixer, pxmHwParams);

    if (!opts->access)
         opts->access = SND_PCM_ACCESS_RW_INTERLEAVED;

    snd_pcm_hw_params_get_access(pxmHwParams, &access);
    error = snd_pcm_hw_params_set_access(pcm->handle, pxmHwParams, opts->access);
    if (error) {
        AFB_API_ERROR(mixer->api,
					 "%s (%s) set_access failed (ignore this error): mixer=%s access=%d Fail current=%d mode error=%s",
					 __func__, card, mixer->uid, opts->access, access, snd_strerror(error));
//Fulup        goto OnErrorExit;
    };

    if (opts->format != SND_PCM_FORMAT_UNKNOWN) {
        snd_pcm_hw_params_get_format(pxmHwParams, &format);
        if ((error = snd_pcm_hw_params_set_format(pcm->handle, pxmHwParams, opts->format)) < 0) {
            AFB_API_ERROR(mixer->api,
						 "%s (%s) mixer=%s Set_Format=%s (%d) FAILED current=%d error=%s",
						 __func__, card, mixer->uid, opts->formatString, opts->format, format, snd_strerror(error));
            AlsaDumpFormats(mixer, pcm->handle);
            goto OnErrorExit;
        }
    }

    if (opts->rate > 0 ) {

		AFB_API_DEBUG(mixer->api,"%s (%s): set rate to %d", __func__, card, opts->rate);
        unsigned int pcmRate = opts->rate;
        /* Attempt to set the rate. Failing on a capture dev is acceptable */
        error = snd_pcm_hw_params_set_rate_near(pcm->handle, pxmHwParams, &opts->rate, 0);
        if ( mode == SND_PCM_STREAM_PLAYBACK && error < 0) {
            AFB_API_ERROR(mixer->api,
						 "%s (%s): mixer=%s FailSet_Rate=%d error=%s",
						 __func__, card, mixer->uid, opts->rate, snd_strerror(error));
            goto OnErrorExit;
        }

        // check we got requested rate
        if (mode == SND_PCM_STREAM_PLAYBACK && opts->rate != pcmRate) {
            AFB_API_ERROR(mixer->api,
						 "%s (%s): mixer=%s Set_Rate Fail ask=%dHz get=%dHz",
						 __func__, card, mixer->uid, pcmRate, opts->rate);
            goto OnErrorExit;
        }
    }

    if (opts->channels) {
        if ((error = snd_pcm_hw_params_set_channels(pcm->handle, pxmHwParams, opts->channels)) < 0) {
            AFB_API_ERROR(mixer->api,
						 "%s (%s): mixer=%s Set_Channels=%d Fail error=%s",
						 __func__, card, mixer->uid, opts->channels, snd_strerror(error));
            goto OnErrorExit;
        };
    }

    /* The following code, that
     * 1) sets period time/size; buffer time/size hardware params
     * 2) sets start and stop threashold in software params
     * ... is taken as such from 'aplay' in alsa-utils */

    unsigned buffer_time = 0;
    unsigned period_time = 0;
    snd_pcm_uframes_t buffer_frames = 0;
    snd_pcm_uframes_t period_frames = 0;

	error = snd_pcm_hw_params_get_buffer_time_max(pxmHwParams, &buffer_time, 0);

	AFB_API_DEBUG(mixer->api, "(%s) HW_BUFFER_TIME MAX is %d", card, buffer_time);

	if (buffer_time > 500000)
		buffer_time = 500000;

	if (period_time == 0 && period_frames == 0) {
		if (buffer_time > 0)
			period_time = buffer_time / 4;
		else
			period_frames = buffer_frames / 4;
	}

	if (period_time > 0) {
		AFB_API_DEBUG(mixer->api, "(%s) SET PERIOD TIME to %d", card, period_time);
		error = snd_pcm_hw_params_set_period_time_near(pcm->handle, pxmHwParams, &period_time, 0);
	}
	else {
		AFB_API_DEBUG(mixer->api, "(%s) SET PERIOD SIZE...", card);
		error = snd_pcm_hw_params_set_period_size_near(pcm->handle, pxmHwParams, &period_frames, 0);
	}

	if (error < 0) {
        AFB_API_ERROR(mixer->api,
					 "%s (%s): mixer=%s Fail to set period in hwparams error=%s",
					 __func__, card, mixer->uid, snd_strerror(error));
        goto OnErrorExit;
	}

	if (buffer_time > 0) {
		AFB_API_DEBUG(mixer->api, "(%s) SET BUFFER TIME to %d", card, buffer_time);
		error = snd_pcm_hw_params_set_buffer_time_near(pcm->handle, pxmHwParams, &buffer_time, 0);
	} else {
		AFB_API_DEBUG(mixer->api, "(%s) SET BUFFER SIZE to %ld...", card, buffer_frames);
		error = snd_pcm_hw_params_set_buffer_size_near(pcm->handle, pxmHwParams, &buffer_frames);
	}

	if (error < 0) {
        AFB_API_ERROR(mixer->api,
					 "%s (%s): mixer=%s Fail to set buffer in hwparams error=%s",
					 __func__, card, mixer->uid, snd_strerror(error));
        goto OnErrorExit;
	}

    // store selected values
    if ((error = snd_pcm_hw_params(pcm->handle, pxmHwParams)) < 0) {
        AFB_API_ERROR(mixer->api,
					 "%s (%s): mixer=%s Fail to apply hwparams error=%s",
					 __func__, card, mixer->uid, snd_strerror(error));
        goto OnErrorExit;
    }

	AFB_API_DEBUG(mixer->api, "(%s) PARAMS after:", card);
    AlsaDumpPcmParams(mixer, pxmHwParams);

    // check we effective hw params after optional format change
    snd_pcm_hw_params_get_channels(pxmHwParams, &opts->channels);
    snd_pcm_hw_params_get_format(pxmHwParams, &opts->format);
    snd_pcm_hw_params_get_rate(pxmHwParams, &opts->rate, 0);

	AFB_API_DEBUG(mixer->api, "(%s) rate is %d", card, opts->rate);

    opts->sampleSize = AlsaPeriodSize(opts->format);
    if (opts->sampleSize == 0) {
        AFB_API_ERROR(mixer->api,
					 "%s (%s): mixer=%s Fail unsupported format format=%d",
					 __func__, card, mixer->uid, opts->format);
        goto OnErrorExit;
    }

    snd_pcm_uframes_t chunk_size, buffer_size;

    snd_pcm_hw_params_get_period_size(pxmHwParams, &chunk_size, 0);
    snd_pcm_hw_params_get_buffer_size(pxmHwParams, &buffer_size);
    if (chunk_size == buffer_size) {
    	AFB_API_ERROR(mixer->api,
					 "(%s) Can't use period equal to buffer size (%lu == %lu)",
					  card, chunk_size, buffer_size);
    	goto OnErrorExit;
    }

    int avail_min = -1;
    size_t n;
    int rate = opts->rate;

	if (avail_min < 0)
		n = chunk_size;
	else
		n = (size_t) ((double)rate * avail_min / 1000000);

    // retrieve software config from PCM
    snd_pcm_sw_params_alloca(&pxmSwParams);
    snd_pcm_sw_params_current(pcm->handle, pxmSwParams);

    // available_min is the minimum number of frames available to read,
    // when the call to poll returns. Assume this is the same for playback (not checked that)

    if ((error = snd_pcm_sw_params_set_avail_min(pcm->handle, pxmSwParams, n)) < 0) {
        AFB_API_ERROR(mixer->api,
					 "%s (%s): mixer=%s Fail set_buffersize error=%s",
					 __func__, card, mixer->uid, snd_strerror(error));
        goto OnErrorExit;
    };

    snd_pcm_sw_params_get_avail_min(pxmSwParams, &pcm->avail_min);

    int start_delay = 0;
	snd_pcm_uframes_t start_threshold;

    /* round up to closest transfer boundary */
    n = buffer_size;
    if (start_delay <= 0) {
    	start_threshold = n + (size_t)((double)rate * start_delay / 1000000);
    } else
    	start_threshold = (size_t)((double)rate * start_delay / 1000000);
    if (start_threshold < 1)
    	start_threshold = 1;

    if (start_threshold > n/2)
    	start_threshold = n/2;

	AFB_API_DEBUG(mixer->api, "(%s) CALCULATED START THRESHOLD: %ld", card, start_threshold);

	if (mode == SND_PCM_STREAM_PLAYBACK) {
		start_threshold = 1;
	}

	AFB_API_DEBUG(mixer->api, "(%s) %s: Set start threshold to %ld", card, modeS, start_threshold);
   	error = snd_pcm_sw_params_set_start_threshold(pcm->handle, pxmSwParams, start_threshold);
   	if (error < 0) {
   		AFB_API_ERROR(mixer->api,
					 "%s (%s): mixer=%s failed set start_threshold, error=%s",
					 __func__, card, mixer->uid, snd_strerror(error));
   		goto OnErrorExit;
   	}

    // push software params into PCM
    if ((error = snd_pcm_sw_params(pcm->handle, pxmSwParams)) < 0) {
        AFB_API_ERROR(mixer->api,
					 "%s (%s): mixer=%s Fail to push SW params error=%s",
					 __func__, card, mixer->uid, snd_strerror(error));
        goto OnErrorExit;
    };

    AFB_API_NOTICE(mixer->api,
				  "%s (%s): mixer=%s Done channels=%d rate=%d format=%d access=%d ... DONE !",
				  __func__, card, mixer->uid, opts->channels, opts->rate, opts->format, opts->access);
    return 0;

OnErrorExit:
    return -1;
}

STATIC int AlsaPcmReadCB( AlsaPcmCopyHandleT * pcmCopyHandle) {
	char string[32];

	snd_pcm_sframes_t availIn;
	snd_pcm_t * pcmIn = pcmCopyHandle->pcmIn->handle;
	alsa_ringbuf_t * rbuf = pcmCopyHandle->rbuf;
	snd_pcm_uframes_t bufSize = alsa_ringbuf_buffer_size(rbuf);

	int err;

	// do we have waiting frames ?
	availIn = snd_pcm_avail_update(pcmIn);
	if (availIn <= 0) {
		if (availIn == -EPIPE) {
			int ret = xrun(pcmIn, (int)availIn);
			AFB_API_DEBUG(pcmCopyHandle->api, "XXX read EPIPE (recov=%d) {%s}!", ret, ALSA_PCM_UID(pcmIn, string));

			// For some (undocumented...) reason, a start is mandatory.
			snd_pcm_start(pcmIn);
		}
		goto ExitOnSuccess;
	}

	while (true) {

		snd_pcm_sframes_t nbRead;

		pthread_mutex_lock(&pcmCopyHandle->mutex);
		snd_pcm_sframes_t remain = alsa_ringbuf_frames_remain_capacity(rbuf);

		if (remain <= 0) {
			pthread_mutex_unlock(&pcmCopyHandle->mutex);
			// Wake up the reader, in case it is sleeping,
			// that lets it an opportunity to pop something.
			sem_post(&pcmCopyHandle->sem);
			break;
		}

		if (remain < availIn)
			remain = availIn;

		char buf[remain*pcmCopyHandle->frame_size];
		pthread_mutex_unlock(&pcmCopyHandle->mutex);

		nbRead = snd_pcm_readi(pcmIn, buf, remain);

		if (nbRead == 0) {
			break;
		}
		if (nbRead < 0) {
			if (nbRead== -EPIPE) {
				err = xrun(pcmIn, (int)nbRead);
				AFB_API_DEBUG(pcmCopyHandle->api, "read EPIPE (%d), recov %d", ++pcmCopyHandle->read_err_count, err);
				goto ExitOnSuccess;
			} else if (nbRead== -ESTRPIPE) {
				AFB_API_DEBUG(pcmCopyHandle->api, "read ESTRPIPE");
				if ((err = suspend(pcmIn, (int)nbRead)) < 0)
					goto ExitOnSuccess;
				nbRead = 0;
			} else {
				goto ExitOnSuccess;
			}
		}
		pthread_mutex_lock(&pcmCopyHandle->mutex);
		alsa_ringbuf_frames_push(rbuf, buf, nbRead);
		snd_pcm_uframes_t used = alsa_ringbuf_frames_used(rbuf);
		pthread_mutex_unlock(&pcmCopyHandle->mutex);

		// Wait for having the buffer full enough before waking up the playback
		// else it will starve immediately.
		if (used > 0.8 * (double)bufSize) {
			sem_post(&pcmCopyHandle->sem);
		}

		availIn -= nbRead;

		// completed, we have read everything
		if (availIn <= 0) {
			break;
		}

	}

ExitOnSuccess:
	return 0;
}


static int xrun( snd_pcm_t * pcm, int error)
{
	int err;

	if ((err = snd_pcm_recover(pcm, error, 1)) < 0) {
		return err;
	}
	return 0;
}

static int suspend( snd_pcm_t * pcm, int error)
{
	int err;

	while ((err = snd_pcm_resume(pcm)) == -EAGAIN) {
		usleep(1);
	}
	if (err < 0)
		return xrun(pcm, error);
	return 0;
}


static void readSuspend(AlsaPcmCopyHandleT * pcmCopyHandle) {

	// will be deaf
	pcmCopyHandle->saveFd = pcmCopyHandle->pollFds[1].fd;
	pcmCopyHandle->pollFds[1].fd = -1;

	AFB_API_NOTICE(pcmCopyHandle->api, "capture muted");
}

static void readResume(AlsaPcmCopyHandleT * pcmCopyHandle) {

	// undeaf it
	pcmCopyHandle->pollFds[1].fd = pcmCopyHandle->saveFd;
	snd_pcm_prepare(pcmCopyHandle->pcmIn->handle);
	snd_pcm_start(pcmCopyHandle->pcmIn->handle);
	AFB_API_NOTICE(pcmCopyHandle->api, "capture unmuted");
}


static void *readThreadEntry(void *handle) {
#define LOOP_TIMEOUT_MSEC	10*1000 /* 10 seconds */

    AlsaPcmCopyHandleT *pcmCopyHandle = (AlsaPcmCopyHandleT*) handle;
    pcmCopyHandle->tid = (int) syscall(SYS_gettid);
	int ix;

    AFB_API_NOTICE(pcmCopyHandle->api,
                  "%s :%s/%d Started, muted=%d",
                  __func__, pcmCopyHandle->info, pcmCopyHandle->tid, pcmCopyHandle->pcmIn->mute);

	struct pollfd * eventFd =  &pcmCopyHandle->pollFds[0];
	struct pollfd * framePfds = &pcmCopyHandle->pollFds[1];

	eventFd->events  = POLLIN | POLLHUP;

	for (ix = 0; ix <pcmCopyHandle->nbPcmFds-1; ix++) {
		framePfds[ix].events = POLLIN | POLLHUP;
	}

   	bool muted = pcmCopyHandle->pcmIn->mute;

  	if (muted)
   		readSuspend(pcmCopyHandle);


    /* loop until end */
    for (;;) {

  	   	int err = poll(pcmCopyHandle->pollFds, pcmCopyHandle->nbPcmFds, LOOP_TIMEOUT_MSEC);
    	if (err < 0) {
    		AFB_API_ERROR(pcmCopyHandle->api, "%s: poll err %s", __func__, strerror(errno));
    		continue;
    	}

    	if (err == 0) {
    		/* timeout */
//			AFB_API_DEBUG(pcmCopyHandle->api, "%s(%s) alive, mute %d", __func__, pcmCopyHandle->pcmIn->cid.cardid, muted );
    		continue;
    	}

		// handle the incoming events/mute order
		if ((eventFd->revents & EPOLLIN) != 0) {
			PcmCopyEvent event;

			size_t ret = read(eventFd->fd, &event, sizeof(event));
			if (ret <= 0)
				continue;

			switch (event.eventType) {
			case PCM_COPY_MUTE:
				if (!muted) {
					readSuspend(pcmCopyHandle);
					muted = true;
				}
				break;
			case PCM_COPY_UNMUTE:
				if (muted) {
					readResume(pcmCopyHandle);
					muted = false;
				};
				break;
			case PCM_COPY_END:
				AFB_API_DEBUG(pcmCopyHandle->api, "%s ending -> EXIT", __func__);
				goto done;
				break;
			case PCM_COPY_LAST:
			default:
				AFB_API_ERROR(pcmCopyHandle->api, "%s: Unexpected event 0x%x", __func__, event.eventType);
				break;
    		}
    		continue;
    	}

		unsigned short revents = 0;

		int res = snd_pcm_poll_descriptors_revents(pcmCopyHandle->pcmIn->handle, framePfds, pcmCopyHandle->nbPcmFds, &revents);

		if (res == -ENODEV) {
    		sleep(1);
    		continue;
    	}

		if (revents & POLLHUP) {
    		AFB_API_NOTICE(pcmCopyHandle->api, "Frame POLLHUP");
    		continue;
    	}

		AlsaPcmReadCB(pcmCopyHandle);
    }
done:
	pthread_exit(0);

	return NULL;
}


static void *writeThreadEntry(void *handle) {
    AlsaPcmCopyHandleT *pcmCopyHandle = (AlsaPcmCopyHandleT*) handle;
	snd_pcm_t * pcmOut = pcmCopyHandle->pcmOut->handle;

	alsa_ringbuf_t * rbuf = pcmCopyHandle->rbuf;

	snd_pcm_status_t *pcmOutStatus;
	snd_pcm_uframes_t pcmOutSize;

	snd_pcm_sframes_t threshold;

	snd_pcm_status_alloca(&pcmOutStatus);
	snd_pcm_status(pcmOut, pcmOutStatus);
	pcmOutSize = snd_pcm_status_get_avail_max(pcmOutStatus);

	const char * cardid = pcmCopyHandle->pcmOut->cid.cardid;

	/* This threshold is the expected space available in the hw output buffer
	 * The aim is to wait to have a significant amount of space, in order to
	 * avoid to write to the device too often, or take a very small amount of
	 * frames from the ring buffer. So basically, this saves some CPU load */

	threshold = pcmOutSize / 3;

	for (;;) {

		sem_wait(&pcmCopyHandle->sem);

		while (true) {

			if (pcmCopyHandle->ending) {
				AFB_API_DEBUG(pcmCopyHandle->api, "%s: ending -> EXIT", __func__);
				goto done;
			}

			snd_pcm_sframes_t used, nbWritten;
			snd_pcm_sframes_t availOut = snd_pcm_avail(pcmOut);

			if (availOut < 0) {
				if (availOut == -EPIPE) {
					AFB_API_DEBUG(pcmCopyHandle->api, "%s: write update EPIPE", cardid);
					xrun(pcmOut, (int)availOut);
					continue;
				}
				if (availOut == -ESTRPIPE) {
					AFB_API_DEBUG(pcmCopyHandle->api, "%s: write update ESTRPIPE", cardid);
					suspend(pcmOut, (int)availOut);
					continue;
				}
			}

			// no space for output
			if (availOut <= threshold) {
				usleep(500);
				continue;
			}

			pthread_mutex_lock(&pcmCopyHandle->mutex);
			used = alsa_ringbuf_frames_used(rbuf);
			if (used <= 0) {
				pthread_mutex_unlock(&pcmCopyHandle->mutex);
				break; // will wait again
			}

			if (used > availOut)
				used = availOut;

			char buf[used*pcmCopyHandle->frame_size];
			alsa_ringbuf_frames_pop(rbuf, buf, used);
			pthread_mutex_unlock(&pcmCopyHandle->mutex);

			nbWritten = snd_pcm_writei( pcmOut, buf, used);
			if (nbWritten <= 0) {
				if (nbWritten == -EPIPE) {
					int err = xrun(pcmOut, (int)nbWritten);
					AFB_API_DEBUG(pcmCopyHandle->api, "XXX %s write EPIPE (%d), recov %d",
							pcmCopyHandle->pcmOut->cid.cardid, 	++pcmCopyHandle->write_err_count , err);

					continue;
				} else if (nbWritten == -ESTRPIPE) {
					AFB_API_DEBUG(pcmCopyHandle->api, "XXX write ESTRPIPE");
					break;
				}
				AFB_API_DEBUG(pcmCopyHandle->api, "Unhandled error %s", strerror(errno));
				break;
			}

		}

	}
done:
   	pthread_exit(0);
   	return NULL;
}


PUBLIC int AlsaPcmCopyMuteSignal(SoftMixerT *mixer, AlsaPcmCtlT *pcmIn, bool mute) {

	PcmCopyEvent event;
	event.eventType = mute?PCM_COPY_MUTE:PCM_COPY_UNMUTE;
	ssize_t ret = write(pcmIn->eventFd, &event, sizeof(event));
	(void) ret;
	return 0;
}

static int AlsaPcmCopyEndSignal(SoftMixerT *mixer, AlsaPcmCtlT *pcmIn) {
	PcmCopyEvent event;
	event.eventType = PCM_COPY_END;
	ssize_t ret = write(pcmIn->eventFd, &event, sizeof(event));
	(void) ret;
	return 0;
}

static int AlsaPcmCopyEnd(SoftMixerT *mixer, AlsaPcmCopyHandleT * handle) {

	handle->ending = true;
	// wake up the reader through the eventfd
	AlsaPcmCopyEndSignal(mixer, handle->pcmIn);
	// wake up the writer through the wait semaphore
	sem_post(&handle->sem);

	return 0;
}

PUBLIC int AlsaPcmCopyStop(SoftMixerT *mixer, AlsaPcmCopyHandleT * handle) {

	AFB_API_DEBUG(mixer->api, "%s: Stopping copy threads of %s", __func__, handle->stream->uid);

	AlsaPcmCopyEnd(mixer, handle);

	if (pthread_join(handle->wthread, NULL) != 0)
		AFB_API_DEBUG(mixer->api, "%s: Failed to join write thread", __func__);

	if (pthread_join(handle->rthread, NULL) != 0)
		AFB_API_DEBUG(mixer->api, "%s: Failed to join read thread", __func__);

	AFB_API_DEBUG(mixer->api, "%s: Copy threads of %s are STOPPED", __func__, handle->stream->uid);

	sem_destroy(&handle->sem);

	alsa_ringbuf_free(handle->rbuf);

	free(handle->pollFds);
	free(handle);

	return 0;

}

PUBLIC int AlsaPcmCopyStart(SoftMixerT *mixer, AlsaStreamAudioT *stream, AlsaPcmCtlT *pcmIn, AlsaPcmCtlT *pcmOut, AlsaPcmHwInfoT * opts) {
    char string[32];
    int error;
	AlsaPcmCopyHandleT *cHandle=NULL;

    // Fulup need to check https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m___direct.html

    AlsaDumpPcmInfo(mixer,"PcmIn",pcmIn->handle);
    AlsaDumpPcmInfo(mixer,"PcmOut",pcmOut->handle);

    /* remember configuration of capture */
    pcmIn->params = (AlsaPcmHwInfoT*)malloc(sizeof(AlsaPcmHwInfoT));
    if (!pcmIn->params) {
		SOFTMIXER_NOMEM(mixer->api);
		goto OnErrorExit;
    }
    memcpy(pcmIn->params, opts, sizeof(AlsaPcmHwInfoT));

    pcmOut->params = (AlsaPcmHwInfoT*)malloc(sizeof(AlsaPcmHwInfoT));
    if (!pcmOut->params) {
		SOFTMIXER_NOMEM(mixer->api);
		goto OnErrorExit;
    }
    memcpy(pcmOut->params, opts, sizeof(AlsaPcmHwInfoT));

    pcmIn->mixer = mixer;
    pcmOut->mixer = mixer;

	AFB_API_DEBUG(mixer->api, "%s: Configure CAPTURE PCM", __func__);

    // prepare PCM for capture and replay
    error = AlsaPcmConf(mixer, pcmIn, SND_PCM_STREAM_CAPTURE);
    if (error) {
    	AFB_API_ERROR(mixer->api, "%s: PCM configuration for capture failed", __func__);
    	goto OnErrorExit;
    }

	AFB_API_DEBUG(mixer->api, "%s: Configure PLAYBACK PCM", __func__);

    // input and output should match
    error = AlsaPcmConf(mixer, pcmOut, SND_PCM_STREAM_PLAYBACK);
    if (error) {
    	AFB_API_ERROR(mixer->api, "%s: PCM configuration for playback failed", __func__);
    	goto OnErrorExit;
    }

    // Prepare PCM for usage
    if ((error = snd_pcm_prepare(pcmOut->handle)) < 0) {
        AFB_API_ERROR(mixer->api, "%s: Fail to prepare PLAYBACK PCM=%s error=%s", __func__, ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error));
        goto OnErrorExit;
    };

    // Prepare PCM for usage
    if ((error = snd_pcm_prepare(pcmIn->handle)) < 0) {
        AFB_API_ERROR(mixer->api, "%s: Fail to prepare CAPTURE PCM=%s error=%s", __func__, ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error));
        goto OnErrorExit;
    };

    // Start PCM
    if ((error = snd_pcm_start(pcmOut->handle)) < 0) {
        AFB_API_ERROR(mixer->api, "%s: Fail to start PLAYBACK PCM=%s error=%s", __func__, ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error));
        goto OnErrorExit;
    };

    // Start PCM
    if ((error = snd_pcm_start(pcmIn->handle)) < 0) {
        AFB_API_ERROR(mixer->api, "%s: Fail to start CAPTURE PCM=%s error=%s", __func__, ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error));
        goto OnErrorExit;
    };

	cHandle = calloc(1, sizeof(AlsaPcmCopyHandleT));
	if (cHandle == NULL) {
		SOFTMIXER_NOMEM(mixer->api);
		goto OnErrorExit;
	}

    cHandle->info = "pcmCpy";
    cHandle->pcmIn = pcmIn;
    cHandle->pcmOut = pcmOut;
    cHandle->api = mixer->api;
    cHandle->channels = opts->channels;
	cHandle->stream = stream;

	cHandle->frame_size = (snd_pcm_format_physical_width(opts->format) / 8) * opts->channels;
	AFB_API_DEBUG(mixer->api, "%s: Frame size is %zu", __func__, cHandle->frame_size);
	AFB_API_DEBUG(mixer->api, "%s: Buffer delay is %d ms", __func__, stream->delayms);

	snd_pcm_uframes_t nbFrames = (stream->delayms * opts->rate)/1000;

    cHandle->rbuf = alsa_ringbuf_new(nbFrames, cHandle->frame_size);
	if (!cHandle->rbuf) {
		SOFTMIXER_NOMEM(mixer->api);
		goto OnErrorExit;
	}

    cHandle->read_err_count  = 0;
    cHandle->write_err_count = 0;

	AFB_API_DEBUG(mixer->api, "%s Copy buffer: nbframes is %zu", __func__, nbFrames);

    // get FD poll descriptor for capture PCM
    int pcmInCount = snd_pcm_poll_descriptors_count(pcmIn->handle);
    if (pcmInCount <= 0) {
        AFB_API_ERROR(mixer->api,
                     "%s: Fail pcmIn=%s get fds count error=%s",
                     __func__, ALSA_PCM_UID(pcmIn->handle, string), snd_strerror(error));
        goto OnErrorExit;
	}

	cHandle->nbPcmFds = pcmInCount+1;
	cHandle->pollFds = (struct pollfd *) malloc((cHandle->nbPcmFds)*sizeof(struct pollfd));
	if (cHandle->pollFds == NULL){
		SOFTMIXER_NOMEM(mixer->api);
    	goto OnErrorExit;
    }

	if ((error = snd_pcm_poll_descriptors(pcmIn->handle, cHandle->pollFds+1, pcmInCount)) < 0) {
        AFB_API_ERROR(mixer->api,
                     "%s: Fail pcmIn=%s get pollfds error=%s",
                     __func__, ALSA_PCM_UID(pcmOut->handle, string), snd_strerror(error));
        goto OnErrorExit;
    };

    // create the mute pipe
	int eventFdPipe[2];
	error = pipe(eventFdPipe);
    if (error < 0) {
        AFB_API_ERROR(mixer->api,
                     "Unable to create the mute signaling pipe");
        goto OnErrorExit;
    }

	struct pollfd * eventPollFd = &cHandle->pollFds[0];
    // read end
	eventPollFd->fd = eventFdPipe[0];
	eventPollFd->events = POLLIN;
	eventPollFd->revents = 0;

    // write end
	pcmIn->eventFd = eventFdPipe[1];

    error = sem_init(&cHandle->sem, 0 , 0);
    if (error < 0) {
    	AFB_API_ERROR(mixer->api,
    	                     "%s Fail initialize loop semaphore pcmIn=%s err=%d",
    	                     __func__, ALSA_PCM_UID(pcmIn->handle, string), error);
    	goto OnErrorExit;
    }

    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);

    error = pthread_mutex_init(&cHandle->mutex, &attr);
    if (error < 0) {
    	AFB_API_ERROR(mixer->api,
    	                     "%s Fail initialize loop mutex pcmIn=%s err=%d",
    	                     __func__, ALSA_PCM_UID(pcmIn->handle, string), error);
    }


    /// start a thread for writing
    if ((error = pthread_create(&cHandle->wthread, NULL, &readThreadEntry, cHandle)) < 0) {
        AFB_API_ERROR(mixer->api,
                     "%s Fail create write thread pcmOut=%s err=%d",
                     __func__, ALSA_PCM_UID(pcmOut->handle, string), error);
        goto OnErrorExit;
    }

    // start a thread for reading
    if ((error = pthread_create(&cHandle->rthread, NULL, &writeThreadEntry, cHandle)) < 0) {
        AFB_API_ERROR(mixer->api,
                     "%s Fail create read thread pcmIn=%s err=%d",
                     __func__, ALSA_PCM_UID(pcmIn->handle, string), error);
        goto OnErrorExit;
    }

    // request a higher priority for each audio stream thread
    struct sched_param params;
    params.sched_priority = sched_get_priority_max(SCHED_FIFO);

    error= pthread_setschedparam(cHandle->rthread, SCHED_FIFO, &params);
    if (error) {
        AFB_API_WARNING(mixer->api,
                       "%s: Failed to increase stream read thread priority pcmIn=%s err=%s",
                       __func__, ALSA_PCM_UID(pcmIn->handle, string), strerror(error));
    }

    error= pthread_setschedparam(cHandle->wthread, SCHED_FIFO, &params);
    if (error) {
        AFB_API_WARNING(mixer->api,
                       "%s: Failed to increase stream write thread priority pcmIn=%s err=%s",
                       __func__, ALSA_PCM_UID(pcmOut->handle, string), strerror(error));
    }

	stream->copy = cHandle;

    return 0;

OnErrorExit:
    AFB_API_ERROR(mixer->api, "%s: - pcmIn=%s" , __func__, ALSA_PCM_UID(pcmIn->handle, string));
    AFB_API_ERROR(mixer->api, "%s: - pcmOut=%s", __func__, ALSA_PCM_UID(pcmOut->handle, string));

	if (cHandle &&cHandle->pollFds) {
		free (cHandle->pollFds);
		cHandle->pollFds = NULL;
	}

	if (cHandle)
		free(cHandle);

    return -1;
}