/* This is called whenever new data may be written to the stream */ static void stream_write_callback(pa_stream *s, size_t length, void *userdata) { pa_assert(s); pa_assert(length > 0); if (raw) { pa_assert(!sndfile); if (stdio_event) mainloop_api->io_enable(stdio_event, PA_IO_EVENT_INPUT); if (!buffer) return; do_stream_write(length); } else { sf_count_t bytes; void *data; pa_assert(sndfile); for (;;) { size_t data_length = length; if (pa_stream_begin_write(s, &data, &data_length) < 0) { pa_log(_("pa_stream_begin_write() failed: %s"), pa_strerror(pa_context_errno(context))); quit(1); return; } if (readf_function) { size_t k = pa_frame_size(&sample_spec); if ((bytes = readf_function(sndfile, data, (sf_count_t) (data_length/k))) > 0) bytes *= (sf_count_t) k; } else bytes = sf_read_raw(sndfile, data, (sf_count_t) data_length); if (bytes > 0) pa_stream_write(s, data, (size_t) bytes, NULL, 0, PA_SEEK_RELATIVE); else pa_stream_cancel_write(s); /* EOF? */ if (bytes < (sf_count_t) data_length) { start_drain(); break; } /* Request fulfilled */ if ((size_t) bytes >= length) break; length -= bytes; } } }
static void stream_request_callback(pa_stream * s, size_t nbytes, void * u) { cubeb_stream * stm; void * buffer; size_t size; int r; long got; size_t towrite; size_t frame_size; stm = u; if (stm->shutdown) return; frame_size = pa_frame_size(&stm->sample_spec); assert(nbytes % frame_size == 0); towrite = nbytes; while (towrite) { size = towrite; r = pa_stream_begin_write(s, &buffer, &size); assert(r == 0); assert(size > 0); assert(size % frame_size == 0); got = stm->data_callback(stm, stm->user_ptr, buffer, size / frame_size); if (got < 0) { pa_stream_cancel_write(s); stm->shutdown = 1; return; } r = pa_stream_write(s, buffer, got * frame_size, NULL, 0, PA_SEEK_RELATIVE); assert(r == 0); if ((size_t) got < size / frame_size) { stm->draining = pa_stream_drain(s, stream_drain_success_callback, stm); stm->shutdown = 1; return; } towrite -= size; } assert(towrite == 0); }
/****************************************************************************** * stream_write_cb() * This will be called by PulseAudio when we need to provide audio data * for playback. *****************************************************************************/ static void stream_write_cb(pa_stream *s, size_t n_requested_bytes, void *userdata) { _mbx_out out = (_mbx_out ) userdata; sample_t *data_to_write = NULL; size_t n_bytes_written = 0; assert ( out != NULL && out->stream == s); if ( out->trigger_shutdown ) { do_shutdown(out); return; } while ( n_bytes_written < n_requested_bytes ) { int r; size_t n_bytes_to_write = n_requested_bytes - n_bytes_written; r = pa_stream_begin_write(s, (void**)&data_to_write, &n_bytes_to_write); assert(n_bytes_to_write % (2*sizeof(sample_t)) == 0); if ( r < 0 ) { mbx_log_error(MBX_LOG_AUDIO_OUTPUT, "Prepare writing data to the pulseaudio server" " failed: %s", pa_msg(out)); pa_stream_cancel_write(s); return; } if ( n_bytes_to_write > 0 ) { int i; assert ( n_bytes_to_write / sizeof(sample_t) < 44100L*2*8 ); sample_t left[44100L*2*8]; sample_t right[44100L*2*8]; out->cb(left, right, n_bytes_to_write / (2*sizeof(sample_t)), out->output_cb_userdata); for ( i=0; i<n_bytes_to_write/(2*sizeof(sample_t)); i++ ) { data_to_write[2*i] = left[i]; data_to_write[2*i+1] = right[i]; } pa_stream_write(s, data_to_write, n_bytes_to_write, NULL, 0, PA_SEEK_RELATIVE); n_bytes_written += n_bytes_to_write; } } }
/*! Starts up audio streaming to the host. */ void HostAudio::open() { m_mainloop = pa_threaded_mainloop_new(); if (!m_mainloop) { qDebug("Could not acquire PulseAudio main loop"); return; } m_api = pa_threaded_mainloop_get_api(m_mainloop); m_context = pa_context_new(m_api, "emumaster"); pa_context_set_state_callback(m_context, contextStreamCallback, this); if (!m_context) { qDebug("Could not acquire PulseAudio device context"); return; } #if defined(MEEGO_EDITION_HARMATTAN) if (pa_context_connect(m_context, 0, PA_CONTEXT_NOFLAGS, 0) < 0) { #elif defined(Q_WS_MAEMO_5) if (pa_context_connect(m_context, 0, (pa_context_flags_t)0, 0) < 0) { #endif int error = pa_context_errno(m_context); qDebug("Could not connect to PulseAudio server: %s", pa_strerror(error)); return; } pa_threaded_mainloop_lock(m_mainloop); if (pa_threaded_mainloop_start(m_mainloop) < 0) { qDebug("Could not start mainloop"); return; } waitForStreamReady(); pa_sample_spec fmt; fmt.channels = 2; fmt.format = PA_SAMPLE_S16LE; fmt.rate = 44100; pa_buffer_attr buffer_attributes; buffer_attributes.tlength = pa_bytes_per_second(&fmt) / 5; buffer_attributes.maxlength = buffer_attributes.tlength * 3; buffer_attributes.minreq = buffer_attributes.tlength / 3; buffer_attributes.prebuf = buffer_attributes.tlength; m_stream = pa_stream_new(m_context, "emumaster", &fmt, 0); if (!m_stream) { int error = pa_context_errno(m_context); qDebug("Could not acquire new PulseAudio stream: %s", pa_strerror(error)); return; } pa_stream_flags_t flags = (pa_stream_flags_t)(PA_STREAM_ADJUST_LATENCY | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE); // pa_stream_flags_t flags = (pa_stream_flags_t) (PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_EARLY_REQUESTS); if (pa_stream_connect_playback(m_stream, 0, &buffer_attributes, flags, 0, 0) < 0) { m_stream = 0; int error = pa_context_errno(m_context); qDebug("Could not connect for playback: %s", pa_strerror(error)); return; } waitForStreamReady(); pa_threaded_mainloop_unlock(m_mainloop); } /*! Stops audio streaming. */ void HostAudio::close() { if (m_mainloop) pa_threaded_mainloop_stop(m_mainloop); if (m_stream) { pa_stream_unref(m_stream); m_stream = 0; } if (m_context) { pa_context_disconnect(m_context); pa_context_unref(m_context); m_context = 0; } if (m_mainloop) { pa_threaded_mainloop_free(m_mainloop); m_mainloop = 0; } } /*! Streams a frame of audio from emulated system to the host. */ void HostAudio::sendFrame() { if (!m_stream) return; pa_threaded_mainloop_lock(m_mainloop); void *data; #if defined(MEEGO_EDITION_HARMATTAN) size_t size = -1; pa_stream_begin_write(m_stream, &data, &size); #elif defined(Q_WS_MAEMO_5) size_t size = 4096; static char buf[4096]; data = buf; #endif size = qMin(size, pa_stream_writable_size(m_stream)); if (size) size = m_emu->fillAudioBuffer(reinterpret_cast<char *>(data), size); if (size) pa_stream_write(m_stream, data, size, 0, 0, PA_SEEK_RELATIVE); #if defined(MEEGO_EDITION_HARMATTAN) else pa_stream_cancel_write(m_stream); #endif pa_threaded_mainloop_unlock(m_mainloop); } /*! \internal */ void HostAudio::waitForStreamReady() { pa_context_state_t context_state = pa_context_get_state(m_context); while (context_state != PA_CONTEXT_READY) { context_state = pa_context_get_state(m_context); if (!PA_CONTEXT_IS_GOOD(context_state)) { int error = pa_context_errno(m_context); qDebug("Context state is not good: %s", pa_strerror(error)); return; } else if (context_state == PA_CONTEXT_READY) { break; } else { //qDebug("PulseAudio context state is %d", context_state); } pa_threaded_mainloop_wait(m_mainloop); } }