/** Resume the audio stream by uncorking it on the server */ static void audio_resume(void) { // without this, certain versions will cause an infinite hang because // pa_stream_writable_size returns 0 always. // Note that this workaround causes A-V desync after pause if (broken_pause) reset(); cork(0); }
// Resume the audio stream by uncorking it on the server static void resume(struct ao *ao) { struct priv *priv = ao->priv; /* Without this, certain versions will cause an infinite hang because * pa_stream_writable_size returns 0 always. * Note that this workaround causes A-V desync after pause. */ if (priv->broken_pause) reset(ao); cork(ao, false); }
/** * empty_cork(): Add the number of recently corked bytes, if any, and empty a * corked socket. */ static void empty_cork(int s, const struct sockaddr *srv, socklen_t srv_len, FILE *f, int n_sent) { bytes_corked += n_sent; uncork(s); recv_write(s, srv, srv_len, f, bytes_corked); fprintf(f, "\n"); cork(s); bytes_corked = 0; }
int main(int argc, char *argv[]) { struct sockaddr *cli, *srv; int is_stream, s, cli_len, srv_len; is_xia = check_cli_params(&is_stream, argc, argv); if (is_stream) { /* XXX Implement stream support. */ printf("Stream support not implemented.\n"); exit(1); } s = any_socket(is_xia, is_stream); assert(s >= 0); cli = get_cli_addr(is_xia, argc, argv, &cli_len); assert(cli); srv = get_srv_addr(is_xia, argc, argv, &srv_len); assert(srv); any_bind(is_xia, 0, s, cli, cli_len); cork(s); while (1) { char input[512]; int n_read = read_command(input, sizeof(input)); if (n_read <= 0) break; if (is_file(input)) { if (bytes_corked) empty_cork(s, srv, srv_len, stdout, 0); datagram_process_file(s, srv, srv_len, input + 3, CORK_SIZE, CORK_TIMES, mark); printf("\n"); } else { process_text(s, srv, srv_len, input, n_read); } printf("\n"); } free(srv); free(cli); assert(!close(s)); return 0; }
// Pause the audio stream by corking it on the server static void pause(struct ao *ao) { cork(ao, true); }
static int init(struct ao *ao, char *params) { struct pa_sample_spec ss; struct pa_channel_map map; char *devarg = NULL; char *host = NULL; char *sink = NULL; const char *version = pa_get_library_version(); struct priv *priv = talloc_zero(ao, struct priv); ao->priv = priv; if (params) { devarg = strdup(params); sink = strchr(devarg, ':'); if (sink) *sink++ = 0; if (devarg[0]) host = devarg; } priv->broken_pause = false; /* not sure which versions are affected, assume 0.9.11* to 0.9.14* * known bad: 0.9.14, 0.9.13 * known good: 0.9.9, 0.9.10, 0.9.15 * To test: pause, wait ca. 5 seconds, framestep and see if MPlayer * hangs somewhen. */ if (strncmp(version, "0.9.1", 5) == 0 && version[5] >= '1' && version[5] <= '4') { mp_msg(MSGT_AO, MSGL_WARN, "[pulse] working around probably broken pause functionality,\n" " see http://www.pulseaudio.org/ticket/440\n"); priv->broken_pause = true; } ss.channels = ao->channels; ss.rate = ao->samplerate; const struct format_map *fmt_map = format_maps; while (fmt_map->mp_format != ao->format) { if (fmt_map->mp_format == AF_FORMAT_UNKNOWN) { mp_msg(MSGT_AO, MSGL_V, "AO: [pulse] Unsupported format, using default\n"); fmt_map = format_maps; break; } fmt_map++; } ao->format = fmt_map->mp_format; ss.format = fmt_map->pa_format; if (!pa_sample_spec_valid(&ss)) { mp_msg(MSGT_AO, MSGL_ERR, "AO: [pulse] Invalid sample spec\n"); goto fail; } pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_ALSA); ao->bps = pa_bytes_per_second(&ss); if (!(priv->mainloop = pa_threaded_mainloop_new())) { mp_msg(MSGT_AO, MSGL_ERR, "AO: [pulse] Failed to allocate main loop\n"); goto fail; } if (!(priv->context = pa_context_new(pa_threaded_mainloop_get_api( priv->mainloop), PULSE_CLIENT_NAME))) { mp_msg(MSGT_AO, MSGL_ERR, "AO: [pulse] Failed to allocate context\n"); goto fail; } pa_context_set_state_callback(priv->context, context_state_cb, ao); if (pa_context_connect(priv->context, host, 0, NULL) < 0) goto fail; pa_threaded_mainloop_lock(priv->mainloop); if (pa_threaded_mainloop_start(priv->mainloop) < 0) goto unlock_and_fail; /* Wait until the context is ready */ pa_threaded_mainloop_wait(priv->mainloop); if (pa_context_get_state(priv->context) != PA_CONTEXT_READY) goto unlock_and_fail; if (!(priv->stream = pa_stream_new(priv->context, "audio stream", &ss, &map))) goto unlock_and_fail; pa_stream_set_state_callback(priv->stream, stream_state_cb, ao); pa_stream_set_write_callback(priv->stream, stream_request_cb, ao); pa_stream_set_latency_update_callback(priv->stream, stream_latency_update_cb, ao); pa_buffer_attr bufattr = { .maxlength = -1, .tlength = pa_usec_to_bytes(1000000, &ss), .prebuf = -1, .minreq = -1, .fragsize = -1, }; if (pa_stream_connect_playback(priv->stream, sink, &bufattr, PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL) < 0) goto unlock_and_fail; /* Wait until the stream is ready */ pa_threaded_mainloop_wait(priv->mainloop); if (pa_stream_get_state(priv->stream) != PA_STREAM_READY) goto unlock_and_fail; pa_threaded_mainloop_unlock(priv->mainloop); free(devarg); return 0; unlock_and_fail: if (priv->mainloop) pa_threaded_mainloop_unlock(priv->mainloop); fail: if (priv->context) GENERIC_ERR_MSG(priv->context, "Init failed"); free(devarg); uninit(ao, true); return -1; } static void cork(struct ao *ao, bool pause) { struct priv *priv = ao->priv; pa_threaded_mainloop_lock(priv->mainloop); priv->retval = 0; if (!waitop(priv, pa_stream_cork(priv->stream, pause, success_cb, ao)) || !priv->retval) GENERIC_ERR_MSG(priv->context, "pa_stream_cork() failed"); } // Play the specified data to the pulseaudio server static int play(struct ao *ao, void *data, int len, int flags) { struct priv *priv = ao->priv; /* For some reason Pulseaudio behaves worse if this is done after * the write - rapidly repeated seeks result in bogus increasing * reported latency. */ if (priv->did_reset) cork(ao, false); pa_threaded_mainloop_lock(priv->mainloop); if (pa_stream_write(priv->stream, data, len, NULL, 0, PA_SEEK_RELATIVE) < 0) { GENERIC_ERR_MSG(priv->context, "pa_stream_write() failed"); len = -1; } if (priv->did_reset) { priv->did_reset = false; if (!waitop(priv, pa_stream_update_timing_info(priv->stream, success_cb, ao)) || !priv->retval) GENERIC_ERR_MSG(priv->context, "pa_stream_UPP() failed"); } else pa_threaded_mainloop_unlock(priv->mainloop); return len; } // Reset the audio stream, i.e. flush the playback buffer on the server side static void reset(struct ao *ao) { // pa_stream_flush() works badly if not corked cork(ao, true); struct priv *priv = ao->priv; pa_threaded_mainloop_lock(priv->mainloop); priv->retval = 0; if (!waitop(priv, pa_stream_flush(priv->stream, success_cb, ao)) || !priv->retval) GENERIC_ERR_MSG(priv->context, "pa_stream_flush() failed"); priv->did_reset = true; }
static void mark(int s) { uncork(s); cork(s); }
static int process_queue(socket_worker_t * self, relay_socket_t * sck, queue_t * private_queue, queue_t * spill_queue, ssize_t * wrote) { if (sck == NULL) { WARN("NULL forwarding socket"); return 0; } blob_t *cur_blob; struct timeval now; struct timeval send_start_time; struct timeval send_end_time; stats_count_t spilled = 0; const config_t *config = self->base.config; const uint64_t spill_microsec = 1000 * config->spill_millisec; const uint64_t grace_microsec = 1000 * config->spill_grace_millisec; const struct sockaddr *dest_addr = (const struct sockaddr *) &sck->sa.in; socklen_t addr_len = sck->addrlen; int in_grace_period = 0; struct timeval grace_period_start; int failed = 0; *wrote = 0; get_time(&send_start_time); cork(sck, 1); while (private_queue->head != NULL) { get_time(&now); /* While not all the socket backends are present, for a configured maximum time, * do not spill/drop. This is a bit crude, better rules/heuristics welcome. */ if (!connected_all()) { if (in_grace_period == 0) { in_grace_period = 1; get_time(&grace_period_start); SAY("Spill/drop grace period of %d millisec started", config->spill_grace_millisec); } if (elapsed_usec(&grace_period_start, &now) >= grace_microsec) { in_grace_period = 0; SAY("Spill/drop grace period of %d millisec expired", config->spill_grace_millisec); } } else { if (in_grace_period) { SAY("Spill/drop grace period of %d millisec canceled", config->spill_grace_millisec); } in_grace_period = 0; } if (in_grace_period == 0) { spilled += spill_by_age(self, config->spill_enabled, private_queue, spill_queue, spill_microsec, &now); } cur_blob = private_queue->head; if (!cur_blob) break; void *blob_data; ssize_t blob_size; if (sck->type == SOCK_DGRAM) { blob_size = BLOB_BUF_SIZE(cur_blob); blob_data = BLOB_BUF_addr(cur_blob); } else { /* sck->type == SOCK_STREAM */ blob_size = BLOB_DATA_MBR_SIZE(cur_blob); blob_data = BLOB_DATA_MBR_addr(cur_blob); } ssize_t blob_left = blob_size; ssize_t blob_sent = 0; int sendto_errno = 0; failed = 0; /* Keep sending while we have data left since a single sendto() * doesn't necessarily send all of it. This may eventually fail * if sendto() returns -1. */ while (!RELAY_ATOMIC_READ(self->base.stopping) && blob_left > 0) { const void *data = (const char *) blob_data + blob_sent; ssize_t sent; sendto_errno = 0; if (sck->type == SOCK_DGRAM) { sent = sendto(sck->socket, data, blob_left, MSG_NOSIGNAL, dest_addr, addr_len); } else { /* sck->type == SOCK_STREAM */ sent = sendto(sck->socket, data, blob_left, MSG_NOSIGNAL, NULL, 0); } sendto_errno = errno; if (0) { /* For debugging. */ peek_send(sck, data, blob_left, sent); } if (sent == -1) { WARN_ERRNO("sendto() tried sending %zd bytes to %s but sent none", blob_left, sck->to_string); RELAY_ATOMIC_INCREMENT(self->counters.error_count, 1); if (sendto_errno == EINTR) { /* sendto() got interrupted by a signal. Wait a while and retry. */ WARN("Interrupted, resuming"); worker_wait_millisec(config->sleep_after_disaster_millisec); continue; } failed = 1; break; /* stop sending from the hijacked queue */ } blob_sent += sent; blob_left -= sent; } if (blob_sent == blob_size) { RELAY_ATOMIC_INCREMENT(self->counters.sent_count, 1); } else if (blob_sent < blob_size) { /* Despite the send-loop above, we failed to send all the bytes. */ WARN("sendto() tried sending %zd bytes to %s but sent only %zd", blob_size, sck->to_string, blob_sent); RELAY_ATOMIC_INCREMENT(self->counters.partial_count, 1); failed = 1; } *wrote += blob_sent; if (failed) { /* We failed to send this packet. Exit the loop, and * right after the loop close the socket, and get out, * letting the main loop to reconnect. */ if ((sendto_errno == EAGAIN || sendto_errno == EWOULDBLOCK)) { /* Traffic jam. Wait a while, but still get out. */ WARN("Traffic jam"); worker_wait_millisec(config->sleep_after_disaster_millisec); } break; } else { queue_shift_nolock(private_queue); blob_destroy(cur_blob); } } cork(sck, 0); get_time(&send_end_time); if (spilled) { if (config->spill_enabled) { WARN("Wrote %lu items which were over spill threshold", (unsigned long) spilled); } else { WARN("Spill disabled: DROPPED %lu items which were over spill threshold", (unsigned long) spilled); } } /* this assumes end_time >= start_time */ uint64_t usec = elapsed_usec(&send_start_time, &send_end_time); RELAY_ATOMIC_INCREMENT(self->counters.send_elapsed_usec, usec); return failed == 0; }
static int init(struct ao *ao) { struct pa_sample_spec ss; struct pa_channel_map map; pa_proplist *proplist = NULL; struct priv *priv = ao->priv; char *host = priv->cfg_host && priv->cfg_host[0] ? priv->cfg_host : NULL; char *sink = priv->cfg_sink && priv->cfg_sink[0] ? priv->cfg_sink : NULL; const char *version = pa_get_library_version(); ao->per_application_mixer = true; priv->broken_pause = false; /* not sure which versions are affected, assume 0.9.11* to 0.9.14* * known bad: 0.9.14, 0.9.13 * known good: 0.9.9, 0.9.10, 0.9.15 * To test: pause, wait ca. 5 seconds, framestep and see if MPlayer * hangs somewhen. */ if (strncmp(version, "0.9.1", 5) == 0 && version[5] >= '1' && version[5] <= '4') { MP_WARN(ao, "working around probably broken pause functionality,\n" " see http://www.pulseaudio.org/ticket/440\n"); priv->broken_pause = true; } if (!(priv->mainloop = pa_threaded_mainloop_new())) { MP_ERR(ao, "Failed to allocate main loop\n"); goto fail; } if (!(priv->context = pa_context_new(pa_threaded_mainloop_get_api( priv->mainloop), PULSE_CLIENT_NAME))) { MP_ERR(ao, "Failed to allocate context\n"); goto fail; } pa_context_set_state_callback(priv->context, context_state_cb, ao); if (pa_context_connect(priv->context, host, 0, NULL) < 0) goto fail; pa_threaded_mainloop_lock(priv->mainloop); if (pa_threaded_mainloop_start(priv->mainloop) < 0) goto unlock_and_fail; /* Wait until the context is ready */ pa_threaded_mainloop_wait(priv->mainloop); if (pa_context_get_state(priv->context) != PA_CONTEXT_READY) goto unlock_and_fail; ss.channels = ao->channels.num; ss.rate = ao->samplerate; ao->format = af_fmt_from_planar(ao->format); const struct format_map *fmt_map = format_maps; while (fmt_map->mp_format != ao->format) { if (fmt_map->mp_format == AF_FORMAT_UNKNOWN) { MP_VERBOSE(ao, "Unsupported format, using default\n"); fmt_map = format_maps; break; } fmt_map++; } ao->format = fmt_map->mp_format; ss.format = fmt_map->pa_format; if (!pa_sample_spec_valid(&ss)) { MP_ERR(ao, "Invalid sample spec\n"); goto unlock_and_fail; } if (!select_chmap(ao, &map)) goto unlock_and_fail; if (!(proplist = pa_proplist_new())) { MP_ERR(ao, "Failed to allocate proplist\n"); goto unlock_and_fail; } (void)pa_proplist_sets(proplist, PA_PROP_MEDIA_ROLE, "video"); if (!(priv->stream = pa_stream_new_with_proplist(priv->context, "audio stream", &ss, &map, proplist))) goto unlock_and_fail; pa_proplist_free(proplist); proplist = NULL; pa_stream_set_state_callback(priv->stream, stream_state_cb, ao); pa_stream_set_write_callback(priv->stream, stream_request_cb, ao); pa_stream_set_latency_update_callback(priv->stream, stream_latency_update_cb, ao); pa_buffer_attr bufattr = { .maxlength = -1, .tlength = pa_usec_to_bytes(1000000, &ss), .prebuf = -1, .minreq = -1, .fragsize = -1, }; if (pa_stream_connect_playback(priv->stream, sink, &bufattr, PA_STREAM_NOT_MONOTONIC, NULL, NULL) < 0) goto unlock_and_fail; /* Wait until the stream is ready */ pa_threaded_mainloop_wait(priv->mainloop); if (pa_stream_get_state(priv->stream) != PA_STREAM_READY) goto unlock_and_fail; pa_threaded_mainloop_unlock(priv->mainloop); return 0; unlock_and_fail: if (priv->mainloop) pa_threaded_mainloop_unlock(priv->mainloop); fail: if (priv->context) { if (!(pa_context_errno(priv->context) == PA_ERR_CONNECTIONREFUSED && ao->probing)) GENERIC_ERR_MSG("Init failed"); } if (proplist) pa_proplist_free(proplist); uninit(ao, true); return -1; } static void cork(struct ao *ao, bool pause) { struct priv *priv = ao->priv; pa_threaded_mainloop_lock(priv->mainloop); priv->retval = 0; if (!waitop(priv, pa_stream_cork(priv->stream, pause, success_cb, ao)) || !priv->retval) GENERIC_ERR_MSG("pa_stream_cork() failed"); } // Play the specified data to the pulseaudio server static int play(struct ao *ao, void **data, int samples, int flags) { struct priv *priv = ao->priv; pa_threaded_mainloop_lock(priv->mainloop); if (pa_stream_write(priv->stream, data[0], samples * ao->sstride, NULL, 0, PA_SEEK_RELATIVE) < 0) { GENERIC_ERR_MSG("pa_stream_write() failed"); samples = -1; } if (flags & AOPLAY_FINAL_CHUNK) { // Force start in case the stream was too short for prebuf pa_operation *op = pa_stream_trigger(priv->stream, NULL, NULL); pa_operation_unref(op); } pa_threaded_mainloop_unlock(priv->mainloop); return samples; } // Reset the audio stream, i.e. flush the playback buffer on the server side static void reset(struct ao *ao) { // pa_stream_flush() works badly if not corked cork(ao, true); struct priv *priv = ao->priv; pa_threaded_mainloop_lock(priv->mainloop); priv->retval = 0; if (!waitop(priv, pa_stream_flush(priv->stream, success_cb, ao)) || !priv->retval) GENERIC_ERR_MSG("pa_stream_flush() failed"); cork(ao, false); }
// Resume the audio stream by uncorking it on the server static void resume(struct ao *ao) { cork(ao, false); }
static int init(struct ao *ao) { pa_proplist *proplist = NULL; pa_format_info *format = NULL; struct priv *priv = ao->priv; char *sink = priv->cfg_sink && priv->cfg_sink[0] ? priv->cfg_sink : ao->device; if (pa_init_boilerplate(ao) < 0) return -1; pa_threaded_mainloop_lock(priv->mainloop); if (!(proplist = pa_proplist_new())) { MP_ERR(ao, "Failed to allocate proplist\n"); goto unlock_and_fail; } (void)pa_proplist_sets(proplist, PA_PROP_MEDIA_ICON_NAME, ao->client_name); if (!(format = pa_format_info_new())) goto unlock_and_fail; if (!set_format(ao, format)) { ao->channels = (struct mp_chmap) MP_CHMAP_INIT_STEREO; ao->samplerate = 48000; ao->format = AF_FORMAT_FLOAT; if (!set_format(ao, format)) { MP_ERR(ao, "Invalid audio format\n"); goto unlock_and_fail; } } if (!(priv->stream = pa_stream_new_extended(priv->context, "audio stream", &format, 1, proplist))) goto unlock_and_fail; pa_format_info_free(format); format = NULL; pa_proplist_free(proplist); proplist = NULL; pa_stream_set_state_callback(priv->stream, stream_state_cb, ao); pa_stream_set_write_callback(priv->stream, stream_request_cb, ao); pa_stream_set_latency_update_callback(priv->stream, stream_latency_update_cb, ao); int buf_size = af_fmt_seconds_to_bytes(ao->format, priv->cfg_buffer / 1000.0, ao->channels.num, ao->samplerate); pa_buffer_attr bufattr = { .maxlength = -1, .tlength = buf_size > 0 ? buf_size : (uint32_t)-1, .prebuf = -1, .minreq = -1, .fragsize = -1, }; int flags = PA_STREAM_NOT_MONOTONIC; if (!priv->cfg_latency_hacks) flags |= PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE; if (pa_stream_connect_playback(priv->stream, sink, &bufattr, flags, NULL, NULL) < 0) goto unlock_and_fail; /* Wait until the stream is ready */ while (1) { int state = pa_stream_get_state(priv->stream); if (state == PA_STREAM_READY) break; if (!PA_STREAM_IS_GOOD(state)) goto unlock_and_fail; pa_threaded_mainloop_wait(priv->mainloop); } if (pa_stream_is_suspended(priv->stream)) { MP_ERR(ao, "The stream is suspended. Bailing out.\n"); goto unlock_and_fail; } pa_threaded_mainloop_unlock(priv->mainloop); return 0; unlock_and_fail: pa_threaded_mainloop_unlock(priv->mainloop); if (format) pa_format_info_free(format); if (proplist) pa_proplist_free(proplist); uninit(ao); return -1; } static void cork(struct ao *ao, bool pause) { struct priv *priv = ao->priv; pa_threaded_mainloop_lock(priv->mainloop); priv->retval = 0; if (!waitop(priv, pa_stream_cork(priv->stream, pause, success_cb, ao)) || !priv->retval) GENERIC_ERR_MSG("pa_stream_cork() failed"); } // Play the specified data to the pulseaudio server static int play(struct ao *ao, void **data, int samples, int flags) { struct priv *priv = ao->priv; pa_threaded_mainloop_lock(priv->mainloop); if (pa_stream_write(priv->stream, data[0], samples * ao->sstride, NULL, 0, PA_SEEK_RELATIVE) < 0) { GENERIC_ERR_MSG("pa_stream_write() failed"); samples = -1; } if (flags & AOPLAY_FINAL_CHUNK) { // Force start in case the stream was too short for prebuf pa_operation *op = pa_stream_trigger(priv->stream, NULL, NULL); pa_operation_unref(op); } pa_threaded_mainloop_unlock(priv->mainloop); return samples; } // Reset the audio stream, i.e. flush the playback buffer on the server side static void reset(struct ao *ao) { // pa_stream_flush() works badly if not corked cork(ao, true); struct priv *priv = ao->priv; pa_threaded_mainloop_lock(priv->mainloop); priv->retval = 0; if (!waitop(priv, pa_stream_flush(priv->stream, success_cb, ao)) || !priv->retval) GENERIC_ERR_MSG("pa_stream_flush() failed"); cork(ao, false); }
/** Pause the audio stream by corking it on the server */ static void audio_pause(void) { cork(1); }