OSStatus gst_osx_audio_sink_io_proc (AudioDeviceID inDevice, const AudioTimeStamp * inNow, const AudioBufferList * inInputData, const AudioTimeStamp * inInputTime, AudioBufferList * outOutputData, const AudioTimeStamp * inOutputTime, void *inClientData) { GstOsxRingBuffer *buf = GST_OSX_RING_BUFFER (inClientData); guint8 *readptr; gint readseg; gint len; if (gst_ring_buffer_prepare_read (GST_RING_BUFFER (buf), &readseg, &readptr, &len)) { outOutputData->mBuffers[0].mDataByteSize = len; memcpy ((char *) outOutputData->mBuffers[0].mData, readptr, len); /* clear written samples */ gst_ring_buffer_clear (GST_RING_BUFFER (buf), readseg); /* we wrote one segment */ gst_ring_buffer_advance (GST_RING_BUFFER (buf), 1); } return 0; }
/* this is the callback of jack. This should be RT-safe. * Writes samples from the jack input port's buffer to the gst ring buffer. */ static int jack_process_cb (jack_nframes_t nframes, void *arg) { GstJackAudioSrc *src; GstRingBuffer *buf; GstJackRingBuffer *abuf; gint len, givenLen; guint8 *writeptr, *dataStart; gint writeseg; gint channels, i, j; sample_t **buffers, *data; buf = GST_RING_BUFFER_CAST (arg); abuf = GST_JACK_RING_BUFFER_CAST (arg); src = GST_JACK_AUDIO_SRC (GST_OBJECT_PARENT (buf)); channels = buf->spec.channels; len = sizeof (sample_t) * nframes * channels; /* alloc pointers to samples */ buffers = g_alloca (sizeof (sample_t *) * channels); data = g_alloca (len); /* get input buffers */ for (i = 0; i < channels; i++) buffers[i] = (sample_t *) jack_port_get_buffer (src->ports[i], nframes); //writeptr = data; dataStart = (guint8 *) data; /* the samples in the jack input buffers have to be interleaved into the * ringbuffer */ for (i = 0; i < nframes; ++i) for (j = 0; j < channels; ++j) *data++ = buffers[j][i]; if (gst_ring_buffer_prepare_read (buf, &writeseg, &writeptr, &givenLen)) { memcpy (writeptr, (char *) dataStart, givenLen); GST_DEBUG ("copy %d frames: %p, %d bytes, %d channels", nframes, writeptr, len / channels, channels); /* clear written samples in the ringbuffer */ // gst_ring_buffer_clear(buf, 0); /* we wrote one segment */ gst_ring_buffer_advance (buf, 1); } return 0; }
/* this is the callback of jack. This should be RT-safe. * Writes samples from the jack input port's buffer to the gst ring buffer. */ static int jack_process_cb (jack_nframes_t nframes, void *arg) { GstJackAudioSrc *src; GstRingBuffer *buf; gint len; guint8 *writeptr; gint writeseg; gint channels, i, j, flen; sample_t *data; buf = GST_RING_BUFFER_CAST (arg); src = GST_JACK_AUDIO_SRC (GST_OBJECT_PARENT (buf)); channels = buf->spec.channels; /* get input buffers */ for (i = 0; i < channels; i++) src->buffers[i] = (sample_t *) jack_port_get_buffer (src->ports[i], nframes); if (gst_ring_buffer_prepare_read (buf, &writeseg, &writeptr, &len)) { flen = len / channels; /* the number of samples must be exactly the segment size */ if (nframes * sizeof (sample_t) != flen) goto wrong_size; /* the samples in the jack input buffers have to be interleaved into the * ringbuffer */ data = (sample_t *) writeptr; for (i = 0; i < nframes; ++i) for (j = 0; j < channels; ++j) *data++ = src->buffers[j][i]; GST_DEBUG ("copy %d frames: %p, %d bytes, %d channels", nframes, writeptr, len / channels, channels); /* we wrote one segment */ gst_ring_buffer_advance (buf, 1); } return 0; /* ERRORS */ wrong_size: { GST_ERROR_OBJECT (src, "nbytes (%d) != flen (%d)", (gint) (nframes * sizeof (sample_t)), flen); return 1; } }
static OSStatus gst_osx_audio_src_io_proc (GstOsxRingBuffer * buf, AudioUnitRenderActionFlags * ioActionFlags, const AudioTimeStamp * inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList * bufferList) { OSStatus status; guint8 *writeptr; gint writeseg; gint len; gint remaining; gint offset = 0; status = AudioUnitRender (buf->core_audio->audiounit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, buf->core_audio->recBufferList); if (status) { GST_WARNING_OBJECT (buf, "AudioUnitRender returned %d", (int) status); return status; } remaining = buf->core_audio->recBufferList->mBuffers[0].mDataByteSize; while (remaining) { if (!gst_ring_buffer_prepare_read (GST_RING_BUFFER (buf), &writeseg, &writeptr, &len)) return 0; len -= buf->segoffset; if (len > remaining) len = remaining; memcpy (writeptr + buf->segoffset, (char *) buf->core_audio->recBufferList->mBuffers[0].mData + offset, len); buf->segoffset += len; offset += len; remaining -= len; if ((gint) buf->segoffset == GST_RING_BUFFER (buf)->spec.segsize) { /* we wrote one segment */ gst_ring_buffer_advance (GST_RING_BUFFER (buf), 1); buf->segoffset = 0; } } return 0; }
/* this is the callback of jack. This should RT-safe. */ static int jack_process_cb (jack_nframes_t nframes, void *arg) { GstJackAudioSink *sink; GstRingBuffer *buf; gint readseg, len; guint8 *readptr; gint i, j, flen, channels; sample_t *data; buf = GST_RING_BUFFER_CAST (arg); sink = GST_JACK_AUDIO_SINK (GST_OBJECT_PARENT (buf)); channels = buf->spec.channels; /* get target buffers */ for (i = 0; i < channels; i++) { sink->buffers[i] = (sample_t *) jack_port_get_buffer (sink->ports[i], nframes); } if (gst_ring_buffer_prepare_read (buf, &readseg, &readptr, &len)) { flen = len / channels; /* the number of samples must be exactly the segment size */ if (nframes * sizeof (sample_t) != flen) goto wrong_size; GST_DEBUG_OBJECT (sink, "copy %d frames: %p, %d bytes, %d channels", nframes, readptr, flen, channels); data = (sample_t *) readptr; /* the samples in the ringbuffer have the channels interleaved, we need to * deinterleave into the jack target buffers */ for (i = 0; i < nframes; i++) { for (j = 0; j < channels; j++) { sink->buffers[j][i] = *data++; } } /* clear written samples in the ringbuffer */ gst_ring_buffer_clear (buf, readseg); /* we wrote one segment */ gst_ring_buffer_advance (buf, 1); } else { GST_DEBUG_OBJECT (sink, "write %d frames silence", nframes); /* We are not allowed to read from the ringbuffer, write silence to all * jack output buffers */ for (i = 0; i < channels; i++) { memset (sink->buffers[i], 0, nframes * sizeof (sample_t)); } } return 0; /* ERRORS */ wrong_size: { GST_ERROR_OBJECT (sink, "nbytes (%d) != flen (%d)", (gint) (nframes * sizeof (sample_t)), flen); return 1; } }
static GstFlowReturn gst_audio_ringbuffer_get_range (GstPad * pad, guint64 offset, guint length, GstBuffer ** buffer) { GstAudioRingbuffer *ringbuffer; GstRingBuffer *rbuf; GstFlowReturn ret; ringbuffer = GST_AUDIO_RINGBUFFER_CAST (gst_pad_get_parent (pad)); rbuf = ringbuffer->buffer; if (ringbuffer->pulling) { GST_DEBUG_OBJECT (ringbuffer, "proxy pulling range"); ret = gst_pad_pull_range (ringbuffer->sinkpad, offset, length, buffer); } else { guint8 *data; guint len; guint64 sample; gint bps, segsize, segtotal, sps; gint sampleslen, segdone; gint readseg, sampleoff; guint8 *dest; GST_DEBUG_OBJECT (ringbuffer, "pulling data at %" G_GUINT64_FORMAT ", length %u", offset, length); if (offset != ringbuffer->src_segment.last_stop) { GST_DEBUG_OBJECT (ringbuffer, "expected offset %" G_GINT64_FORMAT, ringbuffer->src_segment.last_stop); } /* first wait till we have something in the ringbuffer and it * is running */ GST_OBJECT_LOCK (ringbuffer); if (ringbuffer->flushing) goto flushing; while (ringbuffer->waiting) { GST_DEBUG_OBJECT (ringbuffer, "waiting for unlock"); g_cond_wait (ringbuffer->cond, GST_OBJECT_GET_LOCK (ringbuffer)); GST_DEBUG_OBJECT (ringbuffer, "unlocked"); if (ringbuffer->flushing) goto flushing; } GST_OBJECT_UNLOCK (ringbuffer); bps = rbuf->spec.bytes_per_sample; if (G_UNLIKELY (length % bps) != 0) goto wrong_size; segsize = rbuf->spec.segsize; segtotal = rbuf->spec.segtotal; sps = rbuf->samples_per_seg; dest = GST_BUFFER_DATA (rbuf->data); sample = offset / bps; len = length / bps; *buffer = gst_buffer_new_and_alloc (length); data = GST_BUFFER_DATA (*buffer); while (len) { gint diff; /* figure out the segment and the offset inside the segment where * the sample should be read from. */ readseg = sample / sps; sampleoff = (sample % sps); segdone = g_atomic_int_get (&rbuf->segdone) - rbuf->segbase; diff = readseg - segdone; /* we can read now */ readseg = readseg % segtotal; sampleslen = MIN (sps - sampleoff, len); GST_DEBUG_OBJECT (ringbuffer, "read @%p seg %d, off %d, sampleslen %d, diff %d", dest + readseg * segsize, readseg, sampleoff, sampleslen, diff); memcpy (data, dest + (readseg * segsize) + (sampleoff * bps), (sampleslen * bps)); if (diff > 0) gst_ring_buffer_advance (rbuf, diff); len -= sampleslen; sample += sampleslen; data += sampleslen * bps; } ringbuffer->src_segment.last_stop += length; ret = GST_FLOW_OK; } gst_object_unref (ringbuffer); return ret; /* ERRORS */ flushing: { GST_DEBUG_OBJECT (ringbuffer, "we are flushing"); GST_OBJECT_UNLOCK (ringbuffer); gst_object_unref (ringbuffer); return GST_FLOW_WRONG_STATE; } wrong_size: { GST_DEBUG_OBJECT (ringbuffer, "wrong size"); GST_ELEMENT_ERROR (ringbuffer, STREAM, WRONG_TYPE, (NULL), ("asked to pull buffer of wrong size.")); return GST_FLOW_ERROR; } }
/* this internal thread does nothing else but write samples to the audio device. * It will write each segment in the ringbuffer and will update the play * pointer. * The start/stop methods control the thread. */ static void audioringbuffer_thread_func (GstRingBuffer * buf) { GstAudioSink *sink; GstAudioSinkClass *csink; GstAudioRingBuffer *abuf = GST_AUDIORING_BUFFER_CAST (buf); WriteFunc writefunc; GstMessage *message; GValue val = { 0 }; sink = GST_AUDIO_SINK (GST_OBJECT_PARENT (buf)); csink = GST_AUDIO_SINK_GET_CLASS (sink); GST_DEBUG_OBJECT (sink, "enter thread"); GST_OBJECT_LOCK (abuf); GST_DEBUG_OBJECT (sink, "signal wait"); GST_AUDIORING_BUFFER_SIGNAL (buf); GST_OBJECT_UNLOCK (abuf); writefunc = csink->write; if (writefunc == NULL) goto no_function; g_value_init (&val, G_TYPE_POINTER); g_value_set_pointer (&val, sink->thread); message = gst_message_new_stream_status (GST_OBJECT_CAST (buf), GST_STREAM_STATUS_TYPE_ENTER, GST_ELEMENT_CAST (sink)); gst_message_set_stream_status_object (message, &val); GST_DEBUG_OBJECT (sink, "posting ENTER stream status"); gst_element_post_message (GST_ELEMENT_CAST (sink), message); while (TRUE) { gint left, len; guint8 *readptr; gint readseg; /* buffer must be started */ if (gst_ring_buffer_prepare_read (buf, &readseg, &readptr, &len)) { gint written; left = len; do { written = writefunc (sink, readptr, left); GST_LOG_OBJECT (sink, "transfered %d bytes of %d from segment %d", written, left, readseg); if (written < 0 || written > left) { /* might not be critical, it e.g. happens when aborting playback */ GST_WARNING_OBJECT (sink, "error writing data in %s (reason: %s), skipping segment (left: %d, written: %d)", GST_DEBUG_FUNCPTR_NAME (writefunc), (errno > 1 ? g_strerror (errno) : "unknown"), left, written); break; } left -= written; readptr += written; } while (left > 0); /* clear written samples */ gst_ring_buffer_clear (buf, readseg); /* we wrote one segment */ gst_ring_buffer_advance (buf, 1); } else { GST_OBJECT_LOCK (abuf); if (!abuf->running) goto stop_running; GST_DEBUG_OBJECT (sink, "signal wait"); GST_AUDIORING_BUFFER_SIGNAL (buf); GST_DEBUG_OBJECT (sink, "wait for action"); #ifndef GSTREAMER_LITE GST_AUDIORING_BUFFER_WAIT (buf); #else // GSTREAMER_LITE // In same cases we may have condition when we waiting here for ring buffer to start, // while ring buffer is started and data is available. So, lets use wait with timeout // and recheck if we good to go. wait_segment() will start ring buffer when data is available. { GTimeVal timeout; g_get_current_time(&timeout); g_time_val_add(&timeout, 100000); // 100 millisecond GST_AUDIORING_BUFFER_TIMED_WAIT (buf, &timeout); } #endif // GSTREAMER_LITE GST_DEBUG_OBJECT (sink, "got signal"); if (!abuf->running) goto stop_running; GST_DEBUG_OBJECT (sink, "continue running"); GST_OBJECT_UNLOCK (abuf); } } /* Will never be reached */ g_assert_not_reached (); return; /* ERROR */ no_function: { GST_DEBUG_OBJECT (sink, "no write function, exit thread"); return; } stop_running: { GST_OBJECT_UNLOCK (abuf); GST_DEBUG_OBJECT (sink, "stop running, exit thread"); message = gst_message_new_stream_status (GST_OBJECT_CAST (buf), GST_STREAM_STATUS_TYPE_LEAVE, GST_ELEMENT_CAST (sink)); gst_message_set_stream_status_object (message, &val); GST_DEBUG_OBJECT (sink, "posting LEAVE stream status"); gst_element_post_message (GST_ELEMENT_CAST (sink), message); return; } }
static GstFlowReturn gst_base_audio_src_create (GstBaseSrc * bsrc, guint64 offset, guint length, GstBuffer ** outbuf) { GstBaseAudioSrc *src = GST_BASE_AUDIO_SRC (bsrc); GstBuffer *buf; guchar *data; guint samples, total_samples; guint64 sample; gint bps; GstRingBuffer *ringbuffer; GstRingBufferSpec *spec; guint read; GstClockTime timestamp, duration; GstClock *clock; ringbuffer = src->ringbuffer; spec = &ringbuffer->spec; if (G_UNLIKELY (!gst_ring_buffer_is_acquired (ringbuffer))) goto wrong_state; bps = spec->bytes_per_sample; if ((length == 0 && bsrc->blocksize == 0) || length == -1) /* no length given, use the default segment size */ length = spec->segsize; else /* make sure we round down to an integral number of samples */ length -= length % bps; /* figure out the offset in the ringbuffer */ if (G_UNLIKELY (offset != -1)) { sample = offset / bps; /* if a specific offset was given it must be the next sequential * offset we expect or we fail for now. */ if (src->next_sample != -1 && sample != src->next_sample) goto wrong_offset; } else { /* calculate the sequentially next sample we need to read. This can jump and * create a DISCONT. */ sample = gst_base_audio_src_get_offset (src); } GST_DEBUG_OBJECT (src, "reading from sample %" G_GUINT64_FORMAT, sample); /* get the number of samples to read */ total_samples = samples = length / bps; /* FIXME, using a bufferpool would be nice here */ buf = gst_buffer_new_and_alloc (length); data = GST_BUFFER_DATA (buf); do { read = gst_ring_buffer_read (ringbuffer, sample, data, samples); GST_DEBUG_OBJECT (src, "read %u of %u", read, samples); /* if we read all, we're done */ if (read == samples) break; /* else something interrupted us and we wait for playing again. */ GST_DEBUG_OBJECT (src, "wait playing"); if (gst_base_src_wait_playing (bsrc) != GST_FLOW_OK) goto stopped; GST_DEBUG_OBJECT (src, "continue playing"); /* read next samples */ sample += read; samples -= read; data += read * bps; } while (TRUE); /* mark discontinuity if needed */ if (G_UNLIKELY (sample != src->next_sample) && src->next_sample != -1) { GST_WARNING_OBJECT (src, "create DISCONT of %" G_GUINT64_FORMAT " samples at sample %" G_GUINT64_FORMAT, sample - src->next_sample, sample); GST_ELEMENT_WARNING (src, CORE, CLOCK, (_("Can't record audio fast enough")), ("Dropped %" G_GUINT64_FORMAT " samples. This is most likely because " "downstream can't keep up and is consuming samples too slowly.", sample - src->next_sample)); GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT); } src->next_sample = sample + samples; /* get the normal timestamp to get the duration. */ timestamp = gst_util_uint64_scale_int (sample, GST_SECOND, spec->rate); duration = gst_util_uint64_scale_int (src->next_sample, GST_SECOND, spec->rate) - timestamp; GST_OBJECT_LOCK (src); if (!(clock = GST_ELEMENT_CLOCK (src))) goto no_sync; if (clock != src->clock) { /* we are slaved, check how to handle this */ switch (src->priv->slave_method) { case GST_BASE_AUDIO_SRC_SLAVE_RESAMPLE: /* not implemented, use skew algorithm. This algorithm should * work on the readout pointer and produces more or less samples based * on the clock drift */ case GST_BASE_AUDIO_SRC_SLAVE_SKEW: { GstClockTime running_time; GstClockTime base_time; GstClockTime current_time; guint64 running_time_sample; gint running_time_segment; gint last_read_segment; gint segment_skew; gint sps; gint segments_written; gint last_written_segment; /* get the amount of segments written from the device by now */ segments_written = g_atomic_int_get (&ringbuffer->segdone); /* subtract the base to segments_written to get the number of the last written segment in the ringbuffer (one segment written = segment 0) */ last_written_segment = segments_written - ringbuffer->segbase - 1; /* samples per segment */ sps = ringbuffer->samples_per_seg; /* get the current time */ current_time = gst_clock_get_time (clock); /* get the basetime */ base_time = GST_ELEMENT_CAST (src)->base_time; /* get the running_time */ running_time = current_time - base_time; /* the running_time converted to a sample (relative to the ringbuffer) */ running_time_sample = gst_util_uint64_scale_int (running_time, spec->rate, GST_SECOND); /* the segmentnr corrensponding to running_time, round down */ running_time_segment = running_time_sample / sps; /* the segment currently read from the ringbuffer */ last_read_segment = sample / sps; /* the skew we have between running_time and the ringbuffertime (last written to) */ segment_skew = running_time_segment - last_written_segment; GST_DEBUG_OBJECT (bsrc, "\n running_time = %" GST_TIME_FORMAT "\n timestamp = %" GST_TIME_FORMAT "\n running_time_segment = %d" "\n last_written_segment = %d" "\n segment_skew (running time segment - last_written_segment) = %d" "\n last_read_segment = %d", GST_TIME_ARGS (running_time), GST_TIME_ARGS (timestamp), running_time_segment, last_written_segment, segment_skew, last_read_segment); /* Resync the ringbuffer if: * * 1. We are more than the length of the ringbuffer behind. * The length of the ringbuffer then gets to dictate * the threshold for what is concidered "too late" * * 2. If this is our first buffer. * We know that we should catch up to running_time * the first time we are ran. */ if ((segment_skew >= ringbuffer->spec.segtotal) || (last_read_segment == 0)) { gint new_read_segment; gint segment_diff; guint64 new_sample; /* the difference between running_time and the last written segment */ segment_diff = running_time_segment - last_written_segment; /* advance the ringbuffer */ gst_ring_buffer_advance (ringbuffer, segment_diff); /* we move the new read segment to the last known written segment */ new_read_segment = g_atomic_int_get (&ringbuffer->segdone) - ringbuffer->segbase; /* we calculate the new sample value */ new_sample = ((guint64) new_read_segment) * sps; /* and get the relative time to this -> our new timestamp */ timestamp = gst_util_uint64_scale_int (new_sample, GST_SECOND, spec->rate); /* we update the next sample accordingly */ src->next_sample = new_sample + samples; GST_DEBUG_OBJECT (bsrc, "Timeshifted the ringbuffer with %d segments: " "Updating the timestamp to %" GST_TIME_FORMAT ", " "and src->next_sample to %" G_GUINT64_FORMAT, segment_diff, GST_TIME_ARGS (timestamp), src->next_sample); } break; } case GST_BASE_AUDIO_SRC_SLAVE_RETIMESTAMP: { GstClockTime base_time, latency; /* We are slaved to another clock, take running time of the pipeline clock and * timestamp against it. Somebody else in the pipeline should figure out the * clock drift. We keep the duration we calculated above. */ timestamp = gst_clock_get_time (clock); base_time = GST_ELEMENT_CAST (src)->base_time; if (GST_CLOCK_DIFF (timestamp, base_time) < 0) timestamp -= base_time; else timestamp = 0; /* subtract latency */ latency = gst_util_uint64_scale_int (total_samples, GST_SECOND, spec->rate); if (timestamp > latency) timestamp -= latency; else timestamp = 0; } case GST_BASE_AUDIO_SRC_SLAVE_NONE: break; } } else { GstClockTime base_time; /* to get the timestamp against the clock we also need to add our offset */ timestamp = gst_audio_clock_adjust (clock, timestamp); /* we are not slaved, subtract base_time */ base_time = GST_ELEMENT_CAST (src)->base_time; if (GST_CLOCK_DIFF (timestamp, base_time) < 0) { timestamp -= base_time; GST_LOG_OBJECT (src, "buffer timestamp %" GST_TIME_FORMAT " (base_time %" GST_TIME_FORMAT ")", GST_TIME_ARGS (timestamp), GST_TIME_ARGS (base_time)); } else { GST_LOG_OBJECT (src, "buffer timestamp 0, ts %" GST_TIME_FORMAT " <= base_time %" GST_TIME_FORMAT, GST_TIME_ARGS (timestamp), GST_TIME_ARGS (base_time)); timestamp = 0; } } no_sync: GST_OBJECT_UNLOCK (src); GST_BUFFER_TIMESTAMP (buf) = timestamp; GST_BUFFER_DURATION (buf) = duration; GST_BUFFER_OFFSET (buf) = sample; GST_BUFFER_OFFSET_END (buf) = sample + samples; *outbuf = buf; return GST_FLOW_OK; /* ERRORS */ wrong_state: { GST_DEBUG_OBJECT (src, "ringbuffer in wrong state"); return GST_FLOW_WRONG_STATE; } wrong_offset: { GST_ELEMENT_ERROR (src, RESOURCE, SEEK, (NULL), ("resource can only be operated on sequentially but offset %" G_GUINT64_FORMAT " was given", offset)); return GST_FLOW_ERROR; } stopped: { gst_buffer_unref (buf); GST_DEBUG_OBJECT (src, "ringbuffer stopped"); return GST_FLOW_WRONG_STATE; } }
static DWORD WINAPI gst_directsound_write_proc (LPVOID lpParameter) { GstRingBuffer * buf; GstDirectSoundRingBuffer * dsoundbuffer; HRESULT hr; DWORD dwStatus; LPVOID pLockedBuffer1 = NULL, pLockedBuffer2 = NULL; DWORD dwSizeBuffer1 = 0, dwSizeBuffer2 = 0; DWORD dwCurrentPlayCursor = 0; gint64 freeBufferSize = 0; guint8 * readptr = NULL; gint readseg = 0; guint len = 0; gint retries = 0; gboolean flushing = FALSE; gboolean should_run = TRUE; gboolean error = FALSE; buf = (GstRingBuffer *) lpParameter; dsoundbuffer = GST_DIRECTSOUND_RING_BUFFER (buf); do { GST_DSOUND_LOCK (dsoundbuffer); if (dsoundbuffer->flushing || !dsoundbuffer->pDSB8) { GST_DSOUND_UNLOCK (dsoundbuffer); goto complete; } GST_DSOUND_UNLOCK (dsoundbuffer); restore_buffer: /* get current buffer status */ GST_DSOUND_LOCK (dsoundbuffer); hr = IDirectSoundBuffer8_GetStatus (dsoundbuffer->pDSB8, &dwStatus); GST_DSOUND_UNLOCK (dsoundbuffer); if (dwStatus & DSBSTATUS_BUFFERLOST) { GST_DEBUG ("Buffer was lost, attempting to restore"); GST_DSOUND_LOCK (dsoundbuffer); hr = IDirectSoundBuffer8_Restore (dsoundbuffer->pDSB8); GST_DSOUND_UNLOCK (dsoundbuffer); /* restore may fail again, ensure we restore the * buffer before we continue */ if (FAILED(hr) && hr == DSERR_BUFFERLOST) { if (retries++ < MAX_LOST_RETRIES) { GST_DEBUG ("Unable to restore, trying again"); goto restore_buffer; } else { GST_ELEMENT_ERROR (dsoundbuffer->dsoundsink, RESOURCE, FAILED, ("%ls.", DXGetErrorDescription9W(hr)), ("gst_directsound_write_proc: IDirectSoundBuffer8_Restore, hr = %X", (unsigned int) hr)); goto complete; } } } /* get current play cursor and write cursor positions */ GST_DSOUND_LOCK (dsoundbuffer); hr = IDirectSoundBuffer8_GetCurrentPosition (dsoundbuffer->pDSB8, &dwCurrentPlayCursor, NULL); GST_DSOUND_UNLOCK (dsoundbuffer); if (G_UNLIKELY (FAILED(hr))) { /* try and reopen the default directsound device */ if (hr == DIRECTSOUND_ERROR_DEVICE_RECONFIGURED) { /* we have to wait a while for the sound device removal to actually * be processed before attempting to reopen the device. Yes, this sucks */ Sleep (2000); GST_DSOUND_LOCK (dsoundbuffer); IDirectSoundBuffer8_Release (dsoundbuffer->pDSB8); dsoundbuffer->pDSB8 = NULL; GST_DSOUND_UNLOCK (dsoundbuffer); if (gst_directsound_ring_buffer_close_device (buf) && gst_directsound_ring_buffer_open_device (buf) && gst_directsound_create_buffer (buf) ) { dsoundbuffer->buffer_write_offset = 0; goto restore_buffer; } } /* only trigger an error if we're not already in an error state */ if (FAILED(hr) && !error) { GST_ELEMENT_ERROR (dsoundbuffer->dsoundsink, RESOURCE, FAILED, ("%ls.", DXGetErrorDescription9W(hr)), ("gst_directsound_write_proc: IDirectSoundBuffer8_GetCurrentPosition, hr = %X", (unsigned int) hr)); error = TRUE; goto complete; } } GST_LOG ("Current Play Cursor: %u Current Write Offset: %d", (unsigned int) dwCurrentPlayCursor, dsoundbuffer->buffer_write_offset); /* calculate the free size of the circular buffer */ GST_DSOUND_LOCK (dsoundbuffer); if (dwCurrentPlayCursor <= dsoundbuffer->buffer_write_offset) freeBufferSize = dsoundbuffer->buffer_size - (dsoundbuffer->buffer_write_offset - dwCurrentPlayCursor); else freeBufferSize = dwCurrentPlayCursor - dsoundbuffer->buffer_write_offset; GST_DSOUND_UNLOCK (dsoundbuffer); if (!gst_ring_buffer_prepare_read (buf, &readseg, &readptr, &len)) goto complete; len -= dsoundbuffer->segoffset; GST_LOG ("Size of segment to write: %d Free buffer size: %lld", len, freeBufferSize); /* If we can't write this into directsound because we don't have enough * space, then start playback if we're currently paused. Then, sleep * for a little while to wait until space is available */ if (len >= freeBufferSize) { if (!(dwStatus & DSBSTATUS_PLAYING)) { GST_DSOUND_LOCK (dsoundbuffer); hr = IDirectSoundBuffer8_Play (dsoundbuffer->pDSB8, 0, 0, DSBPLAY_LOOPING); GST_DSOUND_UNLOCK (dsoundbuffer); if (FAILED(hr)) { GST_WARNING ("gst_directsound_write_proc: IDirectSoundBuffer8_Play, hr = %X", (unsigned int) hr); } } goto complete; } /* lock it */ GST_DSOUND_LOCK (dsoundbuffer); hr = IDirectSoundBuffer8_Lock (dsoundbuffer->pDSB8, dsoundbuffer->buffer_write_offset, len, &pLockedBuffer1, &dwSizeBuffer1, &pLockedBuffer2, &dwSizeBuffer2, 0L); /* copy chunks */ if (SUCCEEDED (hr)) { if (len <= dwSizeBuffer1) { memcpy (pLockedBuffer1, (LPBYTE) readptr + dsoundbuffer->segoffset, len); } else { memcpy (pLockedBuffer1, (LPBYTE) readptr + dsoundbuffer->segoffset, dwSizeBuffer1); memcpy (pLockedBuffer2, (LPBYTE) readptr + dsoundbuffer->segoffset + dwSizeBuffer1, len - dwSizeBuffer1); } IDirectSoundBuffer8_Unlock (dsoundbuffer->pDSB8, pLockedBuffer1, dwSizeBuffer1, pLockedBuffer2, dwSizeBuffer2); } else { GST_WARNING ("gst_directsound_write_proc: IDirectSoundBuffer8_Lock, hr = %X", (unsigned int) hr); } /* update tracking data */ dsoundbuffer->segoffset += dwSizeBuffer1 + (len - dwSizeBuffer1); dsoundbuffer->buffer_write_offset += dwSizeBuffer1 + (len - dwSizeBuffer1); dsoundbuffer->buffer_write_offset %= dsoundbuffer->buffer_size; GST_DSOUND_UNLOCK (dsoundbuffer); freeBufferSize -= dwSizeBuffer1 + (len - dwSizeBuffer1); GST_LOG ("DirectSound Buffer1 Data Size: %u DirectSound Buffer2 Data Size: %u", (unsigned int) dwSizeBuffer1, (unsigned int) dwSizeBuffer2); GST_LOG ("Free buffer size: %lld", freeBufferSize); /* check if we read a whole segment */ GST_DSOUND_LOCK (dsoundbuffer); if (dsoundbuffer->segoffset == dsoundbuffer->segsize) { GST_DSOUND_UNLOCK (dsoundbuffer); /* advance to next segment */ gst_ring_buffer_clear (buf, readseg); gst_ring_buffer_advance (buf, 1); GST_DSOUND_LOCK (dsoundbuffer); dsoundbuffer->segoffset = 0; } GST_DSOUND_UNLOCK (dsoundbuffer); complete: GST_DSOUND_LOCK (dsoundbuffer); should_run = dsoundbuffer->should_run; flushing = dsoundbuffer->flushing; retries = 0; GST_DSOUND_UNLOCK (dsoundbuffer); /* it's extremely important to sleep in without the lock! */ if (len >= freeBufferSize || flushing || error) Sleep (dsoundbuffer->min_sleep_time); } while(should_run); return 0; }
/* this internal thread does nothing else but read samples from the audio device. * It will read each segment in the ringbuffer and will update the play * pointer. * The start/stop methods control the thread. */ static void audioringbuffer_thread_func (GstRingBuffer * buf) { GstAudioSrc *src; GstAudioSrcClass *csrc; GstAudioRingBuffer *abuf = GST_AUDIORING_BUFFER (buf); ReadFunc readfunc; GstMessage *message; GValue val = { 0 }; src = GST_AUDIO_SRC (GST_OBJECT_PARENT (buf)); csrc = GST_AUDIO_SRC_GET_CLASS (src); GST_DEBUG_OBJECT (src, "enter thread"); readfunc = csrc->read; if (readfunc == NULL) goto no_function; /* FIXME: maybe we should at least use a custom pointer type here? */ g_value_init (&val, G_TYPE_POINTER); g_value_set_pointer (&val, src->thread); message = gst_message_new_stream_status (GST_OBJECT_CAST (buf), GST_STREAM_STATUS_TYPE_ENTER, GST_ELEMENT_CAST (src)); gst_message_set_stream_status_object (message, &val); GST_DEBUG_OBJECT (src, "posting ENTER stream status"); gst_element_post_message (GST_ELEMENT_CAST (src), message); while (TRUE) { gint left, len; guint8 *readptr; gint readseg; if (gst_ring_buffer_prepare_read (buf, &readseg, &readptr, &len)) { gint read; left = len; do { read = readfunc (src, readptr, left); GST_LOG_OBJECT (src, "transfered %d bytes of %d to segment %d", read, left, readseg); if (read < 0 || read > left) { GST_WARNING_OBJECT (src, "error reading data %d (reason: %s), skipping segment", read, g_strerror (errno)); break; } left -= read; readptr += read; } while (left > 0); /* we read one segment */ gst_ring_buffer_advance (buf, 1); } else { GST_OBJECT_LOCK (abuf); if (!abuf->running) goto stop_running; GST_DEBUG_OBJECT (src, "signal wait"); GST_AUDIORING_BUFFER_SIGNAL (buf); GST_DEBUG_OBJECT (src, "wait for action"); GST_AUDIORING_BUFFER_WAIT (buf); GST_DEBUG_OBJECT (src, "got signal"); if (!abuf->running) goto stop_running; GST_DEBUG_OBJECT (src, "continue running"); GST_OBJECT_UNLOCK (abuf); } } /* Will never be reached */ g_assert_not_reached (); return; /* ERROR */ no_function: { GST_DEBUG ("no write function, exit thread"); return; } stop_running: { GST_OBJECT_UNLOCK (abuf); GST_DEBUG ("stop running, exit thread"); message = gst_message_new_stream_status (GST_OBJECT_CAST (buf), GST_STREAM_STATUS_TYPE_LEAVE, GST_ELEMENT_CAST (src)); gst_message_set_stream_status_object (message, &val); GST_DEBUG_OBJECT (src, "posting LEAVE stream status"); gst_element_post_message (GST_ELEMENT_CAST (src), message); return; } }