#include"libaudiomgr_linux.h" #include "core_time.h" #include #include #ifndef RVC_PA_ADJUST_LATENCY_PROTOCOL_VERSION #define RVC_PA_ADJUST_LATENCY_PROTOCOL_VERSION 13 #endif #ifndef RVC_MAX_VOLUME #define RVC_MAX_VOLUME 65536 #endif static int sample_index = 0; // From pulsecore/macro.h #define pa_memzero(x,l) (memset((x), 0, (l))) #define pa_zero(x) (pa_memzero(&(x), sizeof(x))) static uint32_t latency_ms = 20; // requested initial latency in milisec: 0 use max static pa_usec_t latency = 0; //real latency in usec (for timestamping) static int sink_index = 0; static int source_index = 0; AudioMgrImpl::AudioMgrImpl(audiomgr_callback_t* pCallback) { m_audio_context = NULL; memcpy(&m_callback, pCallback, sizeof(audiomgr_callback_t)); } AudioMgrImpl::~AudioMgrImpl() { if (NULL != m_audio_context){ if (NULL != m_audio_context->list_input_devices) { free(m_audio_context->list_input_devices); m_audio_context->list_input_devices = NULL; } if (NULL != m_audio_context->list_output_devices) { free(m_audio_context->list_output_devices); m_audio_context->list_output_devices = NULL; } if (NULL != m_audio_context->capture_buff) { free(m_audio_context->capture_buff); m_audio_context->capture_buff = NULL; } free(m_audio_context); m_audio_context = NULL; } } int AudioMgrImpl::audio_mgr_destroy() { delete this; return 0; } /* * init pulseaudio api * args: * audio_ctx - pointer to audio context data * * asserts: * audio_ctx is not null * * returns: error code (0 = E_OK) */ int AudioMgrImpl::audio_init_pulseaudio() { /*assertions*/ assert(NULL != m_audio_context); if (pa_get_devicelist() < 0) { audiolog( "pulse audio failed to get audio device list from pulse server\n"); return -1; } return 0; } /* * pa_mainloop will call this function when it's ready to tell us * about a source (input). * Since we're not threading when listing devices, * there's no need for mutexes on the devicelist structure * args: * c - pointer to pulse context * l - pointer to source info * eol - end of list * data - pointer to user data (audio context) * * asserts: * none * * returns: none */ static void pa_sourcelist_cb(pa_context* c, const pa_source_info* l, int eol, void* data) { rvc_audio_context_t* audio_ctx = (rvc_audio_context_t*)data; int channels = 1; /* * If eol is set to a positive number, * you're at the end of the list */ if (eol > 0) return; source_index++; if (l->sample_spec.channels < 1) channels = 1; else channels = l->sample_spec.channels; double my_latency = 0.0; int verbosity = 1; if (my_latency <= 0.0) my_latency = (double)latency_ms / 1000; if (l->monitor_of_sink == PA_INVALID_INDEX) { audio_ctx->num_input_dev++; /*add device to list*/ audio_ctx->list_input_devices = (rvc_audio_device_t*)realloc(audio_ctx->list_input_devices, audio_ctx->num_input_dev * sizeof(rvc_audio_device_t)); if (audio_ctx->list_input_devices == NULL) { exit(-1); } /*fill device data*/ audio_ctx->list_input_devices[audio_ctx->num_input_dev - 1].id = l->index; /*saves dev id*/ strncpy(audio_ctx->list_input_devices[audio_ctx->num_input_dev - 1].name, l->name, MAX_PATH-1); strncpy(audio_ctx->list_input_devices[audio_ctx->num_input_dev - 1].description, l->description, MAX_PATH-1); audio_ctx->list_input_devices[audio_ctx->num_input_dev - 1].channels = channels; audio_ctx->list_input_devices[audio_ctx->num_input_dev - 1].samprate = l->sample_spec.rate; audio_ctx->list_input_devices[audio_ctx->num_input_dev - 1].low_latency = my_latency; /*in seconds*/ audio_ctx->list_input_devices[audio_ctx->num_input_dev - 1].high_latency = my_latency; /*in seconds*/ } } void PaSetVolumeCallback(pa_context* c, int success, void* /*pThis*/) { if (!success) { //printf("failed to set volume\n"); } else { //printf("success to set volume\n"); } } static void pa_setsourcevolume_cb(pa_context* c, const pa_source_info* l, int eol, void* data) { rvc_volume_set_t* audio_volume = (rvc_volume_set_t*)data; /* * If eol is set to a positive number, * you're at the end of the list */ if (eol > 0) return; if (l->monitor_of_sink == PA_INVALID_INDEX) { if (strstr(l->description, audio_volume->strdevicename)) { pa_cvolume cvolumes; // Set the same volume for all channels pa_cvolume_set(&cvolumes, l->channel_map.channels, audio_volume->ivolume); pa_operation* paOperation = NULL; if (!(paOperation = pa_context_set_source_volume_by_index(c, l->index, &cvolumes, PaSetVolumeCallback, NULL))) { audio_volume->volume_flag = 1; } pa_operation_unref(paOperation); } } } static void pa_setsinkvolume_cb(pa_context* c, const pa_sink_info* l, int eol, void* data) { rvc_volume_set_t* audio_volume = (rvc_volume_set_t*)data; /* * If eol is set to a positive number, * you're at the end of the list */ if (eol > 0) return; if (strstr(l->description, audio_volume->strdevicename)) { pa_cvolume cvolumes; // Set the same volume for all channels pa_cvolume_set(&cvolumes, l->channel_map.channels, audio_volume->ivolume); pa_operation* paOperation = NULL; if (!(paOperation = pa_context_set_sink_volume_by_index(c, l->index, &cvolumes, PaSetVolumeCallback, NULL))) { audio_volume->volume_flag = 1; } pa_operation_unref(paOperation); } } static void pa_sourcevolume_cb(pa_context* c, const pa_source_info* l, int eol, void* data) { rvc_audio_volume_t* audio_volume = (rvc_audio_volume_t*)data; /* * If eol is set to a positive number, * you're at the end of the list */ if (eol > 0) return; if (l->monitor_of_sink == PA_INVALID_INDEX) { if (strstr(l->description, audio_volume->strdevicename)){ audio_volume->cvolumes = l->volume; audio_volume->volume_flag = 1; } } } static void pa_sinkvolume_cb(pa_context* c, const pa_sink_info* l, int eol, void* userdata) { rvc_audio_volume_t* audio_volume = (rvc_audio_volume_t*)userdata; /* * If eol is set to a positive number, * you're at the end of the list */ if (eol > 0) return; if (strstr(l->description, audio_volume->strdevicename)) { audio_volume->cvolumes = l->volume; audio_volume->volume_flag = 1; } } /* * pa_mainloop will call this function when it's ready to tell us * about a source (input). * This callback is pretty much identical to the previous * but it will only print the output devices * args: * c - pointer to pulse context * l - pointer to sink info * eol - end of list * data - pointer to user data (audio context) * * asserts: * none * * returns: none */ static void pa_sinklist_cb(pa_context* c, const pa_sink_info* l, int eol, void* userdata) { rvc_audio_context_t* audio_ctx = (rvc_audio_context_t*)userdata; /* * If eol is set to a positive number, * you're at the end of the list */ if (eol > 0) return; sink_index++; double my_latency = 0.0; int verbosity = 1; if (my_latency <= 0.0) my_latency = (double)latency_ms / 1000; audio_ctx->num_output_dev++; /*add device to list*/ audio_ctx->list_output_devices = (rvc_audio_device_t*)realloc(audio_ctx->list_output_devices, audio_ctx->num_output_dev * sizeof(rvc_audio_device_t)); if (audio_ctx->list_output_devices == NULL) { printf("AUDIO: FATAL memory allocation failure (pa_sinklist_cb): %s\n", strerror(errno)); exit(-1); } /*fill device data*/ audio_ctx->list_output_devices[audio_ctx->num_output_dev - 1].id = l->index; /*saves dev id*/ strncpy(audio_ctx->list_output_devices[audio_ctx->num_output_dev - 1].name, l->name, MAX_PATH-1); strncpy(audio_ctx->list_output_devices[audio_ctx->num_output_dev - 1].description, l->description, MAX_PATH-1); audio_ctx->list_output_devices[audio_ctx->num_output_dev - 1].channels = l->channel_map.channels; audio_ctx->list_output_devices[audio_ctx->num_output_dev - 1].samprate = l->sample_spec.rate; audio_ctx->list_output_devices[audio_ctx->num_output_dev - 1].low_latency = my_latency; /*in seconds*/ audio_ctx->list_output_devices[audio_ctx->num_output_dev - 1].high_latency = my_latency; /*in seconds*/ } /* * This callback gets called when our context changes state. * We really only care about when it's ready or if it has failed * args: * c -pointer to pulse context * data - pointer to user data * * asserts: * none * * retuns: none */ static void pa_state_cb(pa_context* c, void* data) { pa_context_state_t state; int* pa_ready = (int*)data; state = pa_context_get_state(c); switch (state) { // These are just here for reference case PA_CONTEXT_UNCONNECTED: break; case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: break; case PA_CONTEXT_FAILED: case PA_CONTEXT_TERMINATED: *pa_ready = 2; break; case PA_CONTEXT_READY: *pa_ready = 1; break; default: break; } } /* * clean up and disconnect * args: * pa_ctx - pointer to pulse context * pa_ml - pointer to pulse mainloop * * asserts: * none * * returns: * none */ static void finish(pa_context* pa_ctx, pa_mainloop* pa_ml) { /* clean up and disconnect */ pa_context_disconnect(pa_ctx); pa_context_unref(pa_ctx); pa_mainloop_free(pa_ml); } /* * iterate the main loop until all devices are listed * args: * audio_ctx - pointer to audio context * * asserts: * audio_ctx is not null * * returns: error code */ int AudioMgrImpl::pa_get_devicelist() { /*assertions*/ assert(m_audio_context != NULL); /* Define our pulse audio loop and connection variables */ pa_mainloop* pa_ml; pa_mainloop_api* pa_mlapi; pa_operation* pa_op = NULL; pa_context* pa_ctx; /* We'll need these state variables to keep track of our requests */ int state = 0; int pa_ready = 0; /* Create a mainloop API and connection to the default server */ pa_ml = pa_mainloop_new(); pa_mlapi = pa_mainloop_get_api(pa_ml); pa_ctx = pa_context_new(pa_mlapi, "getDevices"); /* This function connects to the pulse server */ if (pa_context_connect(pa_ctx, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { audiolog("AUDIO: PULSE - unable to connect to server: pa_context_connect failed!"); finish(pa_ctx, pa_ml); return -1; } /* * This function defines a callback so the server will tell us * it's state. * Our callback will wait for the state to be ready. * The callback will modify the variable to 1 so we know when we * have a connection and it's ready. * If there's an error, the callback will set pa_ready to 2 */ pa_context_set_state_callback(pa_ctx, pa_state_cb, &pa_ready); /* * Now we'll enter into an infinite loop until we get the data * we receive or if there's an error */ for (;;) { /* * We can't do anything until PA is ready, * so just iterate the mainloop and continue */ if (pa_ready == 0) { pa_mainloop_iterate(pa_ml, 1, NULL); continue; } ///* We couldn't get a connection to the server, so exit out */ if (pa_ready == 2) { finish(pa_ctx, pa_ml); return -1; } /* * At this point, we're connected to the server and ready * to make requests */ switch (state) { /* State 0: we haven't done anything yet */ case 0: /* * This sends an operation to the server. * pa_sinklist_cb is our callback function and a pointer * o our devicelist will be passed to the callback * (audio_ctx) The operation ID is stored in the * pa_op variable */ pa_op = pa_context_get_sink_info_list( pa_ctx, pa_sinklist_cb, (void*)m_audio_context); /* Update state for next iteration through the loop */ state++; break; case 1: /* * Now we wait for our operation to complete. * When it's complete our pa_output_devicelist is * filled out, and we move along to the next state */ if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) { pa_operation_unref(pa_op); /* * Now we perform another operation to get the * source(input device) list just like before. * This time we pass a pointer to our input structure */ pa_op = pa_context_get_source_info_list( pa_ctx, pa_sourcelist_cb, (void*)m_audio_context); /* Update the state so we know what to do next */ state++; } break; case 2: if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) { /* * Now we're done, * clean up and disconnect and return */ pa_operation_unref(pa_op); finish(pa_ctx, pa_ml); return 0; } break; default: /* We should never see this state */ audiolog("AUDIO: Pulse audio in state %d", state); return -1; } /* * Iterate the main loop and go again. The second argument is whether * or not the iteration should block until something is ready to be * done. Set it to zero for non-blocking. */ pa_mainloop_iterate(pa_ml, 1, NULL); } finish(pa_ctx, pa_ml); return 0; } /* * get the number of available audio devices * * args: * binput - is input device * asserts: * audio_ctx is not null * * returns: number of listed audio devices */ int AudioMgrImpl::audio_get_device_count(bool binput) { /*assertions*/ assert(m_audio_context != NULL); if (binput){ return m_audio_context->num_input_dev; } else { return m_audio_context->num_output_dev; } } /* * get the audio device referenced by index * args: * index - index of audio device * * asserts: * audio_ctx is not null * * returns: audio device referenced by index */ rvc_audio_device_t* AudioMgrImpl::audio_get_input_device(int index) { /*assertions*/ assert(m_audio_context != NULL); if (index >= m_audio_context->num_input_dev){ index = m_audio_context->num_input_dev - 1; } if (index < 0){ index = 0; } return &m_audio_context->list_input_devices[index]; } /* * get the output audio device referenced by index * args: * index - index of output audio device * * asserts: * audio_ctx is not null * * returns: output audio device referenced by index */ rvc_audio_device_t* AudioMgrImpl::audio_get_output_device( int index) { /*assertions*/ assert(m_audio_context != NULL); if (index >= m_audio_context->num_output_dev){ index = m_audio_context->num_output_dev - 1; } if (index < 0){ index = 0; } return &m_audio_context->list_output_devices[index]; } int AudioMgrImpl::audio_get_device_name(char* pstrbuf, size_t ulen, bool binput, int index) { int iret = -1; //模拟立体声 const char stranalogstereo[] = { 0x20,0xe6,0xa8,0xa1,0xe6,0x8b,0x9f,0xe7,0xab,0x8b,0xe4,0xbd,0x93,0xe5,0xa3,0xb0,0 }; //数字立体声 const char strdigitalstereo[] = { 0x20,0xe6,0x95,0xb0,0xe5,0xad,0x97,0xe7,0xab,0x8b,0xe4,0xbd,0x93,0xe5,0xa3,0xb0,0 }; //立体声 const char strstereo[] = { 0x20,0xe7,0xab,0x8b,0xe4,0xbd,0x93,0xe5,0xa3,0xb0,0 }; //多声道 const char strmultistereo[] = { 0x20,0xe5,0xa4,0x9a,0xe5,0xa3,0xb0,0xe9,0x81,0x93,0 }; rvc_audio_device_t* audio_device = NULL; if (binput){ audio_device = audio_get_input_device(index); } else{ audio_device = audio_get_output_device(index); } size_t unamelen = strlen(audio_device->description); if (ulen > unamelen){ memcpy(pstrbuf, audio_device->description, ulen); char* pindex = NULL; if ((pindex = strstr(pstrbuf, stranalogstereo))||(pindex = strstr(pstrbuf, strdigitalstereo))){ *pindex = 0; } if ((pindex = strstr(pstrbuf, strstereo)) || (pindex = strstr(pstrbuf, strmultistereo))) { *pindex = 0; } iret = 0; } return iret; } int AudioMgrImpl::audio_get_device_id(const char* pstrname, bool binput) { int iret = -1; if (NULL == pstrname){ return iret; } int icount = audio_get_device_count(binput); if (icount > 0){ for (int index = 0; index < icount; index++){ rvc_audio_device_t* audio_device = NULL; if (binput) { audio_device = audio_get_input_device(index); } else { audio_device = audio_get_output_device(index); } if (NULL != audio_device){ if (strstr(audio_device->description, pstrname)){ iret = index; break; } } } } return iret; } rvc_audio_device_t* AudioMgrImpl::audio_get_device_infos(bool binput, int index) { rvc_audio_device_t* audio_device = NULL; if (binput) { audio_device = audio_get_input_device(index); } else { audio_device = audio_get_output_device(index); } return audio_device; } int AudioMgrImpl::audio_get_device_volume(int* ivolume, const char* pstrname, bool binput) { audiopulse_get_device_volume(ivolume, pstrname, binput); float fvol = (*ivolume) * 1000 / RVC_MAX_VOLUME; int ivol = fvol; int ilast = ivol % 10; int inum = ivol / 10; if (ilast >= 5) { inum++; } *ivolume = inum; return 0; } int AudioMgrImpl::audiopulse_get_device_volume(int* ivolume, const char* pstrname, bool binput) { int iret = -1; pa_mainloop* pa_ml; pa_mainloop_api* pa_mlapi; pa_operation* pa_op = NULL; pa_context* pa_ctx; /* We'll need these state variables to keep track of our requests */ int state = 0; int pa_ready = 0; rvc_audio_volume_t rvc_volume = { 0 }; strcpy(rvc_volume.strdevicename, pstrname); /* Create a mainloop API and connection to the default server */ pa_ml = pa_mainloop_new(); pa_mlapi = pa_mainloop_get_api(pa_ml); pa_ctx = pa_context_new(pa_mlapi, "get audio volume"); /* This function connects to the pulse server */ if (pa_context_connect(pa_ctx, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { audiolog("AUDIO: PULSE - unable to connect to server: pa_context_connect failed"); finish(pa_ctx, pa_ml); return -1; } /* * This function defines a callback so the server will tell us * it's state. * Our callback will wait for the state to be ready. * The callback will modify the variable to 1 so we know when we * have a connection and it's ready. * If there's an error, the callback will set pa_ready to 2 */ pa_context_set_state_callback(pa_ctx, pa_state_cb, &pa_ready); /* * Now we'll enter into an infinite loop until we get the data * we receive or if there's an error */ for (;;) { /* * We can't do anything until PA is ready, * so just iterate the mainloop and continue */ if (pa_ready == 0) { pa_mainloop_iterate(pa_ml, 1, NULL); continue; } ///* We couldn't get a connection to the server, so exit out */ if (pa_ready == 2) { finish(pa_ctx, pa_ml); return -1; } /* * At this point, we're connected to the server and ready * to make requests */ if (0 == state){ if (binput) { pa_op = pa_context_get_source_info_list( pa_ctx, pa_sourcevolume_cb, (void*)& rvc_volume); } else { pa_op = pa_context_get_sink_info_list( pa_ctx, pa_sinkvolume_cb, (void*)& rvc_volume); } state++; } if (rvc_volume.volume_flag){ *ivolume = rvc_volume.cvolumes.values[0]; pa_operation_unref(pa_op); break; } if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) { /* * Now we're done, * clean up and disconnect and return */ pa_operation_unref(pa_op); finish(pa_ctx, pa_ml); return 0; } /* * Iterate the main loop and go again. The second argument is whether * or not the iteration should block until something is ready to be * done. Set it to zero for non-blocking. */ pa_mainloop_iterate(pa_ml, 1, NULL); } finish(pa_ctx, pa_ml); return iret; } int AudioMgrImpl::audio_set_device_volume(int ivolume, const char* pstrname, bool binput) { int ivol = ivolume * RVC_MAX_VOLUME / 100; return audiopulse_set_device_volume(ivol, pstrname, binput); } int AudioMgrImpl::audiopulse_set_device_volume(int ivolume, const char* pstrname, bool binput) { int iret = -1; pa_mainloop* pa_ml; pa_mainloop_api* pa_mlapi; pa_operation* pa_op = NULL; pa_context* pa_ctx; /* We'll need these state variables to keep track of our requests */ int state = 0; int pa_ready = 0; rvc_volume_set_t rvc_volume = { 0 }; strcpy(rvc_volume.strdevicename, pstrname); rvc_volume.ivolume = ivolume; /* Create a mainloop API and connection to the default server */ pa_ml = pa_mainloop_new(); pa_mlapi = pa_mainloop_get_api(pa_ml); pa_ctx = pa_context_new(pa_mlapi, "set audio volume"); /* This function connects to the pulse server */ if (pa_context_connect(pa_ctx, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { audiolog("AUDIO: PULSE - unable to connect to server: pa_context_connect failed"); finish(pa_ctx, pa_ml); return -1; } /* * This function defines a callback so the server will tell us * it's state. * Our callback will wait for the state to be ready. * The callback will modify the variable to 1 so we know when we * have a connection and it's ready. * If there's an error, the callback will set pa_ready to 2 */ pa_context_set_state_callback(pa_ctx, pa_state_cb, &pa_ready); /* * Now we'll enter into an infinite loop until we get the data * we receive or if there's an error */ for (;;) { /* * We can't do anything until PA is ready, * so just iterate the mainloop and continue */ if (pa_ready == 0) { pa_mainloop_iterate(pa_ml, 1, NULL); continue; } ///* We couldn't get a connection to the server, so exit out */ if (pa_ready == 2) { finish(pa_ctx, pa_ml); return -1; } /* * At this point, we're connected to the server and ready * to make requests */ if (0 == state) { if (binput) { pa_op = pa_context_get_source_info_list( pa_ctx, pa_setsourcevolume_cb, (void*)&rvc_volume); } else { pa_op = pa_context_get_sink_info_list( pa_ctx, pa_setsinkvolume_cb, (void*)&rvc_volume); } state++; } if (rvc_volume.volume_flag) { pa_operation_unref(pa_op); break; } if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) { /* * Now we're done, * clean up and disconnect and return */ pa_operation_unref(pa_op); finish(pa_ctx, pa_ml); return 0; } /* * Iterate the main loop and go again. The second argument is whether * or not the iteration should block until something is ready to be * done. Set it to zero for non-blocking. */ pa_mainloop_iterate(pa_ml, 1, NULL); } finish(pa_ctx, pa_ml); return iret; } int AudioMgrImpl::audio_mgr_initialize() { int iret = -1; m_audio_context = (rvc_audio_context_t*)calloc(1, sizeof(rvc_audio_context_t)); if (NULL == m_audio_context){ audiolog("%s:%d couldn't allocate audio context.", __FUNCTION__, __LINE__); return iret; } iret = audio_init_pulseaudio(); /*force a valid number of channels*/ if (m_audio_context->channels > 2) { m_audio_context->channels = 2; } return iret; } /* * close and clean audio context for pulse audio api * * asserts: * none * * returns: none */ int AudioMgrImpl::audio_close_pulseaudio() { int iret = -1; if (m_audio_context == NULL){ return iret; } if (m_audio_context->stream_flag == AUDIO_STRM_ON) { stop_audio_capture(); } if (NULL != m_audio_context->list_input_devices) { free(m_audio_context->list_input_devices); m_audio_context->list_input_devices = NULL; } if (NULL != m_audio_context->list_output_devices){ free(m_audio_context->list_output_devices); m_audio_context->list_output_devices = NULL; } if (NULL != m_audio_context->capture_buff) { free(m_audio_context->capture_buff); m_audio_context->capture_buff = NULL; } free(m_audio_context); m_audio_context = NULL; iret = 0; return iret; } /* * stop and join the main loop iteration thread * * asserts: * audio_ctx is not null * * returns: error code */ int AudioMgrImpl::stop_audio_capture() { /*assertions*/ assert(m_audio_context != NULL); m_audio_context->stream_flag = AUDIO_STRM_OFF; if (0 != __THREAD_JOIN(m_readthread)){ audiolog("read thread join failed!"); } return 0; } int AudioMgrImpl::audio_mgr_terminate() { int iret = -1; /*assertions*/ assert(m_audio_context != NULL); /* thread must be joined before destroying the mutex * so no need to unlock before destroying it */ iret = audio_close_pulseaudio(); return iret; } /* * update pulse audio latency * args: * s - pointer to pa_stream * * asserts: * none * * returns:none */ static void get_latency(pa_stream* s) { pa_usec_t l; int negative; pa_stream_get_timing_info(s); if (pa_stream_get_latency(s, &l, &negative) != 0) { return; } latency = l; /*can only be negative in monitoring streams*/ } /* * audio record callback * args: * s - pointer to pa_stream * length - buffer length * data - pointer to user data * * asserts: * none * * returns: none */ static void stream_request_cb(pa_stream* s, size_t length, void* data) { rvc_audio_context_t* audio_ctx = (rvc_audio_context_t*)data; if (audio_ctx->channels == 0){ return; } if (audio_ctx->samprate == 0){ return; } int64_t ts = 0; while (pa_stream_readable_size(s) > 0) { const void* inputBuffer; size_t length; /*read from stream*/ if (pa_stream_peek(s, &inputBuffer, &length) < 0){ return; } if (length == 0){ return; /*buffer is empty*/ } get_latency(s); ts = ns_time_monotonic() - (latency * 1000); if (audio_ctx->last_ts <= 0) audio_ctx->last_ts = ts; audio_ctx->audio_param.on_audio_callback(inputBuffer, length, audio_ctx->audio_param.user_data); pa_stream_drop(s); /*clean the samples*/ } } /* * Iterate the main loop while recording is on. * This function runs under it's own thread called by audio_pulse_start * args: * data - pointer to user data (audio context) * * asserts: * data is not null * * returns: pointer to error code */ void* pulse_read_audio(void* data) { AudioMgrImpl* audio_mgr = (AudioMgrImpl*)data; assert(audio_mgr != NULL); rvc_audio_context_t* audio_ctx = audio_mgr->audio_get_context(); /*assertions*/ assert(audio_ctx != NULL); pa_mainloop* pa_ml; pa_mainloop_api* pa_mlapi; pa_buffer_attr bufattr; pa_stream* recordstream = NULL; // pulse audio stream pa_context* pa_ctx; //pulse context pa_sample_spec ss; pa_stream_flags_t flags = PA_STREAM_NOFLAGS; int32_t pastream_flag = (int32_t)PA_STREAM_NOFLAGS; int r; int pa_ready = 0; /* Create a mainloop API and connection to the default server */ pa_ml = pa_mainloop_new(); pa_mlapi = pa_mainloop_get_api(pa_ml); pa_ctx = pa_context_new(pa_mlapi, "rvc pulse API"); if (pa_context_connect(pa_ctx, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { audio_mgr->audiolog("AUDIO: PULSE - unable to connect to server: pa_context_connect failed."); finish(pa_ctx, pa_ml); return ((void*)-1); } /* * This function defines a callback so the server will tell us it's state. * Our callback will wait for the state to be ready. The callback will * modify the variable to 1 so we know when we have a connection and it's * ready. * If there's an error, the callback will set pa_ready to 2 */ pa_context_set_state_callback(pa_ctx, pa_state_cb, &pa_ready); /* * This function defines a time event callback (called every TIME_EVENT_USEC) */ //pa_context_rttime_new(pa_ctx, pa_rtclock_now() + TIME_EVENT_USEC, time_event_callback, NULL); /* * We can't do anything until PA is ready, so just iterate the mainloop * and continue */ while (pa_ready == 0){ pa_mainloop_iterate(pa_ml, 1, NULL); } if (pa_ready == 2){ finish(pa_ctx, pa_ml); return ((void*)-1); } /* set the sample spec (frame rate, channels and format) */ ss.rate = audio_ctx->samprate; ss.channels = audio_ctx->channels; ss.format = audio_ctx->eformat; recordstream = pa_stream_new(pa_ctx, "Record", &ss, NULL); if (!recordstream) audio_mgr->audiolog("AUDIO: (pulse audio) pa_stream_new failed (chan:%d rate:%d)", ss.channels, ss.rate); /* define the callbacks */ pa_stream_set_read_callback(recordstream, stream_request_cb, (void*)audio_ctx); // Set properties of the record buffer pa_zero(bufattr); /* optimal value for all is (uint32_t)-1 ~= 2 sec */ bufattr.maxlength = (uint32_t)-1; bufattr.prebuf = (uint32_t)-1; bufattr.minreq = (uint32_t)-1; if (audio_ctx->latency > 0){ bufattr.fragsize = bufattr.tlength = pa_usec_to_bytes((audio_ctx->latency * 1000) * PA_USEC_PER_MSEC, &ss); uint32_t uvsersion = pa_context_get_protocol_version(pa_ctx); if (uvsersion >= RVC_PA_ADJUST_LATENCY_PROTOCOL_VERSION) { pastream_flag |= PA_STREAM_ADJUST_LATENCY; } //audio_mgr->audiolog("pa protocol version is %d.", uvsersion); } else { bufattr.fragsize = bufattr.tlength = (uint32_t)-1; } pastream_flag |= PA_STREAM_INTERPOLATE_TIMING; pastream_flag |= PA_STREAM_AUTO_TIMING_UPDATE; char* dev = audio_ctx->list_input_devices[audio_ctx->device].name; //audio_mgr->audiolog("AUDIO: (pulse audio) connecting to device %s (channels %d rate %d)", dev, ss.channels, ss.rate); r = pa_stream_connect_record(recordstream, dev, &bufattr, (pa_stream_flags_t)pastream_flag); if (r < 0) { //audio_mgr->audiolog("AUDIO: (pulse audio) skip latency adjustment"); /* * Old pulse audio servers don't like the ADJUST_LATENCY flag, * so retry without that */ r = pa_stream_connect_record(recordstream, dev, &bufattr, pa_stream_flags_t((int32_t)PA_STREAM_INTERPOLATE_TIMING | (int32_t)PA_STREAM_AUTO_TIMING_UPDATE)); } if (r < 0) { audio_mgr->audiolog("AUDIO: (pulse audio) pa_stream_connect_record failed"); finish(pa_ctx, pa_ml); return ((void*)-1); } get_latency(recordstream); /* * Iterate the main loop while streaming. The second argument is whether * or not the iteration should block until something is ready to be * done. Set it to zero for non-blocking. */ while (audio_ctx->stream_flag == AUDIO_STRM_ON){ pa_mainloop_iterate(pa_ml, 1, NULL); } pa_stream_set_read_callback(recordstream, NULL, NULL); //audio_mgr->audiolog("AUDIO: (pulse audio) stream terminated(%i)", audio_ctx->stream_flag); pa_stream_disconnect(recordstream); pa_stream_unref(recordstream); finish(pa_ctx, pa_ml); return ((void*)0); } int AudioMgrImpl::set_audio_capture_params(audiocap_param_t* param) { assert(param != NULL); audio_set_pulseaudio_device(param->ideviceid); audio_set_pulsecap_params(param); return 0; } /* * set audio device * index - device index to set * * asserts: * audio_ctx is not null * * returns: none */ int AudioMgrImpl::audio_set_pulseaudio_device(int index) { /*assertions*/ assert(m_audio_context != NULL); if (index >= m_audio_context->num_input_dev) { m_audio_context->device = m_audio_context->num_input_dev - 1; } else if (index >= 0) { m_audio_context->device = index; } return 0; } int AudioMgrImpl::audio_set_pulsecap_params(audiocap_param_t* param) { assert(param != NULL); m_audio_context->channels = param->ichannels; if (m_audio_context->channels > 2) { m_audio_context->channels = 2;/*limit it to stereo input*/ } m_audio_context->samprate = param->isamprate; m_audio_context->eformat = pa_sample_format_t(param->isampleformat); m_audio_context->audio_param.on_audio_callback = param->on_audio_callback; m_audio_context->audio_param.user_data = param->user_data; m_audio_context->latency = param->flatency; } /* * set the current latency * * asserts: * audio_ctx is not null * * returns: none */ void AudioMgrImpl::audio_set_latency(double latency) { /*assertions*/ assert(m_audio_context != NULL); m_audio_context->latency = latency; } rvc_audio_context_t* AudioMgrImpl::audio_get_context() { return m_audio_context; } audiomgr_callback_t* AudioMgrImpl::audio_get_callback() { return &m_callback; } int AudioMgrImpl::start_audio_capture() { /*assertions*/ assert(m_audio_context != NULL); m_audio_context->stream_flag = AUDIO_STRM_ON; /* start audio capture thread */ if (__THREAD_CREATE(&m_readthread, pulse_read_audio, this)){ audiolog("AUDIO: (pulse audio) read thread creation failed"); m_audio_context->stream_flag = AUDIO_STRM_OFF; return (-1); } return 0; } void AudioMgrImpl::audiolog(const char* fmt, ...) { if (m_callback.debug) { va_list arg; va_start(arg, fmt); if(*m_callback.debug){ (*m_callback.debug)(m_callback.user_data, fmt, arg); } va_end(arg); } }