static void thread_func(void *userdata) { struct userdata *u = userdata; pa_proplist *proplist; pa_assert(u); pa_log_debug("Thread starting up"); pa_thread_mq_install(u->thread_mq); proplist = tunnel_new_proplist(u); u->context = pa_context_new_with_proplist(u->thread_mainloop_api, "PulseAudio", proplist); pa_proplist_free(proplist); if (!u->context) { pa_log("Failed to create libpulse context"); goto fail; } if (u->cookie_file && pa_context_load_cookie_from_file(u->context, u->cookie_file) != 0) { pa_log_error("Can not load cookie file!"); goto fail; } pa_context_set_state_callback(u->context, context_state_cb, u); if (pa_context_connect(u->context, u->remote_server, PA_CONTEXT_NOAUTOSPAWN, NULL) < 0) { pa_log("Failed to connect libpulse context"); goto fail; } for (;;) { int ret; if (pa_mainloop_iterate(u->thread_mainloop, 1, &ret) < 0) { if (ret == 0) goto finish; else goto fail; } if (PA_UNLIKELY(u->sink->thread_info.rewind_requested)) pa_sink_process_rewind(u->sink, 0); if (u->connected && pa_stream_get_state(u->stream) == PA_STREAM_READY && PA_SINK_IS_LINKED(u->sink->thread_info.state)) { size_t writable; writable = pa_stream_writable_size(u->stream); if (writable > 0) { if(u->transcode.encoding != -1) { pa_memchunk memchunk; const void *p; size_t nbBytes; unsigned char *cbits; pa_sink_render_full(u->sink, u->transcode.frame_size*u->transcode.channels*u->transcode.sample_size, &memchunk); pa_assert(memchunk.length > 0); pa_assert(memchunk.length >= u->transcode.frame_size*u->transcode.channels); pa_log_debug("received memchunk length: %zu bytes", memchunk.length ); /* we have new data to write */ p = pa_memblock_acquire(memchunk.memblock); nbBytes = pa_transcode_encode(&u->transcode, (uint8_t*) p + memchunk.index, &cbits); pa_log_debug("encoded length: %zu bytes", nbBytes); /* TODO: Use pa_stream_begin_write() to reduce copying. */ ret = pa_stream_write_compressed(u->stream, (uint8_t*) cbits, nbBytes, NULL, /**< A cleanup routine for the data or NULL to request an internal copy */ 0, /** offset */ PA_SEEK_RELATIVE, u->transcode.frame_size*u->transcode.channels*u->transcode.sample_size); pa_memblock_release(memchunk.memblock); pa_memblock_unref(memchunk.memblock); if(nbBytes > 0) free(cbits); if (ret != 0) { pa_log_error("Could not write data into the stream ... ret = %i", ret); u->thread_mainloop_api->quit(u->thread_mainloop_api, TUNNEL_THREAD_FAILED_MAINLOOP); } } else { pa_memchunk memchunk; const void *p; pa_sink_render_full(u->sink, writable, &memchunk); pa_assert(memchunk.length > 0); /* we have new data to write */ p = pa_memblock_acquire(memchunk.memblock); /* TODO: Use pa_stream_begin_write() to reduce copying. */ ret = pa_stream_write(u->stream, (uint8_t*) p + memchunk.index, memchunk.length, NULL, /**< A cleanup routine for the data or NULL to request an internal copy */ 0, /** offset */ PA_SEEK_RELATIVE); pa_memblock_release(memchunk.memblock); pa_memblock_unref(memchunk.memblock); if (ret != 0) { pa_log_error("Could not write data into the stream ... ret = %i", ret); u->thread_mainloop_api->quit(u->thread_mainloop_api, TUNNEL_THREAD_FAILED_MAINLOOP); } } } } } fail: pa_asyncmsgq_post(u->thread_mq->outq, PA_MSGOBJECT(u->module->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); pa_asyncmsgq_wait_for(u->thread_mq->inq, PA_MESSAGE_SHUTDOWN); finish: if (u->stream) { pa_stream_disconnect(u->stream); pa_stream_unref(u->stream); u->stream = NULL; } if (u->context) { pa_context_disconnect(u->context); pa_context_unref(u->context); u->context = NULL; } pa_log_debug("Thread shutting down"); }
static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *memchunk) { struct userdata *u = PA_SINK(o)->userdata; switch (code) { case SINK_MESSAGE_RENDER: /* Handle the request from the JACK thread */ if (u->sink->thread_info.state == PA_SINK_RUNNING) { pa_memchunk chunk; size_t nbytes; void *p; pa_assert(offset > 0); nbytes = (size_t) offset * pa_frame_size(&u->sink->sample_spec); pa_sink_render_full(u->sink, nbytes, &chunk); p = (uint8_t*) pa_memblock_acquire(chunk.memblock) + chunk.index; pa_deinterleave(p, u->buffer, u->channels, sizeof(float), (unsigned) offset); pa_memblock_release(chunk.memblock); pa_memblock_unref(chunk.memblock); } else { unsigned c; pa_sample_spec ss; /* Humm, we're not RUNNING, hence let's write some silence */ ss = u->sink->sample_spec; ss.channels = 1; for (c = 0; c < u->channels; c++) pa_silence_memory(u->buffer[c], (size_t) offset * pa_sample_size(&ss), &ss); } u->frames_in_buffer = (jack_nframes_t) offset; u->saved_frame_time = * (jack_nframes_t*) data; u->saved_frame_time_valid = TRUE; return 0; case SINK_MESSAGE_BUFFER_SIZE: pa_sink_set_max_request_within_thread(u->sink, (size_t) offset * pa_frame_size(&u->sink->sample_spec)); return 0; case SINK_MESSAGE_ON_SHUTDOWN: pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); return 0; case PA_SINK_MESSAGE_GET_LATENCY: { jack_nframes_t l, ft, d; size_t n; /* This is the "worst-case" latency */ l = jack_port_get_total_latency(u->client, u->port[0]) + u->frames_in_buffer; if (u->saved_frame_time_valid) { /* Adjust the worst case latency by the time that * passed since we last handed data to JACK */ ft = jack_frame_time(u->client); d = ft > u->saved_frame_time ? ft - u->saved_frame_time : 0; l = l > d ? l - d : 0; } /* Convert it to usec */ n = l * pa_frame_size(&u->sink->sample_spec); *((pa_usec_t*) data) = pa_bytes_to_usec(n, &u->sink->sample_spec); return 0; } } return pa_sink_process_msg(o, code, data, offset, memchunk); }
/* Called from input thread context */ static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) { struct userdata *u; pa_source_output_assert_ref(o); pa_source_output_assert_io_context(o); pa_assert_se(u = o->userdata); if (!PA_SOURCE_OUTPUT_IS_LINKED(pa_source_output_get_state(u->source_output))) { pa_log("push when no link?"); return; } /* PUT YOUR CODE HERE TO DO SOMETHING WITH THE SOURCE DATA */ /* if uplink sink exists, pull data from there; simplify by using same length as chunk provided by source */ if(u->sink && (pa_sink_get_state(u->sink) == PA_SINK_RUNNING)) { pa_memchunk tchunk; size_t nbytes = chunk->length; pa_mix_info streams[2]; pa_memchunk target_chunk; void *target; int ch; /* Hmm, process any rewind request that might be queued up */ pa_sink_process_rewind(u->sink, 0); /* get data from the sink */ while (pa_memblockq_peek(u->sink_memblockq, &tchunk) < 0) { pa_memchunk nchunk; /* make sure we get nbytes from the sink with render_full, otherwise we cannot mix with the uplink */ pa_sink_render_full(u->sink, nbytes, &nchunk); pa_memblockq_push(u->sink_memblockq, &nchunk); pa_memblock_unref(nchunk.memblock); } pa_assert(tchunk.length == chunk->length); /* move the read pointer for sink memblockq */ pa_memblockq_drop(u->sink_memblockq, tchunk.length); /* allocate target chunk */ /* this could probably be done in-place, but having chunk as both the input and output creates issues with reference counts */ target_chunk.index = 0; target_chunk.length = chunk->length; pa_assert(target_chunk.length == chunk->length); target_chunk.memblock = pa_memblock_new(o->source->core->mempool, target_chunk.length); pa_assert( target_chunk.memblock ); /* get target pointer */ target = (void*)((uint8_t*)pa_memblock_acquire(target_chunk.memblock) + target_chunk.index); /* set-up mixing structure volume was taken care of in sink and source already */ streams[0].chunk = *chunk; for(ch=0;ch<o->sample_spec.channels;ch++) streams[0].volume.values[ch] = PA_VOLUME_NORM; /* FIXME */ streams[0].volume.channels = o->sample_spec.channels; streams[1].chunk = tchunk; for(ch=0;ch<o->sample_spec.channels;ch++) streams[1].volume.values[ch] = PA_VOLUME_NORM; /* FIXME */ streams[1].volume.channels = o->sample_spec.channels; /* do mixing */ pa_mix(streams, /* 2 streams to be mixed */ 2, target, /* put result in target chunk */ chunk->length, /* same length as input */ (const pa_sample_spec *)&o->sample_spec, /* same sample spec for input and output */ NULL, /* no volume information */ FALSE); /* no mute */ pa_memblock_release(target_chunk.memblock); pa_memblock_unref(tchunk.memblock); /* clean-up */ /* forward the data to the virtual source */ pa_source_post(u->source, &target_chunk); pa_memblock_unref(target_chunk.memblock); /* clean-up */ } else { /* forward the data to the virtual source */ pa_source_post(u->source, chunk); } }