Changeset 6094


Ignore:
Timestamp:
Oct 17, 2019 7:02:50 AM (5 years ago)
Author:
ming
Message:

Fixed #1778: Support for Voice Processing IO Audio Unit on Mac

Location:
pjproject/trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • pjproject/trunk/pjmedia/include/pjmedia/audiodev.h

    r6001 r6094  
    3838 * @defgroup s2_audio_device_reference Audio Device API Reference 
    3939 * @ingroup audio_device_api 
    40  * @brief API Reference 
     40 * @brief Documentation and API Reference 
    4141 * @{ 
     42 * 
     43 * @section ec_sec Hardware/Built-in Echo Cancellation 
     44 * 
     45 * On some platforms, audio device comes with built-in echo cancellation 
     46 * feature. This is usually done based on specific hardware configuration, 
     47 * such as the use of multiple microphones and/or a known fixed distance 
     48 * between the capture and playback device, in order to precalculate the 
     49 * echo time distance. Because of this, when using the hardware EC, 
     50 * users may not get the freedom to select their own audio devices. 
     51 * This is applicable for Mac (users must use default audio devices) and 
     52 * iOS (users must use the same built-in audio device). 
     53 * 
     54 * In PJMEDIA, applications wishing to use sofware echo instead can pass 
     55 * PJMEDIA_ECHO_USE_SW_ECHO when calling pjmedia_snd_port_create2(). 
    4256 */ 
    4357 
  • pjproject/trunk/pjmedia/src/pjmedia-audiodev/coreaudio_dev.m

    r5463 r6094  
    101101 
    102102    AudioComponent               io_comp; 
     103    pj_bool_t                    has_vpio; 
    103104    struct stream_list           streams; 
    104105}; 
     
    271272    if (cf->io_comp == NULL) 
    272273        return PJMEDIA_EAUD_INIT; // cannot find IO unit; 
     274 
     275    desc.componentSubType = kAudioUnitSubType_VoiceProcessingIO; 
     276    if (AudioComponentFindNext(NULL, &desc) != NULL) 
     277        cf->has_vpio = PJ_TRUE; 
    273278 
    274279    status = ca_factory_refresh(f); 
     
    632637            } 
    633638        } 
     639        if (cf->has_vpio) { 
     640            cdi->info.caps |= PJMEDIA_AUD_DEV_CAP_EC; 
     641        } 
    634642 
    635643        cf->dev_count++; 
    636644 
    637645        PJ_LOG(4, (THIS_FILE, " dev_id %d: %s  (in=%d, out=%d) %dHz", 
    638                i, 
     646               cdi->dev_id, 
    639647               cdi->info.name, 
    640648               cdi->info.input_count, 
     
    10011009} 
    10021010 
     1011/* Copy 16-bit signed int samples to a destination buffer, which can be 
     1012 * either 16-bit signed int, or float. 
     1013 */ 
     1014static void copy_samples(void *dst, unsigned *dst_pos, unsigned dst_elmt_size, 
     1015                         pj_int16_t *src, unsigned nsamples) 
     1016{ 
     1017   if (dst_elmt_size == sizeof(pj_int16_t)) { 
     1018        /* Destination is also 16-bit signed int. */ 
     1019        pjmedia_copy_samples((pj_int16_t*)dst + *dst_pos, src, nsamples); 
     1020   } else { 
     1021        /* Convert it first to float. */ 
     1022        unsigned i; 
     1023        float *fdst = (float *)dst; 
     1024 
     1025        pj_assert(dst_elmt_size == sizeof(Float32)); 
     1026 
     1027        for (i = 0; i< nsamples; i++) { 
     1028            /* Value needs to be between -1.0 to 1.0 */ 
     1029            fdst[*dst_pos + i] = (float)src[i] / 32768.0f; 
     1030        } 
     1031   } 
     1032   *dst_pos += nsamples; 
     1033} 
     1034 
    10031035static OSStatus output_renderer(void                       *inRefCon, 
    10041036                                AudioUnitRenderActionFlags *ioActionFlags, 
     
    10111043    pj_status_t status = 0; 
    10121044    unsigned nsamples_req = inNumberFrames * stream->param.channel_count; 
    1013     pj_int16_t *output = ioData->mBuffers[0].mData; 
     1045    void *output = ioData->mBuffers[0].mData; 
     1046    unsigned elmt_size = ioData->mBuffers[0].mDataByteSize / inNumberFrames; 
     1047    unsigned output_pos = 0; 
    10141048 
    10151049    pj_assert(!stream->quit_flag); 
     
    10371071        /* samples buffered >= requested by sound device */ 
    10381072        if (stream->play_buf_count >= nsamples_req) { 
    1039             pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf, 
    1040                                 nsamples_req); 
     1073            copy_samples(output, &output_pos, elmt_size, 
     1074                         stream->play_buf, nsamples_req); 
    10411075            stream->play_buf_count -= nsamples_req; 
    10421076            pjmedia_move_samples(stream->play_buf, 
     
    10491083 
    10501084        /* samples buffered < requested by sound device */ 
    1051         pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf, 
    1052                             stream->play_buf_count); 
     1085        copy_samples(output, &output_pos, elmt_size, 
     1086                     stream->play_buf, stream->play_buf_count); 
    10531087        nsamples_req -= stream->play_buf_count; 
    1054         output = (pj_int16_t*)output + stream->play_buf_count; 
    10551088        stream->play_buf_count = 0; 
    10561089    } 
     
    10671100 
    10681101        if (nsamples_req >= stream->param.samples_per_frame) { 
    1069             frame.buf = output; 
     1102            /* If the output buffer is 16-bit signed int, we can 
     1103             * directly use the supplied buffer. 
     1104             */ 
     1105            if (elmt_size == sizeof(pj_int16_t)) { 
     1106                frame.buf = (pj_int16_t *)output + output_pos; 
     1107            } else { 
     1108                frame.buf = stream->play_buf; 
     1109            } 
    10701110            status = (*stream->play_cb)(stream->user_data, &frame); 
    10711111            if (status != PJ_SUCCESS) 
     
    10761116 
    10771117            nsamples_req -= stream->param.samples_per_frame; 
    1078             output = (pj_int16_t*)output + stream->param.samples_per_frame; 
     1118            if (elmt_size == sizeof(pj_int16_t)) { 
     1119                output_pos += stream->param.samples_per_frame; 
     1120            } else { 
     1121                copy_samples(output, &output_pos, elmt_size, stream->play_buf, 
     1122                             stream->param.samples_per_frame); 
     1123            } 
    10791124        } else { 
    10801125            frame.buf = stream->play_buf; 
     
    10861131                pj_bzero(frame.buf, frame.size); 
    10871132 
    1088             pjmedia_copy_samples((pj_int16_t*)output, stream->play_buf, 
    1089                                 nsamples_req); 
     1133            copy_samples(output, &output_pos, elmt_size, 
     1134                         stream->play_buf, nsamples_req); 
    10901135            stream->play_buf_count = stream->param.samples_per_frame - 
    10911136                                     nsamples_req; 
     
    12931338                                       &enable, 
    12941339                                       sizeof(enable)); 
    1295         if (ostatus != noErr) { 
     1340        if (ostatus != noErr && !strm->param.ec_enabled) { 
    12961341            PJ_LOG(4, (THIS_FILE, 
    12971342                   "Warning: cannot enable IO of capture device %d", 
     
    13081353                                           &enable, 
    13091354                                           sizeof(enable)); 
    1310             if (ostatus != noErr) { 
     1355            if (ostatus != noErr && !strm->param.ec_enabled) { 
    13111356                PJ_LOG(4, (THIS_FILE, 
    13121357                       "Warning: cannot disable IO of capture device %d", 
     
    13271372                                       &enable, 
    13281373                                       sizeof(enable)); 
    1329         if (ostatus != noErr) { 
     1374        if (ostatus != noErr && !strm->param.ec_enabled) 
     1375        { 
    13301376            PJ_LOG(4, (THIS_FILE, 
    13311377                   "Warning: cannot enable IO of playback device %d", 
     
    13361382 
    13371383#if COREAUDIO_MAC 
    1338     PJ_LOG(5, (THIS_FILE, "Opening device %d", dev_id)); 
    1339     ostatus = AudioUnitSetProperty(*io_unit, 
    1340                                    kAudioOutputUnitProperty_CurrentDevice, 
    1341                                    kAudioUnitScope_Global, 
    1342                                    0, 
    1343                                    &dev_id, 
    1344                                    sizeof(dev_id)); 
    1345     if (ostatus != noErr) { 
    1346         return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); 
     1384    if (!strm->param.ec_enabled) { 
     1385        /* When using VPIO on Mac, we shouldn't set the current device. 
     1386         * Doing so will cause getting the buffer size later to fail 
     1387         * with kAudioUnitErr_InvalidProperty error (-10879). 
     1388         */ 
     1389        PJ_LOG(4, (THIS_FILE, "Setting current device %d", dev_id)); 
     1390        ostatus = AudioUnitSetProperty(*io_unit, 
     1391                                       kAudioOutputUnitProperty_CurrentDevice, 
     1392                                       kAudioUnitScope_Global, 
     1393                                       0, 
     1394                                       &dev_id, 
     1395                                       sizeof(dev_id)); 
     1396        if (ostatus != noErr) { 
     1397            PJ_LOG(3, (THIS_FILE, "Failed setting current device %d, " 
     1398                       "error: %d", dev_id, ostatus)); 
     1399            return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); 
     1400        } 
    13471401    } 
    13481402#endif 
     
    13531407        UInt32 size; 
    13541408 
    1355         /* 
    1356          * Keep the sample rate from the device, otherwise we will confuse 
    1357          * AUHAL 
    1358          */ 
    1359         size = sizeof(AudioStreamBasicDescription); 
    1360         ostatus = AudioUnitGetProperty(*io_unit, 
    1361                                        kAudioUnitProperty_StreamFormat, 
    1362                                        kAudioUnitScope_Input, 
    1363                                        1, 
    1364                                        &deviceFormat, 
    1365                                        &size); 
    1366         if (ostatus != noErr) { 
    1367             return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); 
    1368         } 
    1369         strm->streamFormat.mSampleRate = deviceFormat.mSampleRate; 
     1409        if (!strm->param.ec_enabled) { 
     1410            /* Keep the sample rate from the device, otherwise we will confuse 
     1411             * AUHAL. 
     1412             */ 
     1413            size = sizeof(AudioStreamBasicDescription); 
     1414            ostatus = AudioUnitGetProperty(*io_unit, 
     1415                                           kAudioUnitProperty_StreamFormat, 
     1416                                           kAudioUnitScope_Input, 
     1417                                           1, 
     1418                                           &deviceFormat, 
     1419                                           &size); 
     1420            if (ostatus != noErr) { 
     1421                PJ_LOG(3, (THIS_FILE, "Failed getting stream format of device" 
     1422                                      " %d, error: %d", dev_id, ostatus)); 
     1423                return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); 
     1424            } 
     1425            strm->streamFormat.mSampleRate = deviceFormat.mSampleRate; 
     1426        } 
    13701427#endif 
    13711428 
     
    13811438                                       sizeof(strm->streamFormat)); 
    13821439        if (ostatus != noErr) { 
     1440            PJ_LOG(3, (THIS_FILE, "Failed setting stream format of device %d" 
     1441                                  ", error: %d", dev_id, ostatus)); 
    13831442            return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); 
    13841443        } 
     
    13951454        if (ostatus == noErr) { 
    13961455            if (strm->streamFormat.mSampleRate != deviceFormat.mSampleRate) { 
     1456                PJ_LOG(4, (THIS_FILE, "Creating audio resample from %d to %d", 
     1457                           (int)deviceFormat.mSampleRate, 
     1458                           (int)strm->streamFormat.mSampleRate)); 
    13971459                pj_status_t rc = create_audio_resample(strm, &deviceFormat); 
    1398                 if (PJ_SUCCESS != rc) 
     1460                if (PJ_SUCCESS != rc) { 
     1461                    PJ_LOG(3, (THIS_FILE, "Failed creating resample %d", 
     1462                               rc)); 
    13991463                    return rc; 
     1464                } 
    14001465            } 
    14011466        } else { 
     1467            PJ_LOG(3, (THIS_FILE, "Failed getting stream format of device %d" 
     1468                                  " (2), error: %d", dev_id, ostatus)); 
    14021469            return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); 
    14031470        } 
     
    14071474    if (dir & PJMEDIA_DIR_PLAYBACK) { 
    14081475        AURenderCallbackStruct output_cb; 
     1476        AudioStreamBasicDescription streamFormat = strm->streamFormat; 
    14091477 
    14101478        /* Set the stream format */ 
     1479#if COREAUDIO_MAC 
     1480        if (strm->param.ec_enabled) { 
     1481            /* When using VPIO on Mac, we need to use float data. Using 
     1482             * signed integer will generate no errors, but strangely, 
     1483             * no sound will be played. 
     1484             */ 
     1485            streamFormat.mFormatFlags      = kLinearPCMFormatFlagIsFloat | 
     1486                                             kLinearPCMFormatFlagIsPacked; 
     1487            streamFormat.mBitsPerChannel   = sizeof(float) * 8; 
     1488            streamFormat.mBytesPerFrame    = streamFormat.mChannelsPerFrame * 
     1489                                             streamFormat.mBitsPerChannel / 8; 
     1490            streamFormat.mBytesPerPacket   = streamFormat.mBytesPerFrame * 
     1491                                             streamFormat.mFramesPerPacket; 
     1492        } 
     1493#endif 
    14111494        ostatus = AudioUnitSetProperty(*io_unit, 
    14121495                                       kAudioUnitProperty_StreamFormat, 
    14131496                                       kAudioUnitScope_Input, 
    14141497                                       0, 
    1415                                        &strm->streamFormat, 
    1416                                        sizeof(strm->streamFormat)); 
     1498                                       &streamFormat, 
     1499                                       sizeof(streamFormat)); 
    14171500        if (ostatus != noErr) { 
    14181501            PJ_LOG(4, (THIS_FILE, 
     
    14311514                                       sizeof(output_cb)); 
    14321515        if (ostatus != noErr) { 
     1516            PJ_LOG(3, (THIS_FILE, "Failed setting render callback %d", 
     1517                       ostatus)); 
    14331518            return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); 
    14341519        } 
     
    14371522        strm->play_buf = (pj_int16_t*)pj_pool_alloc(strm->pool, 
    14381523                         strm->param.samples_per_frame * 
     1524                         (strm->param.ec_enabled? 2: 1) * 
    14391525                         strm->param.bits_per_sample >> 3); 
    14401526        if (!strm->play_buf) 
     
    14581544                      kAudioOutputUnitProperty_SetInputCallback, 
    14591545                      kAudioUnitScope_Global, 
    1460                       0, 
     1546                      1, 
    14611547                      &input_cb, 
    14621548                      sizeof(input_cb)); 
    14631549        if (ostatus != noErr) { 
     1550            PJ_LOG(3, (THIS_FILE, "Failed setting input callback %d", 
     1551                       ostatus)); 
    14641552            return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); 
    14651553        } 
    14661554 
    14671555#if COREAUDIO_MAC 
     1556        /* Set device's buffer frame size */ 
     1557        size = sizeof(UInt32); 
     1558        buf_size = (20 * strm->streamFormat.mSampleRate) / 1000; 
     1559        ostatus = AudioUnitSetProperty (*io_unit, 
     1560                                        kAudioDevicePropertyBufferFrameSize, 
     1561                                        kAudioUnitScope_Global, 
     1562                                        0, 
     1563                                        &buf_size, 
     1564                                        size); 
     1565        if (ostatus != noErr) { 
     1566            PJ_LOG(3, (THIS_FILE, "Failed setting buffer size %d", 
     1567                       ostatus)); 
     1568            return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); 
     1569        } 
     1570 
    14681571        /* Get device's buffer frame size */ 
    1469         size = sizeof(UInt32); 
    14701572        ostatus = AudioUnitGetProperty(*io_unit, 
    14711573                                       kAudioDevicePropertyBufferFrameSize, 
     
    14741576                                       &buf_size, 
    14751577                                       &size); 
    1476         if (ostatus != noErr) 
    1477         { 
     1578        if (ostatus != noErr) { 
     1579            PJ_LOG(3, (THIS_FILE, "Failed getting buffer size %d", 
     1580                       ostatus)); 
    14781581            return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); 
    14791582        } 
     
    14891592        ab->mNumberChannels = strm->streamFormat.mChannelsPerFrame; 
    14901593        ab->mDataByteSize = buf_size * ab->mNumberChannels * 
    1491                             strm->param.bits_per_sample >> 3; 
     1594                            strm->streamFormat.mBitsPerChannel >> 3; 
    14921595        ab->mData = pj_pool_alloc(strm->pool, 
    14931596                                  ab->mDataByteSize); 
     
    15221625    ostatus = AudioUnitInitialize(*io_unit); 
    15231626    if (ostatus != noErr) { 
     1627        PJ_LOG(3, (THIS_FILE, "Failed initializing audio unit %d", ostatus)); 
    15241628        return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); 
    15251629    } 
     
    15631667    strm->streamFormat.mChannelsPerFrame = param->channel_count; 
    15641668    strm->streamFormat.mBytesPerFrame    = strm->streamFormat.mChannelsPerFrame 
    1565                                            * strm->param.bits_per_sample >> 3; 
     1669                                           * strm->streamFormat.mBitsPerChannel 
     1670                                           / 8; 
    15661671    strm->streamFormat.mFramesPerPacket  = 1; 
    15671672    strm->streamFormat.mBytesPerPacket   = strm->streamFormat.mBytesPerFrame * 
     
    15801685    } 
    15811686    if (param->flags & PJMEDIA_AUD_DEV_CAP_EC) { 
    1582         ca_stream_set_cap(&strm->base, 
    1583                           PJMEDIA_AUD_DEV_CAP_EC, 
    1584                           &param->ec_enabled); 
     1687        status = ca_stream_set_cap(&strm->base, 
     1688                                   PJMEDIA_AUD_DEV_CAP_EC, 
     1689                                   &param->ec_enabled); 
     1690        if (status != PJ_SUCCESS) 
     1691            strm->param.ec_enabled = PJ_FALSE; 
    15851692    } else { 
    15861693        pj_bool_t ec = PJ_FALSE; 
     
    15891696    } 
    15901697 
     1698#if !TARGET_OS_IPHONE 
     1699    if (strm->param.ec_enabled && 
     1700        param->rec_id != PJMEDIA_AUD_DEFAULT_CAPTURE_DEV && 
     1701        param->play_id != PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV) 
     1702    { 
     1703        PJ_LOG(4, (THIS_FILE, "Warning: audio device id settings are " 
     1704                              "ignored when using VPIO")); 
     1705    } 
     1706#endif 
     1707 
    15911708    strm->io_units[0] = strm->io_units[1] = NULL; 
    1592     if (param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK && 
    1593         param->rec_id == param->play_id) 
     1709    if ((param->dir == PJMEDIA_DIR_CAPTURE_PLAYBACK && 
     1710         param->rec_id == param->play_id) || 
     1711        (param->flags & PJMEDIA_AUD_DEV_CAP_EC && param->ec_enabled)) 
    15941712    { 
    1595         /* If both input and output are on the same device, only create 
    1596          * one audio unit to interface with the device. 
     1713        /* If both input and output are on the same device or if EC is enabled, 
     1714         * only create one audio unit to interface with the device(s). 
    15971715         */ 
    15981716        status = create_audio_unit(cf->io_comp, 
     
    18912009    PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); 
    18922010 
    1893 #if COREAUDIO_MAC 
    1894     if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING && 
    1895         (strm->param.dir & PJMEDIA_DIR_PLAYBACK)) 
    1896     { 
    1897         OSStatus ostatus; 
    1898         Float32 volume = *(unsigned*)pval; 
    1899  
    1900         /* Output volume setting */ 
    1901         volume /= 100.0; 
    1902         ostatus = AudioUnitSetProperty (strm->io_units[1] ? strm->io_units[1] : 
    1903                                         strm->io_units[0], 
    1904                                         kAudioDevicePropertyVolumeScalar, 
    1905                                         kAudioUnitScope_Output, 
    1906                                         0, 
    1907                                         &volume, 
    1908                                         sizeof(Float32)); 
    1909         if (ostatus != noErr) { 
    1910             return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); 
    1911         } 
    1912         strm->param.output_vol = *(unsigned*)pval; 
    1913         return PJ_SUCCESS; 
    1914     } 
    1915  
    1916 #else 
    19172011    if (cap==PJMEDIA_AUD_DEV_CAP_EC) { 
    19182012        AudioComponentDescription desc; 
     
    19212015        desc.componentType = kAudioUnitType_Output; 
    19222016        desc.componentSubType = (*(pj_bool_t*)pval)? 
    1923         kAudioUnitSubType_VoiceProcessingIO : 
    1924         kAudioUnitSubType_RemoteIO; 
     2017                                kAudioUnitSubType_VoiceProcessingIO : 
     2018#if COREAUDIO_MAC 
     2019                                kAudioUnitSubType_HALOutput; 
     2020#else 
     2021                                kAudioUnitSubType_RemoteIO; 
     2022#endif 
    19252023        desc.componentManufacturer = kAudioUnitManufacturer_Apple; 
    19262024        desc.componentFlags = 0; 
     
    19352033        PJ_LOG(4, (THIS_FILE, "Using %s audio unit", 
    19362034                   (desc.componentSubType == 
    1937                     kAudioUnitSubType_RemoteIO? "RemoteIO": 
    1938                     "VoiceProcessingIO"))); 
     2035                    kAudioUnitSubType_VoiceProcessingIO? 
     2036                    "VoiceProcessingIO": "default"))); 
    19392037         
    19402038        return PJ_SUCCESS; 
    1941     } else if ((cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && 
     2039    } 
     2040#if COREAUDIO_MAC 
     2041    else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING && 
     2042        (strm->param.dir & PJMEDIA_DIR_PLAYBACK)) 
     2043    { 
     2044        OSStatus ostatus; 
     2045        Float32 volume = *(unsigned*)pval; 
     2046 
     2047        /* Output volume setting */ 
     2048        volume /= 100.0; 
     2049        ostatus = AudioUnitSetProperty (strm->io_units[1] ? strm->io_units[1] : 
     2050                                        strm->io_units[0], 
     2051                                        kAudioDevicePropertyVolumeScalar, 
     2052                                        kAudioUnitScope_Output, 
     2053                                        0, 
     2054                                        &volume, 
     2055                                        sizeof(Float32)); 
     2056        if (ostatus != noErr) { 
     2057            return PJMEDIA_AUDIODEV_ERRNO_FROM_COREAUDIO(ostatus); 
     2058        } 
     2059        strm->param.output_vol = *(unsigned*)pval; 
     2060        return PJ_SUCCESS; 
     2061    } 
     2062 
     2063#else 
     2064    else if ((cap==PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY && 
    19422065         (strm->param.dir & PJMEDIA_DIR_CAPTURE)) || 
    19432066        (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY && 
  • pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h

    r6035 r6094  
    65466546 
    65476547    /** 
    6548      * Echo canceller options (see #pjmedia_echo_create()) 
     6548     * Echo canceller options (see #pjmedia_echo_create()). 
     6549     * Specify PJMEDIA_ECHO_USE_SW_ECHO here if application wishes 
     6550     * to use software echo canceller instead of device EC. 
    65496551     * 
    65506552     * Default: 0. 
  • pjproject/trunk/pjsip/include/pjsua2/endpoint.hpp

    r6081 r6094  
    808808 
    809809    /** 
    810      * Echo canceller options (see pjmedia_echo_create()) 
     810     * Echo canceller options (see pjmedia_echo_create()). 
     811     * Specify PJMEDIA_ECHO_USE_SW_ECHO here if application wishes 
     812     * to use software echo canceller instead of device EC. 
    811813     * 
    812814     * Default: 0. 
Note: See TracChangeset for help on using the changeset viewer.